Refactor: Replace MEM_cnew with a type-aware template version of MEM_callocN.

The general idea is to keep the 'old', C-style MEM_callocN signature, and slowly
replace most of its usages with the new, C++-style type-safer template version.

* `MEM_cnew<T>` allocation version is renamed to `MEM_callocN<T>`.
* `MEM_cnew_array<T>` allocation version is renamed to `MEM_calloc_arrayN<T>`.
* `MEM_cnew<T>` duplicate version is renamed to `MEM_dupallocN<T>`.

Similar templates type-safe version of `MEM_mallocN` will be added soon
as well.

Following discussions in !134452.

NOTE: For now static type checking in `MEM_callocN` and related are slightly
different for Windows MSVC. This compiler seems to consider structs using the
`DNA_DEFINE_CXX_METHODS` macro as non-trivial (likely because their default
copy constructors are deleted). So using checks on trivially
constructible/destructible instead on this compiler/system.

Pull Request: https://projects.blender.org/blender/blender/pulls/134771
This commit is contained in:
Bastien Montagne
2025-03-05 16:35:09 +01:00
committed by Bastien Montagne
parent 65889672e2
commit dd168a35c5
423 changed files with 1122 additions and 1024 deletions

View File

@@ -52,7 +52,7 @@ extern "C" {
extern size_t (*MEM_allocN_len)(const void *vmemh) ATTR_WARN_UNUSED_RESULT;
/**
* Release memory previously allocated by the C-style and #MEM_cnew functions of this module.
* Release memory previously allocated by the C-style functions of this module.
*
* It is illegal to call this function with data allocated by #MEM_new.
*/
@@ -69,7 +69,7 @@ extern short (*MEM_testN)(void *vmemh);
* Duplicates a block of memory, and returns a pointer to the
* newly allocated block.
* NULL-safe; will return NULL when receiving a NULL pointer. */
extern void *(*MEM_dupallocN)(const void *vmemh) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT;
void *MEM_dupallocN(const void *vmemh) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT;
/**
* Reallocates a block of memory, and returns pointer to the newly
@@ -97,17 +97,17 @@ extern void *(*MEM_recallocN_id)(void *vmemh,
* memory is cleared. The name must be static, because only a
* pointer to it is stored!
*/
extern void *(*MEM_callocN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT
ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2);
void *MEM_callocN(size_t len, const char *str) ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1)
ATTR_NONNULL(2);
/**
* Allocate a block of memory of size (len * size), with tag name
* str, aborting in case of integer overflows to prevent vulnerabilities.
* The memory is cleared. The name must be static, because only a
* pointer to it is stored! */
extern void *(*MEM_calloc_arrayN)(size_t len,
size_t size,
const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT
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);
/**
@@ -325,8 +325,8 @@ inline T *MEM_new(const char *allocation_name, Args &&...args)
*
* As with the `delete` C++ operator, passing in `nullptr` is allowed and does nothing.
*
* It is illegal to call this function with data allocated by #MEM_cnew or the C-style allocation
* functions of this module.
* It is illegal to call this function with data allocated by the C-style allocation functions of
* this module.
*/
template<typename T> inline void MEM_delete(const T *ptr)
{
@@ -356,23 +356,49 @@ template<typename T> inline void MEM_delete(const T *ptr)
/**
* Allocate zero-initialized memory for an object of type #T. The constructor of #T is not called,
* therefore this should only be used with trivial types (like all C types).
* therefore this must only be used with trivial types (like all C types).
*
* When allocating an enforced specific amount of bytes, the C version of this function should be
* used instead. While this should be avoided in C++ code, it is still required in some cases, e.g.
* for ID allocation based on #IDTypeInfo::struct_size.
*
* #MEM_freeN must be used to free a pointer returned by this call. Calling #MEM_delete on it is
* illegal.
*/
template<typename T> inline T *MEM_cnew(const char *allocation_name)
template<typename T> inline T *MEM_callocN(const char *allocation_name)
{
# ifdef _MSC_VER
/* MSVC considers C-style types using the DNA_DEFINE_CXX_METHODS as non-trivial (more
* specifically, non-trivially copyable, likely because the default copy constructors are
* deleted). GCC and clang (both on linux, OSX, and clang-cl on Windows on Arm) do not.
*
* So for now, use a more restricted check on MSVC, should still catch most of actual invalid
* cases. */
static_assert(std::is_trivially_constructible_v<T>,
"For non-trivial types, MEM_new must be used.");
# else
static_assert(std::is_trivial_v<T>, "For non-trivial types, MEM_new must be used.");
# endif
return static_cast<T *>(MEM_calloc_arrayN_aligned(1, sizeof(T), alignof(T), allocation_name));
}
/**
* Same as MEM_cnew but for arrays, better alternative to #MEM_calloc_arrayN.
* Type-safe version of #MEM_calloc_arrayN/#MEM_calloc_array_alignedN.
*/
template<typename T> inline T *MEM_cnew_array(const size_t length, const char *allocation_name)
template<typename T> inline T *MEM_calloc_arrayN(const size_t length, const char *allocation_name)
{
# ifdef _MSC_VER
/* MSVC considers C-style types using the DNA_DEFINE_CXX_METHODS as non-trivial (more
* specifically, non-trivially copyable, likely because the default copy constructors are
* deleted). GCC and clang (both on linux, OSX, and clang-cl on Windows on Arm) do not.
*
* So for now, use a more restricted check on MSVC, should still catch most of actual invalid
* cases. */
static_assert(std::is_trivially_constructible_v<T>,
"For non-trivial types, MEM_new must be used.");
# else
static_assert(std::is_trivial_v<T>, "For non-trivial types, MEM_new must be used.");
# endif
return static_cast<T *>(
MEM_calloc_arrayN_aligned(length, sizeof(T), alignof(T), allocation_name));
}
@@ -385,11 +411,23 @@ template<typename T> inline T *MEM_cnew_array(const size_t length, const char *a
* deprecated fields: some compilers will generate access deprecated field warnings in implicitly
* defined copy constructors.
*
* This is a better alternative to #MEM_dupallocN.
* This is a better alternative to the C-style implementation of #MEM_dupallocN, unless the source
* is an array or of a non-fully-defined type.
*/
template<typename T> inline T *MEM_cnew(const char *allocation_name, const T &other)
template<typename T> inline T *MEM_dupallocN(const char *allocation_name, const T &other)
{
# ifdef _MSC_VER
/* MSVC considers C-style types using the DNA_DEFINE_CXX_METHODS as non-trivial (more
* specifically, non-trivially copyable, likely because the default copy constructors are
* deleted). GCC and clang (both on linux, OSX, and clang-cl on Windows on Arm) do not.
*
* So for now, use a more restricted check on MSVC, should still catch most of actual invalid
* cases. */
static_assert(std::is_trivially_constructible_v<T>,
"For non-trivial types, MEM_new must be used.");
# else
static_assert(std::is_trivial_v<T>, "For non-trivial types, MEM_new must be used.");
# endif
T *new_object = static_cast<T *>(MEM_mallocN_aligned(sizeof(T), alignof(T), allocation_name));
if (new_object) {
memcpy(new_object, &other, sizeof(T));

View File

@@ -36,11 +36,13 @@ const char *malloc_conf =
size_t (*MEM_allocN_len)(const void *vmemh) = MEM_lockfree_allocN_len;
void (*mem_guarded::internal::mem_freeN_ex)(void *vmemh,
AllocationType allocation_type) = MEM_lockfree_freeN;
void *(*MEM_dupallocN)(const void *vmemh) = MEM_lockfree_dupallocN;
void *(*mem_guarded::internal::mem_dupallocN)(const void *vmemh) = MEM_lockfree_dupallocN;
void *(*MEM_reallocN_id)(void *vmemh, size_t len, const char *str) = MEM_lockfree_reallocN_id;
void *(*MEM_recallocN_id)(void *vmemh, size_t len, const char *str) = MEM_lockfree_recallocN_id;
void *(*MEM_callocN)(size_t len, const char *str) = MEM_lockfree_callocN;
void *(*MEM_calloc_arrayN)(size_t len, size_t size, const char *str) = MEM_lockfree_calloc_arrayN;
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_mallocN)(size_t len, const char *str) = MEM_lockfree_mallocN;
void *(*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,
@@ -111,11 +113,26 @@ void MEM_freeN(void *vmemh)
mem_freeN_ex(vmemh, AllocationType::ALLOC_FREE);
}
void *MEM_callocN(size_t len, const char *str)
{
return mem_callocN(len, str);
}
void *MEM_calloc_arrayN(size_t len, size_t size, const char *str)
{
return mem_calloc_arrayN(len, size, str);
}
void *MEM_mallocN_aligned(size_t len, size_t alignment, const char *str)
{
return mem_mallocN_aligned_ex(len, alignment, str, AllocationType::ALLOC_FREE);
}
void *MEM_dupallocN(const void *vmemh)
{
return mem_dupallocN(vmemh);
}
/**
* Perform assert checks on allocator type change.
*
@@ -142,11 +159,11 @@ void MEM_use_lockfree_allocator()
MEM_allocN_len = MEM_lockfree_allocN_len;
mem_freeN_ex = MEM_lockfree_freeN;
MEM_dupallocN = MEM_lockfree_dupallocN;
mem_dupallocN = MEM_lockfree_dupallocN;
MEM_reallocN_id = MEM_lockfree_reallocN_id;
MEM_recallocN_id = MEM_lockfree_recallocN_id;
MEM_callocN = MEM_lockfree_callocN;
MEM_calloc_arrayN = MEM_lockfree_calloc_arrayN;
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;
@@ -178,11 +195,11 @@ void MEM_use_guarded_allocator()
MEM_allocN_len = MEM_guarded_allocN_len;
mem_freeN_ex = MEM_guarded_freeN;
MEM_dupallocN = MEM_guarded_dupallocN;
mem_dupallocN = MEM_guarded_dupallocN;
MEM_reallocN_id = MEM_guarded_reallocN_id;
MEM_recallocN_id = MEM_guarded_recallocN_id;
MEM_callocN = MEM_guarded_callocN;
MEM_calloc_arrayN = MEM_guarded_calloc_arrayN;
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;

View File

@@ -720,7 +720,7 @@ static void *mem_guarded_malloc_arrayN_aligned(const size_t len,
return nullptr;
}
if (alignment <= MEM_MIN_CPP_ALIGNMENT) {
return MEM_callocN(r_bytes_num, str);
return mem_callocN(r_bytes_num, str);
}
return MEM_mallocN_aligned(r_bytes_num, alignment, str);
}

View File

@@ -20,6 +20,22 @@ enum class AllocationType {
/** Internal implementation of #MEM_freeN, exposed because #MEM_delete needs access to it. */
extern void (*mem_freeN_ex)(void *vmemh, AllocationType allocation_type);
/**
* Internal implementation of #MEM_callocN, exposed because public #MEM_callocN 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_calloc_arrayN, exposed because public #MEM_calloc_arrayN 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_mallocN_aligned, exposed because #MEM_new needs access to it.
*/
extern void *(*mem_mallocN_aligned_ex)(size_t len,
@@ -27,6 +43,12 @@ extern void *(*mem_mallocN_aligned_ex)(size_t len,
const char *str,
AllocationType allocation_type);
/**
* Internal implementation of #MEM_dupallocN, exposed because public #MEM_dupallocN 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

View File

@@ -474,7 +474,7 @@ static void *mem_lockfree_malloc_arrayN_aligned(const size_t len,
return nullptr;
}
if (alignment <= MEM_MIN_CPP_ALIGNMENT) {
return MEM_mallocN(r_bytes_num, str);
return mem_callocN(r_bytes_num, str);
}
void *ptr = MEM_mallocN_aligned(r_bytes_num, alignment, str);
return ptr;