Add Chromium-only Blender WebEngine parity work
This commit is contained in:
93
blender-5.2.0/intern/guardedalloc/intern/leak_detector.cc
Normal file
93
blender-5.2.0/intern/guardedalloc/intern/leak_detector.cc
Normal file
@@ -0,0 +1,93 @@
|
||||
/* SPDX-FileCopyrightText: 2020-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_mem
|
||||
*/
|
||||
|
||||
#include <any>
|
||||
#include <cstdio> /* Needed for `printf` on WIN32/APPLE. */
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
#include "mallocn_intern.hh"
|
||||
|
||||
bool leak_detector_has_run = false;
|
||||
char free_after_leak_detection_message[] =
|
||||
"Freeing memory after the leak detector has run. This can happen when using "
|
||||
"static variables in C++ that are defined outside of functions. To fix this "
|
||||
"error, use the 'construct on first use' idiom.";
|
||||
|
||||
namespace {
|
||||
|
||||
bool fail_on_memleak = false;
|
||||
|
||||
class MemLeakPrinter {
|
||||
public:
|
||||
~MemLeakPrinter()
|
||||
{
|
||||
leak_detector_has_run = true;
|
||||
const uint leaked_blocks = MEM_get_memory_blocks_in_use();
|
||||
if (leaked_blocks == 0) {
|
||||
return;
|
||||
}
|
||||
const size_t mem_in_use = MEM_get_memory_in_use();
|
||||
printf("Error: Not freed memory blocks: %u, total unfreed memory %f MB\n",
|
||||
leaked_blocks,
|
||||
double(mem_in_use) / 1024 / 1024);
|
||||
MEM_printmemlist();
|
||||
|
||||
/* In guarded implementation, the fact that all allocated memory blocks are stored in the
|
||||
* static `membase` listbase is enough for LSAN to not detect them as leaks. Clearing it solves
|
||||
* that issue. */
|
||||
mem_clearmemlist();
|
||||
|
||||
if (fail_on_memleak) {
|
||||
/* There are many other ways to change the exit code to failure here:
|
||||
* - Make the destructor `noexcept(false)` and throw an exception.
|
||||
* - Call exit(EXIT_FAILURE).
|
||||
* - Call terminate().
|
||||
*/
|
||||
abort();
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void MEM_init_memleak_detection()
|
||||
{
|
||||
/* Calling this ensures that the memory usage counters outlive the memory leak detection. */
|
||||
memory_usage_init();
|
||||
|
||||
/* Ensure that the static memleak data storage is initialized before the #MemLeakPrinter one, so
|
||||
* that it outlives the memory leak detection. */
|
||||
std::any any_data = std::make_any<int>(0);
|
||||
mem_guarded::internal::add_memleak_data(any_data);
|
||||
|
||||
/**
|
||||
* This variable is constructed when this function is first called. This should happen as soon as
|
||||
* possible when the program starts.
|
||||
*
|
||||
* It is destructed when the program exits. During destruction, it will print information about
|
||||
* leaked memory blocks. Static variables are destructed in reversed order of their
|
||||
* construction. Therefore, all static variables that own memory have to be constructed after
|
||||
* this function has been called.
|
||||
*/
|
||||
static MemLeakPrinter printer;
|
||||
}
|
||||
|
||||
void MEM_enable_fail_on_memleak()
|
||||
{
|
||||
fail_on_memleak = true;
|
||||
}
|
||||
|
||||
void mem_guarded::internal::add_memleak_data(std::any data)
|
||||
{
|
||||
static std::mutex mutex;
|
||||
static std::vector<std::any> data_vec;
|
||||
std::lock_guard lock{mutex};
|
||||
data_vec.push_back(std::move(data));
|
||||
}
|
||||
232
blender-5.2.0/intern/guardedalloc/intern/mallocn.cc
Normal file
232
blender-5.2.0/intern/guardedalloc/intern/mallocn.cc
Normal file
@@ -0,0 +1,232 @@
|
||||
/* SPDX-FileCopyrightText: 2002-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_mem
|
||||
*
|
||||
* Guarded memory allocation, and boundary-write detection.
|
||||
*/
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
/* To ensure strict conversions. */
|
||||
#include "../../source/blender/blenlib/BLI_strict_flags.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "mallocn_intern.hh"
|
||||
|
||||
#include "mallocn_intern_function_pointers.hh"
|
||||
|
||||
using namespace mem_guarded::internal;
|
||||
|
||||
/* NOTE: Keep in sync with MEM_use_lockfree_allocator(). */
|
||||
|
||||
size_t (*MEM_allocN_len)(const void *vmemh) = MEM_lockfree_allocN_len;
|
||||
void (*mem_guarded::internal::mem_freeN_ex)(void *vmemh,
|
||||
DestructorType destructor_type) = MEM_lockfree_freeN;
|
||||
void *(*mem_guarded::internal::mem_dupallocN)(const void *vmemh) = MEM_lockfree_dupallocN;
|
||||
void *(*MEM_realloc_uninitialized_id)(void *vmemh,
|
||||
size_t len,
|
||||
const char *str) = MEM_lockfree_reallocN_id;
|
||||
void *(*MEM_realloc_zeroed_id)(void *vmemh,
|
||||
size_t len,
|
||||
const char *str) = MEM_lockfree_recallocN_id;
|
||||
void *(*mem_guarded::internal::mem_callocN)(size_t len, const char *str) = MEM_lockfree_callocN;
|
||||
void *(*mem_guarded::internal::mem_calloc_arrayN)(size_t len,
|
||||
size_t size,
|
||||
const char *str) = MEM_lockfree_calloc_arrayN;
|
||||
void *(*mem_guarded::internal::mem_mallocN)(size_t len, const char *str) = MEM_lockfree_mallocN;
|
||||
void *(*mem_guarded::internal::mem_malloc_arrayN)(size_t len,
|
||||
size_t size,
|
||||
const char *str) = MEM_lockfree_malloc_arrayN;
|
||||
void *(*mem_guarded::internal::mem_mallocN_aligned_ex)(size_t len,
|
||||
size_t alignment,
|
||||
const char *str,
|
||||
DestructorType destructor_type) =
|
||||
MEM_lockfree_mallocN_aligned;
|
||||
void *(*MEM_new_array_uninitialized_aligned)(size_t len,
|
||||
size_t size,
|
||||
size_t alignment,
|
||||
const char *str) = MEM_lockfree_malloc_arrayN_aligned;
|
||||
void *(*MEM_new_array_zeroed_aligned)(size_t len,
|
||||
size_t size,
|
||||
size_t alignment,
|
||||
const char *str) = MEM_lockfree_calloc_arrayN_aligned;
|
||||
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_consistency_check)(void) = MEM_lockfree_consistency_check;
|
||||
void (*MEM_set_memory_debug)(void) = MEM_lockfree_set_memory_debug;
|
||||
size_t (*MEM_get_memory_in_use)(void) = MEM_lockfree_get_memory_in_use;
|
||||
uint (*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;
|
||||
size_t (*MEM_get_peak_memory)(void) = MEM_lockfree_get_peak_memory;
|
||||
|
||||
void (*mem_clearmemlist)(void) = mem_lockfree_clearmemlist;
|
||||
|
||||
#ifndef NDEBUG
|
||||
const char *(*MEM_name_ptr)(void *vmemh) = MEM_lockfree_name_ptr;
|
||||
void (*MEM_name_ptr_set)(void *vmemh, const char *str) = MEM_lockfree_name_ptr_set;
|
||||
#endif
|
||||
|
||||
void *aligned_malloc(size_t size, size_t alignment)
|
||||
{
|
||||
/* #posix_memalign requires alignment to be a multiple of `sizeof(void *)`. */
|
||||
assert(alignment >= ALIGNED_MALLOC_MINIMUM_ALIGNMENT);
|
||||
|
||||
#ifdef _WIN32
|
||||
return _aligned_malloc(size, alignment);
|
||||
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
void *result;
|
||||
|
||||
if (posix_memalign(&result, alignment, size)) {
|
||||
/* non-zero means allocation error
|
||||
* either no allocation or bad alignment value
|
||||
*/
|
||||
return NULL;
|
||||
}
|
||||
return result;
|
||||
#else /* This is for Linux. */
|
||||
return memalign(alignment, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void aligned_free(void *ptr)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
free(ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MEM_delete_void(void *vmemh)
|
||||
{
|
||||
mem_freeN_ex(vmemh, DestructorType::Trivial);
|
||||
}
|
||||
|
||||
void *MEM_new_zeroed(size_t len, const char *str)
|
||||
{
|
||||
return mem_callocN(len, str);
|
||||
}
|
||||
|
||||
void *MEM_new_array_zeroed(size_t len, size_t size, const char *str)
|
||||
{
|
||||
return mem_calloc_arrayN(len, size, str);
|
||||
}
|
||||
|
||||
void *MEM_new_uninitialized(size_t len, const char *str)
|
||||
{
|
||||
return mem_mallocN(len, str);
|
||||
}
|
||||
|
||||
void *MEM_new_array_uninitialized(size_t len, size_t size, const char *str)
|
||||
{
|
||||
return mem_malloc_arrayN(len, size, str);
|
||||
}
|
||||
|
||||
void *MEM_new_uninitialized_aligned(size_t len, size_t alignment, const char *str)
|
||||
{
|
||||
return mem_mallocN_aligned_ex(len, alignment, str, DestructorType::Trivial);
|
||||
}
|
||||
|
||||
void *MEM_dupalloc_void(const void *vmemh)
|
||||
{
|
||||
return mem_dupallocN(vmemh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform assert checks on allocator type change.
|
||||
*
|
||||
* Helps catching issues (in debug build) caused by an unintended allocator type change when there
|
||||
* are allocation happened.
|
||||
*/
|
||||
static void assert_for_allocator_change()
|
||||
{
|
||||
/* NOTE: Assume that there is no "sticky" internal state which would make switching allocator
|
||||
* type after all allocations are freed unsafe. In fact, it should be safe to change allocator
|
||||
* type after all blocks has been freed: some regression tests do rely on this property of
|
||||
* allocators. */
|
||||
assert(MEM_get_memory_blocks_in_use() == 0);
|
||||
}
|
||||
|
||||
void MEM_use_lockfree_allocator()
|
||||
{
|
||||
/* NOTE: Keep in sync with static initialization of the variables. */
|
||||
|
||||
/* TODO(sergey): Find a way to de-duplicate the logic. Maybe by requiring an explicit call
|
||||
* to guarded allocator initialization at an application startup. */
|
||||
|
||||
assert_for_allocator_change();
|
||||
|
||||
MEM_allocN_len = MEM_lockfree_allocN_len;
|
||||
mem_freeN_ex = MEM_lockfree_freeN;
|
||||
mem_dupallocN = MEM_lockfree_dupallocN;
|
||||
MEM_realloc_uninitialized_id = MEM_lockfree_reallocN_id;
|
||||
MEM_realloc_zeroed_id = MEM_lockfree_recallocN_id;
|
||||
mem_callocN = MEM_lockfree_callocN;
|
||||
mem_calloc_arrayN = MEM_lockfree_calloc_arrayN;
|
||||
mem_mallocN = MEM_lockfree_mallocN;
|
||||
mem_malloc_arrayN = MEM_lockfree_malloc_arrayN;
|
||||
mem_mallocN_aligned_ex = MEM_lockfree_mallocN_aligned;
|
||||
MEM_new_array_uninitialized_aligned = MEM_lockfree_malloc_arrayN_aligned;
|
||||
MEM_new_array_zeroed_aligned = MEM_lockfree_calloc_arrayN_aligned;
|
||||
MEM_printmemlist_pydict = MEM_lockfree_printmemlist_pydict;
|
||||
MEM_printmemlist = MEM_lockfree_printmemlist;
|
||||
MEM_callbackmemlist = MEM_lockfree_callbackmemlist;
|
||||
MEM_printmemlist_stats = MEM_lockfree_printmemlist_stats;
|
||||
MEM_set_error_callback = MEM_lockfree_set_error_callback;
|
||||
MEM_consistency_check = MEM_lockfree_consistency_check;
|
||||
MEM_set_memory_debug = MEM_lockfree_set_memory_debug;
|
||||
MEM_get_memory_in_use = MEM_lockfree_get_memory_in_use;
|
||||
MEM_get_memory_blocks_in_use = MEM_lockfree_get_memory_blocks_in_use;
|
||||
MEM_reset_peak_memory = MEM_lockfree_reset_peak_memory;
|
||||
MEM_get_peak_memory = MEM_lockfree_get_peak_memory;
|
||||
|
||||
mem_clearmemlist = mem_lockfree_clearmemlist;
|
||||
|
||||
#ifndef NDEBUG
|
||||
MEM_name_ptr = MEM_lockfree_name_ptr;
|
||||
MEM_name_ptr_set = MEM_lockfree_name_ptr_set;
|
||||
#endif
|
||||
}
|
||||
|
||||
void MEM_use_guarded_allocator()
|
||||
{
|
||||
assert_for_allocator_change();
|
||||
|
||||
MEM_allocN_len = MEM_guarded_allocN_len;
|
||||
mem_freeN_ex = MEM_guarded_freeN;
|
||||
mem_dupallocN = MEM_guarded_dupallocN;
|
||||
MEM_realloc_uninitialized_id = MEM_guarded_reallocN_id;
|
||||
MEM_realloc_zeroed_id = MEM_guarded_recallocN_id;
|
||||
mem_callocN = MEM_guarded_callocN;
|
||||
mem_calloc_arrayN = MEM_guarded_calloc_arrayN;
|
||||
mem_mallocN = MEM_guarded_mallocN;
|
||||
mem_malloc_arrayN = MEM_guarded_malloc_arrayN;
|
||||
mem_mallocN_aligned_ex = MEM_guarded_mallocN_aligned;
|
||||
MEM_new_array_uninitialized_aligned = MEM_guarded_malloc_arrayN_aligned;
|
||||
MEM_new_array_zeroed_aligned = MEM_guarded_calloc_arrayN_aligned;
|
||||
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_consistency_check = MEM_guarded_consistency_check;
|
||||
MEM_set_memory_debug = MEM_guarded_set_memory_debug;
|
||||
MEM_get_memory_in_use = MEM_guarded_get_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;
|
||||
|
||||
mem_clearmemlist = mem_guarded_clearmemlist;
|
||||
|
||||
#ifndef NDEBUG
|
||||
MEM_name_ptr = MEM_guarded_name_ptr;
|
||||
MEM_name_ptr_set = MEM_guarded_name_ptr_set;
|
||||
#endif
|
||||
}
|
||||
1425
blender-5.2.0/intern/guardedalloc/intern/mallocn_guarded_impl.cc
Normal file
1425
blender-5.2.0/intern/guardedalloc/intern/mallocn_guarded_impl.cc
Normal file
File diff suppressed because it is too large
Load Diff
69
blender-5.2.0/intern/guardedalloc/intern/mallocn_inline.hh
Normal file
69
blender-5.2.0/intern/guardedalloc/intern/mallocn_inline.hh
Normal file
@@ -0,0 +1,69 @@
|
||||
/* SPDX-FileCopyrightText: 2002-2017 `Jason Evans <jasone@canonware.com>`. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2007-2012 Mozilla Foundation. All rights reserved.
|
||||
* SPDX-FileCopyrightText: 2009-2017 Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_mem
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
/* BEGIN copied from BLI_asan.h */
|
||||
|
||||
/* Clang defines this. */
|
||||
#ifndef __has_feature
|
||||
# define __has_feature(x) 0
|
||||
#endif
|
||||
|
||||
#if (defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)) && \
|
||||
(!defined(_MSC_VER) || _MSC_VER > 1929) /* MSVC 2019 and below doesn't ship ASAN headers. */
|
||||
# include "sanitizer/asan_interface.h"
|
||||
# define WITH_ASAN
|
||||
#else
|
||||
/* Ensure return value is used. Just using UNUSED_VARS results in a warning. */
|
||||
# define ASAN_POISON_MEMORY_REGION(addr, size) (void)(0 && ((size) != 0 && (addr) != NULL))
|
||||
# define ASAN_UNPOISON_MEMORY_REGION(addr, size) (void)(0 && ((size) != 0 && (addr) != NULL))
|
||||
#endif
|
||||
|
||||
/* END copied from BLI_asan.h */
|
||||
|
||||
/**
|
||||
* Util to trigger an error for the given memory block.
|
||||
*
|
||||
* When ASAN is available, it will poison the memory block before accessing it, to trigger a
|
||||
* detailed ASAN report. Otherwise, it will abort if aborting on assert is set.
|
||||
*/
|
||||
#ifdef WITH_ASAN
|
||||
MEM_INLINE void MEM_trigger_error_on_memory_block(const void *address, const size_t size)
|
||||
{
|
||||
if (address == nullptr) {
|
||||
# ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
# endif
|
||||
return;
|
||||
}
|
||||
|
||||
/* Trigger ASAN error by poisoning the memory and accessing it. */
|
||||
ASAN_POISON_MEMORY_REGION(address, size);
|
||||
char *buffer = const_cast<char *>(static_cast<const char *>(address));
|
||||
const char c = *buffer;
|
||||
*buffer &= 255;
|
||||
*buffer = c;
|
||||
|
||||
/* In case ASAN is set to not terminate on error, but abort on assert is requested. */
|
||||
# ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
# endif
|
||||
ASAN_UNPOISON_MEMORY_REGION(address, size);
|
||||
}
|
||||
#else
|
||||
MEM_INLINE void MEM_trigger_error_on_memory_block(const void * /*address*/, const size_t /*size*/)
|
||||
{
|
||||
# ifdef WITH_ASSERT_ABORT
|
||||
abort();
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
226
blender-5.2.0/intern/guardedalloc/intern/mallocn_intern.hh
Normal file
226
blender-5.2.0/intern/guardedalloc/intern/mallocn_intern.hh
Normal file
@@ -0,0 +1,226 @@
|
||||
/* SPDX-FileCopyrightText: 2013 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_mem
|
||||
*/
|
||||
|
||||
#ifdef __GNUC__
|
||||
# define UNUSED(x) UNUSED_##x __attribute__((__unused__))
|
||||
#elif defined(_MSC_VER)
|
||||
/* NOTE: This suppresses the warning for the line, not the attribute. */
|
||||
# define UNUSED(x) UNUSED_##x __pragma(warning(suppress : 4100))
|
||||
#else
|
||||
# define UNUSED(x) UNUSED_##x
|
||||
#endif
|
||||
|
||||
#undef HAVE_MALLOC_STATS
|
||||
#define USE_MALLOC_USABLE_SIZE /* internal, when we have malloc_usable_size() */
|
||||
|
||||
#if defined(HAVE_MALLOC_STATS_H)
|
||||
# include <malloc.h>
|
||||
# define HAVE_MALLOC_STATS
|
||||
#elif defined(__FreeBSD__)
|
||||
# include <malloc_np.h>
|
||||
#elif defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
# undef USE_MALLOC_USABLE_SIZE
|
||||
#elif defined(__APPLE__)
|
||||
# include <malloc/malloc.h>
|
||||
# define malloc_usable_size malloc_size
|
||||
#elif defined(WIN32)
|
||||
# include <malloc.h>
|
||||
# define malloc_usable_size _msize
|
||||
#elif defined(__HAIKU__)
|
||||
# include <malloc.h>
|
||||
size_t malloc_usable_size(void *ptr);
|
||||
#else
|
||||
# pragma message "We don't know how to use malloc_usable_size on your platform"
|
||||
# undef USE_MALLOC_USABLE_SIZE
|
||||
#endif
|
||||
|
||||
#define SIZET_FORMAT "%zu"
|
||||
#define SIZET_ARG(a) (size_t(a))
|
||||
|
||||
#define SIZET_ALIGN_4(len) ((len + 3) & ~size_t(3))
|
||||
|
||||
#ifdef __GNUC__
|
||||
# define LIKELY(x) __builtin_expect(!!(x), 1)
|
||||
# define UNLIKELY(x) __builtin_expect(!!(x), 0)
|
||||
#else
|
||||
# define LIKELY(x) (x)
|
||||
# define UNLIKELY(x) (x)
|
||||
#endif
|
||||
|
||||
#if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__)
|
||||
/* Needed for `memalign` on Linux and `_aligned_alloc` on Windows. */
|
||||
|
||||
# include <malloc.h>
|
||||
#else
|
||||
/* Apple's malloc is 16-byte aligned, and does not have `malloc.h`, so include `stdilb` instead. */
|
||||
# include <stdlib.h>
|
||||
#endif
|
||||
|
||||
/* visual studio 2012 does not define inline for C */
|
||||
#ifdef _MSC_VER
|
||||
# define MEM_INLINE static __inline
|
||||
#else
|
||||
# define MEM_INLINE static inline
|
||||
#endif
|
||||
|
||||
#define IS_POW2(a) (((a) & ((a) - 1)) == 0)
|
||||
|
||||
/* Extra padding which needs to be applied on MemHead to make it aligned. */
|
||||
#define MEMHEAD_ALIGN_PADDING(alignment) \
|
||||
(size_t(alignment) - (sizeof(MemHeadAligned) % size_t(alignment)))
|
||||
|
||||
/* Real pointer returned by the `malloc` or `aligned_alloc`. */
|
||||
#define MEMHEAD_REAL_PTR(memh) ((char *)memh - MEMHEAD_ALIGN_PADDING(memh->alignment))
|
||||
|
||||
#include "mallocn_inline.hh"
|
||||
|
||||
#define ALIGNED_MALLOC_MINIMUM_ALIGNMENT sizeof(void *)
|
||||
|
||||
void *aligned_malloc(size_t size, size_t alignment);
|
||||
void aligned_free(void *ptr);
|
||||
|
||||
extern bool leak_detector_has_run;
|
||||
extern char free_after_leak_detection_message[];
|
||||
|
||||
void memory_usage_init(void);
|
||||
void memory_usage_block_alloc(size_t size);
|
||||
void memory_usage_block_free(size_t size);
|
||||
size_t memory_usage_block_num(void);
|
||||
size_t memory_usage_current(void);
|
||||
/**
|
||||
* Get the approximate peak memory usage since the last call to #memory_usage_peak_reset.
|
||||
* This is approximate, because the peak usage is not updated after every allocation (see
|
||||
* #peak_update_threshold).
|
||||
*
|
||||
* In the worst case, the peak memory usage is underestimated by
|
||||
* `peak_update_threshold * #threads`. After large allocations (larger than the threshold), the
|
||||
* peak usage is always updated so those allocations will always be taken into account.
|
||||
*/
|
||||
size_t memory_usage_peak(void);
|
||||
void memory_usage_peak_reset(void);
|
||||
|
||||
/**
|
||||
* Clear the listbase of allocated memory blocks.
|
||||
*
|
||||
* WARNING: This will make the whole guardedalloc system fully inconsistent. It is only indented to
|
||||
* be called in one place: the destructor of the #MemLeakPrinter class, which is only
|
||||
* instantiated once as a static variable by #MEM_init_memleak_detection, and therefore destructed
|
||||
* once at program exit.
|
||||
*/
|
||||
extern void (*mem_clearmemlist)(void);
|
||||
|
||||
/* Prototypes for counted allocator functions */
|
||||
size_t MEM_lockfree_allocN_len(const void *vmemh) ATTR_WARN_UNUSED_RESULT;
|
||||
void MEM_lockfree_freeN(void *vmemh, mem_guarded::internal::DestructorType destructor_type);
|
||||
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 *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(2);
|
||||
void *MEM_lockfree_recallocN_id(void *vmemh,
|
||||
size_t len,
|
||||
const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(2);
|
||||
void *MEM_lockfree_callocN(size_t len, const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
|
||||
void *MEM_lockfree_calloc_arrayN(size_t len,
|
||||
size_t size,
|
||||
const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(3);
|
||||
void *MEM_lockfree_mallocN(size_t len, const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
|
||||
void *MEM_lockfree_malloc_arrayN(size_t len,
|
||||
size_t size,
|
||||
const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(3);
|
||||
void *MEM_lockfree_mallocN_aligned(size_t len,
|
||||
size_t alignment,
|
||||
const char *str,
|
||||
mem_guarded::internal::DestructorType destructor_type)
|
||||
ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(3);
|
||||
void *MEM_lockfree_malloc_arrayN_aligned(size_t len,
|
||||
size_t size,
|
||||
size_t alignment,
|
||||
const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(4);
|
||||
void *MEM_lockfree_calloc_arrayN_aligned(size_t len,
|
||||
size_t size,
|
||||
size_t alignment,
|
||||
const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(4);
|
||||
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_consistency_check(void);
|
||||
void MEM_lockfree_set_memory_debug(void);
|
||||
size_t MEM_lockfree_get_memory_in_use(void);
|
||||
unsigned int MEM_lockfree_get_memory_blocks_in_use(void);
|
||||
void MEM_lockfree_reset_peak_memory(void);
|
||||
size_t MEM_lockfree_get_peak_memory(void) ATTR_WARN_UNUSED_RESULT;
|
||||
|
||||
void mem_lockfree_clearmemlist(void);
|
||||
|
||||
#ifndef NDEBUG
|
||||
const char *MEM_lockfree_name_ptr(void *vmemh);
|
||||
void MEM_lockfree_name_ptr_set(void *vmemh, const char *str);
|
||||
#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, mem_guarded::internal::DestructorType destructor_type);
|
||||
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 *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(2);
|
||||
void *MEM_guarded_recallocN_id(void *vmemh,
|
||||
size_t len,
|
||||
const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(2);
|
||||
void *MEM_guarded_callocN(size_t len, const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
|
||||
void *MEM_guarded_calloc_arrayN(size_t len,
|
||||
size_t size,
|
||||
const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(3);
|
||||
void *MEM_guarded_mallocN(size_t len, const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
|
||||
void *MEM_guarded_malloc_arrayN(size_t len,
|
||||
size_t size,
|
||||
const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(3);
|
||||
void *MEM_guarded_mallocN_aligned(size_t len,
|
||||
size_t alignment,
|
||||
const char *str,
|
||||
mem_guarded::internal::DestructorType destructor_type)
|
||||
ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(3);
|
||||
void *MEM_guarded_malloc_arrayN_aligned(size_t len, size_t size, size_t alignment, const char *str)
|
||||
ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(4);
|
||||
void *MEM_guarded_calloc_arrayN_aligned(size_t len, size_t size, size_t alignment, const char *str)
|
||||
ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(4);
|
||||
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_consistency_check(void);
|
||||
void MEM_guarded_set_memory_debug(void);
|
||||
size_t MEM_guarded_get_memory_in_use(void);
|
||||
unsigned int MEM_guarded_get_memory_blocks_in_use(void);
|
||||
void MEM_guarded_reset_peak_memory(void);
|
||||
size_t MEM_guarded_get_peak_memory(void) ATTR_WARN_UNUSED_RESULT;
|
||||
|
||||
void mem_guarded_clearmemlist(void);
|
||||
|
||||
#ifndef NDEBUG
|
||||
const char *MEM_guarded_name_ptr(void *vmemh);
|
||||
void MEM_guarded_name_ptr_set(void *vmemh, const char *str);
|
||||
#endif
|
||||
@@ -0,0 +1,88 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_mem
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace mem_guarded::internal {
|
||||
|
||||
enum class DestructorType {
|
||||
/** Allocation has a trivial destructor. */
|
||||
Trivial,
|
||||
/** Allocation has a non-trivial destructor that should be called when freeing. */
|
||||
NonTrivial,
|
||||
};
|
||||
|
||||
/** Internal implementation of #MEM_delete_void, exposed because #MEM_delete needs access to it. */
|
||||
extern void (*mem_freeN_ex)(void *vmemh, DestructorType destructor_type);
|
||||
|
||||
/**
|
||||
* Internal implementation of #MEM_new_zeroed, exposed because public #MEM_new_zeroed cannot be a
|
||||
* function pointer, to allow its overload by C++ template version.
|
||||
*/
|
||||
extern void *(*mem_callocN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
|
||||
|
||||
/**
|
||||
* Internal implementation of #MEM_new_array_zeroed, exposed because public #MEM_new_array_zeroed
|
||||
* cannot be a function pointer, to allow its overload by C++ template version.
|
||||
*/
|
||||
extern void *(*mem_calloc_arrayN)(size_t len,
|
||||
size_t size,
|
||||
const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(3);
|
||||
|
||||
/**
|
||||
* Internal implementation of #MEM_new_uninitialized, exposed because public #MEM_new_uninitialized
|
||||
* cannot be a function pointer, to allow its overload by C++ template version.
|
||||
*/
|
||||
extern void *(*mem_mallocN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
|
||||
|
||||
/**
|
||||
* Internal implementation of #MEM_new_array_uninitialized, exposed because public
|
||||
* #MEM_new_array_uninitialized cannot be a function pointer, to allow its overload by C++ template
|
||||
* version.
|
||||
*/
|
||||
extern void *(*mem_malloc_arrayN)(size_t len,
|
||||
size_t size,
|
||||
const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(3);
|
||||
|
||||
/** Internal implementation of #MEM_new_uninitialized_aligned, exposed because #MEM_new needs
|
||||
* access to it.
|
||||
*/
|
||||
extern void *(*mem_mallocN_aligned_ex)(size_t len,
|
||||
size_t alignment,
|
||||
const char *str,
|
||||
DestructorType destructor_type);
|
||||
|
||||
/**
|
||||
* Internal implementation of #MEM_dupalloc_void, exposed because public #MEM_dupalloc_void cannot
|
||||
* be a function pointer, to allow its overload by C++ template version.
|
||||
*/
|
||||
extern void *(*mem_dupallocN)(const void *vmemh) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Store a std::any into a static opaque storage vector. The only purpose of this call is to
|
||||
* control the lifetime of the given data, there is no way to access it from here afterwards. User
|
||||
* code is expected to keep its own reference to the data contained in the `std::any` as long as it
|
||||
* needs it.
|
||||
*
|
||||
* Typically, this `any` should contain a `shared_ptr` to the actual data, to ensure that the data
|
||||
* itself is not duplicated, and that the static storage does become an owner of it.
|
||||
*
|
||||
* That way, the memleak data does not get destructed before the static storage is. Since this
|
||||
* storage is created before the memleak detection data (see the implementation of
|
||||
* #MEM_init_memleak_detection), it is guaranteed to happen after the execution and destruction of
|
||||
* the memleak detector.
|
||||
*/
|
||||
void add_memleak_data(std::any data);
|
||||
|
||||
} // namespace mem_guarded::internal
|
||||
@@ -0,0 +1,594 @@
|
||||
/* SPDX-FileCopyrightText: 2013-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup intern_mem
|
||||
*
|
||||
* Memory allocation which keeps track on allocated memory counters
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h> /* printf */
|
||||
#include <stdlib.h>
|
||||
#include <string.h> /* memcpy */
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
#include "MEM_safe_multiply.h"
|
||||
|
||||
/* Quiet warnings when dealing with allocated data written into the blend file.
|
||||
* This also rounds up and causes warnings which we don't consider bugs in practice. */
|
||||
#ifdef WITH_MEM_VALGRIND
|
||||
# include "valgrind/memcheck.h"
|
||||
#endif
|
||||
|
||||
/* to ensure strict conversions */
|
||||
#include "../../source/blender/blenlib/BLI_strict_flags.h"
|
||||
|
||||
#include "atomic_ops.h"
|
||||
#include "mallocn_intern.hh"
|
||||
#include "mallocn_intern_function_pointers.hh"
|
||||
|
||||
using namespace mem_guarded::internal;
|
||||
|
||||
namespace {
|
||||
|
||||
typedef struct MemHead {
|
||||
/* Length of allocated memory block. */
|
||||
size_t len;
|
||||
} MemHead;
|
||||
static_assert(MEM_MIN_CPP_ALIGNMENT <= alignof(MemHead), "Bad alignment of MemHead");
|
||||
static_assert(MEM_MIN_CPP_ALIGNMENT <= sizeof(MemHead), "Bad size of MemHead");
|
||||
|
||||
typedef struct MemHeadAligned {
|
||||
short alignment;
|
||||
size_t len;
|
||||
} MemHeadAligned;
|
||||
static_assert(MEM_MIN_CPP_ALIGNMENT <= alignof(MemHeadAligned), "Bad alignment of MemHeadAligned");
|
||||
static_assert(MEM_MIN_CPP_ALIGNMENT <= sizeof(MemHeadAligned), "Bad size of MemHeadAligned");
|
||||
|
||||
} // namespace
|
||||
|
||||
static bool malloc_debug_memset = false;
|
||||
|
||||
static void (*error_callback)(const char *) = nullptr;
|
||||
|
||||
/**
|
||||
* Guardedalloc always allocate multiple of 4 bytes. That means that the lower 2 bits of the
|
||||
* `len` member of #MemHead/#MemHeadAligned data can be used for the bitflags below.
|
||||
*/
|
||||
enum {
|
||||
/** This block used aligned allocation, and its 'head' is of #MemHeadAligned type. */
|
||||
MEMHEAD_FLAG_ALIGN = 1 << 0,
|
||||
/**
|
||||
* This block of memory has been allocated for a type with a non-trivial destructor.
|
||||
* This checks that #MEM_delete is used to free the memory, and not #MEM_delete_void.
|
||||
*/
|
||||
MEMHEAD_FLAG_NONTRIVIAL_DESTRUCTOR = 1 << 1,
|
||||
|
||||
MEMHEAD_FLAG_MASK = (1 << 2) - 1
|
||||
};
|
||||
|
||||
#define MEMHEAD_FROM_PTR(ptr) (((MemHead *)ptr) - 1)
|
||||
#define PTR_FROM_MEMHEAD(memhead) (memhead + 1)
|
||||
#define MEMHEAD_ALIGNED_FROM_PTR(ptr) (((MemHeadAligned *)ptr) - 1)
|
||||
#define MEMHEAD_IS_ALIGNED(memhead) ((memhead)->len & size_t(MEMHEAD_FLAG_ALIGN))
|
||||
#define MEMHEAD_HAS_NONTRIVIAL_DESTRUCTOR(memhead) \
|
||||
((memhead)->len & size_t(MEMHEAD_FLAG_NONTRIVIAL_DESTRUCTOR))
|
||||
#define MEMHEAD_LEN(memhead) ((memhead)->len & ~size_t(MEMHEAD_FLAG_MASK))
|
||||
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 1, 0)))
|
||||
#endif
|
||||
static void
|
||||
print_error(const char *message, va_list str_format_args)
|
||||
{
|
||||
char buf[512];
|
||||
vsnprintf(buf, sizeof(buf), message, str_format_args);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
if (error_callback) {
|
||||
error_callback(buf);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
#endif
|
||||
static void
|
||||
print_error(const char *message, ...)
|
||||
{
|
||||
va_list str_format_args;
|
||||
va_start(str_format_args, message);
|
||||
print_error(message, str_format_args);
|
||||
va_end(str_format_args);
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 2, 3)))
|
||||
#endif
|
||||
static void
|
||||
report_error_on_address(const void *vmemh, const char *message, ...)
|
||||
{
|
||||
va_list str_format_args;
|
||||
|
||||
va_start(str_format_args, message);
|
||||
print_error(message, str_format_args);
|
||||
va_end(str_format_args);
|
||||
|
||||
if (vmemh == nullptr) {
|
||||
MEM_trigger_error_on_memory_block(nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
|
||||
const size_t len = MEMHEAD_LEN(memh);
|
||||
|
||||
const void *address = memh;
|
||||
size_t size = len + sizeof(*memh);
|
||||
if (UNLIKELY(MEMHEAD_IS_ALIGNED(memh))) {
|
||||
const MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
|
||||
address = MEMHEAD_REAL_PTR(memh_aligned);
|
||||
size = len + sizeof(*memh_aligned) + MEMHEAD_ALIGN_PADDING(memh_aligned->alignment);
|
||||
}
|
||||
MEM_trigger_error_on_memory_block(address, size);
|
||||
}
|
||||
|
||||
size_t MEM_lockfree_allocN_len(const void *vmemh)
|
||||
{
|
||||
if (LIKELY(vmemh)) {
|
||||
return MEMHEAD_LEN(MEMHEAD_FROM_PTR(vmemh));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MEM_lockfree_freeN(void *vmemh, DestructorType destructor_type)
|
||||
{
|
||||
if (UNLIKELY(leak_detector_has_run)) {
|
||||
print_error("%s\n", free_after_leak_detection_message);
|
||||
}
|
||||
|
||||
if (UNLIKELY(vmemh == nullptr)) {
|
||||
report_error_on_address(vmemh, "Attempt to free nullptr pointer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
|
||||
size_t len = MEMHEAD_LEN(memh);
|
||||
|
||||
if (destructor_type != DestructorType::NonTrivial && MEMHEAD_HAS_NONTRIVIAL_DESTRUCTOR(memh)) {
|
||||
report_error_on_address(vmemh,
|
||||
"Attempt to use C-style MEM_delete_void on a pointer created with "
|
||||
"CPP-style MEM_new or new\n");
|
||||
}
|
||||
|
||||
memory_usage_block_free(len);
|
||||
|
||||
if (UNLIKELY(malloc_debug_memset && len)) {
|
||||
memset(memh + 1, 255, len);
|
||||
}
|
||||
if (UNLIKELY(MEMHEAD_IS_ALIGNED(memh))) {
|
||||
MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
|
||||
aligned_free(MEMHEAD_REAL_PTR(memh_aligned));
|
||||
}
|
||||
else {
|
||||
free(memh);
|
||||
}
|
||||
}
|
||||
|
||||
void *MEM_lockfree_dupallocN(const void *vmemh)
|
||||
{
|
||||
void *newp = nullptr;
|
||||
if (vmemh) {
|
||||
const MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
|
||||
const size_t prev_size = MEM_lockfree_allocN_len(vmemh);
|
||||
|
||||
if (MEMHEAD_HAS_NONTRIVIAL_DESTRUCTOR(memh)) {
|
||||
report_error_on_address(vmemh,
|
||||
"Attempt to use C-style MEM_dupalloc_void on a pointer created with "
|
||||
"CPP-style MEM_new or new\n");
|
||||
}
|
||||
|
||||
if (UNLIKELY(MEMHEAD_IS_ALIGNED(memh))) {
|
||||
const MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
|
||||
newp = MEM_lockfree_mallocN_aligned(
|
||||
prev_size, size_t(memh_aligned->alignment), "dupli_malloc", DestructorType::Trivial);
|
||||
}
|
||||
else {
|
||||
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 = nullptr;
|
||||
|
||||
if (vmemh) {
|
||||
const MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
|
||||
const size_t old_len = MEM_lockfree_allocN_len(vmemh);
|
||||
|
||||
if (MEMHEAD_HAS_NONTRIVIAL_DESTRUCTOR(memh)) {
|
||||
report_error_on_address(
|
||||
vmemh,
|
||||
"Attempt to use C-style MEM_realloc_uninitialized on a pointer created with "
|
||||
"CPP-style MEM_new or new\n");
|
||||
}
|
||||
|
||||
if (LIKELY(!MEMHEAD_IS_ALIGNED(memh))) {
|
||||
newp = MEM_lockfree_mallocN(len, "realloc");
|
||||
}
|
||||
else {
|
||||
const MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
|
||||
newp = MEM_lockfree_mallocN_aligned(
|
||||
len, size_t(memh_aligned->alignment), "realloc", DestructorType::Trivial);
|
||||
}
|
||||
|
||||
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, DestructorType::Trivial);
|
||||
}
|
||||
else {
|
||||
newp = MEM_lockfree_mallocN(len, str);
|
||||
}
|
||||
|
||||
return newp;
|
||||
}
|
||||
|
||||
void *MEM_lockfree_recallocN_id(void *vmemh, size_t len, const char *str)
|
||||
{
|
||||
void *newp = nullptr;
|
||||
|
||||
if (vmemh) {
|
||||
const MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
|
||||
const size_t old_len = MEM_lockfree_allocN_len(vmemh);
|
||||
|
||||
if (MEMHEAD_HAS_NONTRIVIAL_DESTRUCTOR(memh)) {
|
||||
report_error_on_address(
|
||||
vmemh,
|
||||
"Attempt to use C-style MEM_realloc_zeroed on a pointer created with "
|
||||
"CPP-style MEM_new or new\n");
|
||||
}
|
||||
|
||||
if (LIKELY(!MEMHEAD_IS_ALIGNED(memh))) {
|
||||
newp = MEM_lockfree_mallocN(len, "recalloc");
|
||||
}
|
||||
else {
|
||||
const MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
|
||||
newp = MEM_lockfree_mallocN_aligned(
|
||||
len, size_t(memh_aligned->alignment), "recalloc", DestructorType::Trivial);
|
||||
}
|
||||
|
||||
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, DestructorType::Trivial);
|
||||
}
|
||||
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 (LIKELY(memh)) {
|
||||
memh->len = len;
|
||||
memory_usage_block_alloc(len);
|
||||
|
||||
return PTR_FROM_MEMHEAD(memh);
|
||||
}
|
||||
print_error("Calloc returns null: len=" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
|
||||
SIZET_ARG(len),
|
||||
str,
|
||||
memory_usage_current());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *MEM_lockfree_calloc_arrayN(size_t len, size_t size, const char *str)
|
||||
{
|
||||
size_t total_size;
|
||||
if (UNLIKELY(!MEM_size_safe_multiply(len, size, &total_size))) {
|
||||
print_error(
|
||||
"Calloc array aborted due to integer overflow: "
|
||||
"len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
|
||||
SIZET_ARG(len),
|
||||
SIZET_ARG(size),
|
||||
str,
|
||||
memory_usage_current());
|
||||
abort();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return MEM_lockfree_callocN(total_size, str);
|
||||
}
|
||||
|
||||
void *MEM_lockfree_mallocN(size_t len, const char *str)
|
||||
{
|
||||
MemHead *memh;
|
||||
|
||||
#ifdef WITH_MEM_VALGRIND
|
||||
const size_t len_unaligned = len;
|
||||
#endif
|
||||
len = SIZET_ALIGN_4(len);
|
||||
|
||||
memh = (MemHead *)malloc(len + sizeof(MemHead));
|
||||
|
||||
if (LIKELY(memh)) {
|
||||
|
||||
if (LIKELY(len)) {
|
||||
if (UNLIKELY(malloc_debug_memset)) {
|
||||
memset(memh + 1, 255, len);
|
||||
}
|
||||
#ifdef WITH_MEM_VALGRIND
|
||||
if (malloc_debug_memset) {
|
||||
VALGRIND_MAKE_MEM_UNDEFINED(memh + 1, len_unaligned);
|
||||
}
|
||||
else {
|
||||
VALGRIND_MAKE_MEM_DEFINED((const char *)(memh + 1) + len_unaligned, len - len_unaligned);
|
||||
}
|
||||
#endif /* WITH_MEM_VALGRIND */
|
||||
}
|
||||
|
||||
memh->len = len;
|
||||
memory_usage_block_alloc(len);
|
||||
|
||||
return PTR_FROM_MEMHEAD(memh);
|
||||
}
|
||||
print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
|
||||
SIZET_ARG(len),
|
||||
str,
|
||||
memory_usage_current());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *MEM_lockfree_malloc_arrayN(size_t len, size_t size, const char *str)
|
||||
{
|
||||
size_t total_size;
|
||||
if (UNLIKELY(!MEM_size_safe_multiply(len, size, &total_size))) {
|
||||
print_error(
|
||||
"Malloc array aborted due to integer overflow: "
|
||||
"len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
|
||||
SIZET_ARG(len),
|
||||
SIZET_ARG(size),
|
||||
str,
|
||||
memory_usage_current());
|
||||
abort();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return MEM_lockfree_mallocN(total_size, str);
|
||||
}
|
||||
|
||||
void *MEM_lockfree_mallocN_aligned(size_t len,
|
||||
size_t alignment,
|
||||
const char *str,
|
||||
const DestructorType destructor_type)
|
||||
{
|
||||
/* Huge alignment values doesn't make sense and they wouldn't fit into 'short' used in the
|
||||
* MemHead. */
|
||||
assert(alignment < 1024);
|
||||
|
||||
/* We only support alignments that are a power of two. */
|
||||
assert(IS_POW2(alignment));
|
||||
|
||||
/* Some OS specific aligned allocators require a certain minimal alignment. */
|
||||
if (alignment < ALIGNED_MALLOC_MINIMUM_ALIGNMENT) {
|
||||
alignment = ALIGNED_MALLOC_MINIMUM_ALIGNMENT;
|
||||
}
|
||||
|
||||
/* It's possible that MemHead's size is not properly aligned,
|
||||
* do extra padding to deal with this.
|
||||
*
|
||||
* We only support small alignments which fits into short in
|
||||
* order to save some bits in MemHead structure.
|
||||
*/
|
||||
size_t extra_padding = MEMHEAD_ALIGN_PADDING(alignment);
|
||||
|
||||
#ifdef WITH_MEM_VALGRIND
|
||||
const size_t len_unaligned = len;
|
||||
#endif
|
||||
len = SIZET_ALIGN_4(len);
|
||||
|
||||
MemHeadAligned *memh = (MemHeadAligned *)aligned_malloc(
|
||||
len + extra_padding + sizeof(MemHeadAligned), alignment);
|
||||
|
||||
if (LIKELY(memh)) {
|
||||
/* We keep padding in the beginning of MemHead,
|
||||
* this way it's always possible to get MemHead
|
||||
* from the data pointer.
|
||||
*/
|
||||
memh = (MemHeadAligned *)((char *)memh + extra_padding);
|
||||
|
||||
if (LIKELY(len)) {
|
||||
if (UNLIKELY(malloc_debug_memset)) {
|
||||
memset(memh + 1, 255, len);
|
||||
}
|
||||
#ifdef WITH_MEM_VALGRIND
|
||||
if (malloc_debug_memset) {
|
||||
VALGRIND_MAKE_MEM_UNDEFINED(memh + 1, len_unaligned);
|
||||
}
|
||||
else {
|
||||
VALGRIND_MAKE_MEM_DEFINED((const char *)(memh + 1) + len_unaligned, len - len_unaligned);
|
||||
}
|
||||
#endif /* WITH_MEM_VALGRIND */
|
||||
}
|
||||
|
||||
memh->len = len | size_t(MEMHEAD_FLAG_ALIGN) |
|
||||
size_t(destructor_type == DestructorType::NonTrivial ?
|
||||
MEMHEAD_FLAG_NONTRIVIAL_DESTRUCTOR :
|
||||
0);
|
||||
memh->alignment = short(alignment);
|
||||
memory_usage_block_alloc(len);
|
||||
|
||||
return PTR_FROM_MEMHEAD(memh);
|
||||
}
|
||||
print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
|
||||
SIZET_ARG(len),
|
||||
str,
|
||||
memory_usage_current());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void *mem_lockfree_malloc_arrayN_aligned(const size_t len,
|
||||
const size_t size,
|
||||
const size_t alignment,
|
||||
const char *str,
|
||||
size_t &r_bytes_num)
|
||||
{
|
||||
if (UNLIKELY(!MEM_size_safe_multiply(len, size, &r_bytes_num))) {
|
||||
print_error(
|
||||
"Calloc array aborted due to integer overflow: "
|
||||
"len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
|
||||
SIZET_ARG(len),
|
||||
SIZET_ARG(size),
|
||||
str,
|
||||
memory_usage_current());
|
||||
abort();
|
||||
return nullptr;
|
||||
}
|
||||
if (alignment <= MEM_MIN_CPP_ALIGNMENT) {
|
||||
return mem_mallocN(r_bytes_num, str);
|
||||
}
|
||||
void *ptr = MEM_new_uninitialized_aligned(r_bytes_num, alignment, str);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void *MEM_lockfree_malloc_arrayN_aligned(const size_t len,
|
||||
const size_t size,
|
||||
const size_t alignment,
|
||||
const char *str)
|
||||
{
|
||||
size_t bytes_num;
|
||||
return mem_lockfree_malloc_arrayN_aligned(len, size, alignment, str, bytes_num);
|
||||
}
|
||||
|
||||
void *MEM_lockfree_calloc_arrayN_aligned(const size_t len,
|
||||
const size_t size,
|
||||
const size_t alignment,
|
||||
const char *str)
|
||||
{
|
||||
/* There is no lower level #calloc with an alignment parameter, so unless the alignment is less
|
||||
* than or equal to what we'd get by default, we have to fall back to #memset unfortunately. */
|
||||
if (alignment <= MEM_MIN_CPP_ALIGNMENT) {
|
||||
return MEM_lockfree_calloc_arrayN(len, size, str);
|
||||
}
|
||||
|
||||
size_t bytes_num;
|
||||
void *ptr = mem_lockfree_malloc_arrayN_aligned(len, size, alignment, str, bytes_num);
|
||||
if (!ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
memset(ptr, 0, bytes_num);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void MEM_lockfree_printmemlist_pydict() {}
|
||||
|
||||
void MEM_lockfree_printmemlist() {}
|
||||
|
||||
void mem_lockfree_clearmemlist() {}
|
||||
|
||||
/* Unused. */
|
||||
|
||||
void MEM_lockfree_callbackmemlist(void (*func)(void *))
|
||||
{
|
||||
(void)func; /* Ignored. */
|
||||
}
|
||||
|
||||
void MEM_lockfree_printmemlist_stats()
|
||||
{
|
||||
printf("\ntotal memory len: %.3f MB\n", double(memory_usage_current()) / double(1024 * 1024));
|
||||
printf("peak memory len: %.3f MB\n", double(memory_usage_peak()) / 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_consistency_check()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void MEM_lockfree_set_memory_debug()
|
||||
{
|
||||
malloc_debug_memset = true;
|
||||
}
|
||||
|
||||
size_t MEM_lockfree_get_memory_in_use()
|
||||
{
|
||||
return memory_usage_current();
|
||||
}
|
||||
|
||||
uint MEM_lockfree_get_memory_blocks_in_use()
|
||||
{
|
||||
return uint(memory_usage_block_num());
|
||||
}
|
||||
|
||||
/* Dummy. */
|
||||
|
||||
void MEM_lockfree_reset_peak_memory()
|
||||
{
|
||||
memory_usage_peak_reset();
|
||||
}
|
||||
|
||||
size_t MEM_lockfree_get_peak_memory()
|
||||
{
|
||||
return memory_usage_peak();
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
const char *MEM_lockfree_name_ptr(void *vmemh)
|
||||
{
|
||||
if (vmemh) {
|
||||
return "unknown block name ptr";
|
||||
}
|
||||
|
||||
return "MEM_lockfree_name_ptr(nullptr)";
|
||||
}
|
||||
|
||||
void MEM_lockfree_name_ptr_set(void * /*vmemh*/, const char * /*str*/) {}
|
||||
#endif /* !NDEBUG */
|
||||
266
blender-5.2.0/intern/guardedalloc/intern/memory_usage.cc
Normal file
266
blender-5.2.0/intern/guardedalloc/intern/memory_usage.cc
Normal file
@@ -0,0 +1,266 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
#include "mallocn_intern.hh"
|
||||
|
||||
#include "../../source/blender/blenlib/BLI_strict_flags.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct Local;
|
||||
struct Global;
|
||||
|
||||
/**
|
||||
* This is stored per thread. Align to cache line size to avoid false sharing.
|
||||
*/
|
||||
struct alignas(128) Local {
|
||||
/**
|
||||
* Retain shared ownership of #Global to make sure that it is not destructed.
|
||||
*/
|
||||
std::shared_ptr<Global> global;
|
||||
|
||||
/** Helps to find bugs during program shutdown. */
|
||||
bool destructed = false;
|
||||
/**
|
||||
* This is the first created #Local and on the main thread. When the main local data is
|
||||
* destructed, we know that Blender is quitting and that we can't rely on thread locals being
|
||||
* available still.
|
||||
*/
|
||||
bool is_main = false;
|
||||
/**
|
||||
* Number of bytes. This can be negative when e.g. one thread allocates a lot of memory, and
|
||||
* another frees it. It has to be an atomic, because it may be accessed by other threads when the
|
||||
* total memory usage is counted.
|
||||
*/
|
||||
std::atomic<int64_t> mem_in_use = 0;
|
||||
/**
|
||||
* Number of allocated blocks. Can be negative and is atomic for the same reason as above.
|
||||
*/
|
||||
std::atomic<int64_t> blocks_num = 0;
|
||||
/**
|
||||
* Amount of memory used when the peak was last updated. This is used so that we don't have to
|
||||
* update the peak memory usage after every memory allocation. Instead it's only updated when "a
|
||||
* lot" of new memory has been allocated. This makes the peak memory usage a little bit less
|
||||
* accurate, but it's still good enough for practical purposes.
|
||||
*/
|
||||
std::atomic<int64_t> mem_in_use_during_peak_update = 0;
|
||||
|
||||
Local();
|
||||
~Local();
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a singleton that stores global data. It's owned by a `std::shared_ptr` which is owned by
|
||||
* the static variable in #get_global_ptr and all #Local objects.
|
||||
*/
|
||||
struct Global {
|
||||
/**
|
||||
* Mutex that protects the vector below.
|
||||
*/
|
||||
std::mutex locals_mutex;
|
||||
/**
|
||||
* All currently constructed #Local. This must only be accessed when the mutex above is
|
||||
* locked. Individual threads insert and remove themselves here.
|
||||
*/
|
||||
std::vector<Local *> locals;
|
||||
/**
|
||||
* Number of bytes that are not tracked by #Local. This is necessary because when a thread exits,
|
||||
* its #Local data is freed. The memory counts stored there would be lost. The memory counts may
|
||||
* be non-zero during thread destruction, if the thread did an unequal amount of allocations and
|
||||
* frees (which is perfectly valid behavior as long as other threads have the responsibility to
|
||||
* free any memory that the thread allocated).
|
||||
*
|
||||
* To solve this, the memory counts are added to these global counters when the thread
|
||||
* exists. The global counters are also used when the entire process starts to exit, because the
|
||||
* #Local data of the main thread is already destructed when the leak detection happens (during
|
||||
* destruction of static variables which happens after destruction of thread-locals).
|
||||
*/
|
||||
std::atomic<int64_t> mem_in_use_outside_locals = 0;
|
||||
/**
|
||||
* Number of blocks that are not tracked by #Local, for the same reason as above.
|
||||
*/
|
||||
std::atomic<int64_t> blocks_num_outside_locals = 0;
|
||||
/**
|
||||
* Peak memory usage since the last reset.
|
||||
*/
|
||||
std::atomic<size_t> peak = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* This is true for most of the lifetime of the program. Only when it starts exiting this becomes
|
||||
* false indicating that global counters should be used for correctness.
|
||||
*/
|
||||
static std::atomic<bool> use_local_counters = true;
|
||||
/**
|
||||
* When a thread allocated this amount of memory, the peak memory usage is updated. An alternative
|
||||
* would be to update the global peak memory after every allocation, but that would cause much more
|
||||
* overhead with little benefit.
|
||||
*/
|
||||
static constexpr int64_t peak_update_threshold = 1024 * 1024;
|
||||
|
||||
static std::shared_ptr<Global> &get_global_ptr()
|
||||
{
|
||||
static std::shared_ptr<Global> global = std::make_shared<Global>();
|
||||
return global;
|
||||
}
|
||||
|
||||
static Global &get_global()
|
||||
{
|
||||
return *get_global_ptr();
|
||||
}
|
||||
|
||||
static Local &get_local_data()
|
||||
{
|
||||
static thread_local Local local;
|
||||
assert(!local.destructed);
|
||||
return local;
|
||||
}
|
||||
|
||||
Local::Local()
|
||||
{
|
||||
this->global = get_global_ptr();
|
||||
|
||||
std::lock_guard lock{this->global->locals_mutex};
|
||||
if (this->global->locals.empty()) {
|
||||
/* This is the first thread creating #Local, it is therefore the main thread because it's
|
||||
* created through #memory_usage_init. */
|
||||
this->is_main = true;
|
||||
}
|
||||
/* Register self in the global list. */
|
||||
this->global->locals.push_back(this);
|
||||
}
|
||||
|
||||
Local::~Local()
|
||||
{
|
||||
std::lock_guard lock{this->global->locals_mutex};
|
||||
|
||||
/* Unregister self from the global list. */
|
||||
this->global->locals.erase(
|
||||
std::find(this->global->locals.begin(), this->global->locals.end(), this));
|
||||
/* Don't forget the memory counts stored locally. */
|
||||
this->global->blocks_num_outside_locals.fetch_add(this->blocks_num, std::memory_order_relaxed);
|
||||
this->global->mem_in_use_outside_locals.fetch_add(this->mem_in_use, std::memory_order_relaxed);
|
||||
|
||||
if (this->is_main) {
|
||||
/* The main thread started shutting down. Use global counters from now on to avoid accessing
|
||||
* thread-locals after they have been destructed. */
|
||||
use_local_counters.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
/* Helps to detect when thread locals are accidentally accessed after destruction. */
|
||||
this->destructed = true;
|
||||
}
|
||||
|
||||
/** Check if the current memory usage is higher than the peak and update it if yes. */
|
||||
static void update_global_peak()
|
||||
{
|
||||
Global &global = get_global();
|
||||
/* Update peak. */
|
||||
global.peak = std::max<size_t>(global.peak, memory_usage_current());
|
||||
|
||||
std::lock_guard lock{global.locals_mutex};
|
||||
|
||||
for (Local *local : global.locals) {
|
||||
assert(!local->destructed);
|
||||
/* Updating this makes sure that the peak is not updated too often, which would degrade
|
||||
* performance. */
|
||||
local->mem_in_use_during_peak_update = local->mem_in_use.load(std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
void memory_usage_init()
|
||||
{
|
||||
/* Makes sure that the static and thread-local variables on the main thread are initialized. */
|
||||
get_local_data();
|
||||
}
|
||||
|
||||
void memory_usage_block_alloc(const size_t size)
|
||||
{
|
||||
if (LIKELY(use_local_counters.load(std::memory_order_relaxed))) {
|
||||
Local &local = get_local_data();
|
||||
/* Increase local memory counts. This does not cause thread synchronization in the majority of
|
||||
* cases, because each thread has these counters on a separate cache line. It may only cause
|
||||
* synchronization if another thread is computing the total current memory usage at the same
|
||||
* time, which is very rare compared to doing allocations. */
|
||||
local.blocks_num.fetch_add(1, std::memory_order_relaxed);
|
||||
local.mem_in_use.fetch_add(int64_t(size), std::memory_order_relaxed);
|
||||
|
||||
/* If a certain amount of new memory has been allocated, update the peak. */
|
||||
if (local.mem_in_use - local.mem_in_use_during_peak_update > peak_update_threshold) {
|
||||
update_global_peak();
|
||||
}
|
||||
}
|
||||
else {
|
||||
Global &global = get_global();
|
||||
/* Increase global memory counts. */
|
||||
global.blocks_num_outside_locals.fetch_add(1, std::memory_order_relaxed);
|
||||
global.mem_in_use_outside_locals.fetch_add(int64_t(size), std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
void memory_usage_block_free(const size_t size)
|
||||
{
|
||||
if (LIKELY(use_local_counters)) {
|
||||
/* Decrease local memory counts. See comment in #memory_usage_block_alloc for details regarding
|
||||
* thread synchronization. */
|
||||
Local &local = get_local_data();
|
||||
local.mem_in_use.fetch_sub(int64_t(size), std::memory_order_relaxed);
|
||||
local.blocks_num.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
else {
|
||||
Global &global = get_global();
|
||||
/* Decrease global memory counts. */
|
||||
global.blocks_num_outside_locals.fetch_sub(1, std::memory_order_relaxed);
|
||||
global.mem_in_use_outside_locals.fetch_sub(int64_t(size), std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
size_t memory_usage_block_num()
|
||||
{
|
||||
Global &global = get_global();
|
||||
std::lock_guard lock{global.locals_mutex};
|
||||
|
||||
/* Count the number of active blocks. */
|
||||
int64_t blocks_num = global.blocks_num_outside_locals;
|
||||
for (const Local *local : global.locals) {
|
||||
blocks_num += local->blocks_num;
|
||||
}
|
||||
return size_t(blocks_num);
|
||||
}
|
||||
|
||||
size_t memory_usage_current()
|
||||
{
|
||||
Global &global = get_global();
|
||||
std::lock_guard lock{global.locals_mutex};
|
||||
|
||||
/* Count the memory that's currently in use. */
|
||||
int64_t mem_in_use = global.mem_in_use_outside_locals;
|
||||
for (const Local *local : global.locals) {
|
||||
mem_in_use += local->mem_in_use;
|
||||
}
|
||||
return size_t(mem_in_use);
|
||||
}
|
||||
|
||||
size_t memory_usage_peak()
|
||||
{
|
||||
update_global_peak();
|
||||
Global &global = get_global();
|
||||
return global.peak;
|
||||
}
|
||||
|
||||
void memory_usage_peak_reset()
|
||||
{
|
||||
Global &global = get_global();
|
||||
global.peak = memory_usage_current();
|
||||
}
|
||||
Reference in New Issue
Block a user