Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View 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));
}

View 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
}

File diff suppressed because it is too large Load Diff

View 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

View 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

View File

@@ -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

View File

@@ -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 */

View 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();
}