From 307d0de26ef4a60a30f188e912ab37ecd6bc2c5a Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 11 Aug 2025 14:56:11 +0200 Subject: [PATCH] Allocator: Add `MEM_new_for_free` to allow construction of almost-trivial types. The data constructed by this call remains in the 'C-alloc' realm, i.e. it can be `MEM_dupallocN`'ed, and `MEM_freeN`'ed. This is intended as a temporary API only, to facilitate transition to full C++ handling of data in Blender. It's primary target is to allow pseudo-POD types to use default values for their members. See e.g. !134531. Unlike !143827 and !138829, it does not change the current rule (`new` must be paired with `delete`, and `alloc` must be paired with `free`). Instead, it defines an explicit and temporary API to allow a very limited form of construction to happen on C-allocated data, provided that the type is default-constructible, and remains trivial after construction. ### Notes * The new API is purposely as restrictive as possible, trying to only allow the current known needs (init with default member values). This can easily be extended if needed. * To try to stay as close as malloc/calloc behavior as possible, and avoid the 'zero-initialization' gotcha, it does not use value-initialization, but instead default-initialization on zero- initialized memory. _Ideally it would even not allow any user-defined default constructor, but this does not seem simple to detect._ Pull Request: https://projects.blender.org/blender/blender/pulls/144141 --- intern/guardedalloc/MEM_guardedalloc.h | 63 ++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/intern/guardedalloc/MEM_guardedalloc.h b/intern/guardedalloc/MEM_guardedalloc.h index 0f7fce5a3e0..0aa7c40895f 100644 --- a/intern/guardedalloc/MEM_guardedalloc.h +++ b/intern/guardedalloc/MEM_guardedalloc.h @@ -340,6 +340,13 @@ void MEM_use_guarded_allocator(void); * * \{ */ +namespace mem_guarded::internal { +/* Note that we intentionally don't care about a non-trivial default constructor here. */ +template +constexpr bool is_trivial_after_construction = std::is_trivially_copyable_v && + std::is_trivially_destructible_v; +} // namespace mem_guarded::internal + /** * Allocate new memory for an object of type #T, and construct it. * #MEM_delete must be used to delete the object. Calling #MEM_freeN on it is illegal. @@ -362,6 +369,46 @@ inline T *MEM_new(const char *allocation_name, Args &&...args) return new (buffer) T(std::forward(args)...); } +/** + * Allocate new memory for an object of type #T, and construct it with its default constructor. + * Both #MEM_delete and #MEM_freeN can be used to delete the object. + * + * Designed to be used with 'pseudo-POD' types, that are trivially copyable and destructible, but + * not trivially constructible. Once constructed, this data can be managed as a C-type one (using + * `MEM_dupallocN`, `MEM_freeN`, safely assigned to a void pointer and freed as such, etc.). + * + * The typical use-cases are C-like structs containing only trivial data, that define default + * values for (some of) their members. + * + * \note This function uses 'default initialization' on zero-initialized memory, _not_ 'value + * initialization'. This means that even if a user-defined default constructor is provided, + * non-explicitely initialized data will be zero-initialized. For POD types (e.g. pure C-style + * structs), its behavior is functionnally identical to using `MEM_callocN()`. + * + * \warning This function is intended as a temporary work-around during the process of converting + * Blender data management from C-style (alloc/free) to C++-style (new/delete). It will be removed + * once not needed anymore (i.e. mainly when there is no more need to dupalloc and free untyped + * data stored in void pointers). + */ +template inline T *MEM_new_for_free(const char *allocation_name) +{ + static_assert(mem_guarded::internal::is_trivial_after_construction, + "MEM_new_for_free can only construct types that are trivially copyable and " + "destructible, use MEM_new instead."); + void *buffer; + /* 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 (alignof(T) <= MEM_MIN_CPP_ALIGNMENT) { + buffer = MEM_callocN(sizeof(T), allocation_name); + } + else { + buffer = mem_guarded::internal::mem_mallocN_aligned_ex( + sizeof(T), alignof(T), allocation_name, mem_guarded::internal::AllocationType::ALLOC_FREE); + memset(buffer, 0, sizeof(T)); + } + return new (buffer) T; +} + /** * Destruct and deallocate an object previously allocated and constructed with #MEM_new, or some * type-overloaded `new` operators using MEM_guardedalloc as backend. @@ -564,10 +611,13 @@ template inline T *MEM_malloc_arrayN(const size_t length, const char template inline T *MEM_dupallocN(const char *allocation_name, const T &other) { # ifdef _MSC_VER - static_assert(std::is_trivially_constructible_v, - "For non-trivial types, MEM_new must be used."); + static_assert(std::is_trivially_assignable_v && std::is_trivially_destructible_v, + "MEM_dupallocN can only duplicate types that are trivially copyable and " + "destructible, use MEM_new instead."); # else - static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); + static_assert(mem_guarded::internal::is_trivial_after_construction, + "MEM_dupallocN can only duplicate types that are trivially copyable and " + "destructible, use MEM_new instead."); # endif T *new_object = static_cast(MEM_mallocN_aligned(sizeof(T), alignof(T), allocation_name)); if (new_object) { @@ -580,9 +630,12 @@ template inline void MEM_freeN(T *ptr) { # ifdef _MSC_VER static_assert(std::is_trivially_destructible_v, - "For non-trivial types, MEM_delete must be used."); + "MEM_freeN can only free types that are trivially copyable and destructible, use " + "MEM_delete instead."); # else - static_assert(std::is_trivial_v, "For non-trivial types, MEM_delete must be used."); + static_assert(mem_guarded::internal::is_trivial_after_construction, + "MEM_freeN can only free types that are trivially copyable and destructible, use " + "MEM_delete instead."); # endif mem_guarded::internal::mem_freeN_ex(const_cast(static_cast(ptr)), mem_guarded::internal::AllocationType::ALLOC_FREE);