Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
global_alloc.cpp
Go to the documentation of this file.
1#include <helios/memory/details/profile.hpp>
2
3#ifdef HELIOS_MEMORY_USE_MIMALLOC
4#include <mimalloc.h>
5#endif
6
7#if defined(_MSC_VER) && defined(_DEBUG)
8#include <crtdbg.h>
9#endif
10
11#include <cstddef>
12#include <cstdlib>
13#include <new>
14
15// mimalloc global hooks bypass MSVC ASan-instrumented allocation. Route through
16// the CRT/debug heap when AddressSanitizer is enabled (MSVC /fsanitize=address
17// or Clang -fsanitize=address).
18#if defined(HELIOS_MEMORY_USE_MIMALLOC) && !defined(__SANITIZE_ADDRESS__)
19#define HELIOS_GLOBAL_ALLOC_USE_MIMALLOC_BACKEND
20#endif
21
22// These replaceable operator new/delete overloads form a single atomic set:
23// once any of them is provided, every allocation must be paired with a
24// deallocation from this same file. LTO and linker dead-stripping can each
25// independently decide that an individual overload has no directly-visible
26// caller (calls to it may only be emitted implicitly, e.g. by scalar
27// deleting destructors) and discard it, while keeping others - silently
28// falling back to the default library implementation for the discarded
29// overloads. Mixing this file's operator new with the default operator
30// delete (or vice versa) corrupts the heap, so every overload below is
31// force-kept regardless of apparent reachability.
32#if defined(__has_attribute)
33#if __has_attribute(retain)
34#define HELIOS_GLOBAL_ALLOC_KEEP __attribute__((used, retain))
35#else
36#define HELIOS_GLOBAL_ALLOC_KEEP __attribute__((used))
37#endif
38#elif defined(__GNUC__) || defined(__clang__)
39#define HELIOS_GLOBAL_ALLOC_KEEP __attribute__((used))
40#else
41#define HELIOS_GLOBAL_ALLOC_KEEP
42#endif
43
44namespace {
45
46#if defined(HELIOS_MEMORY_ENABLE_PROFILE) && \
47 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
48thread_local bool g_in_global_alloc_hook = false;
49#endif
50
51[[nodiscard]] void* RawAlloc(size_t size) noexcept {
52#ifdef HELIOS_GLOBAL_ALLOC_USE_MIMALLOC_BACKEND
53 return mi_malloc(size);
54#elif defined(_MSC_VER) && defined(_DEBUG)
55 // MSVC debug builds route new/delete through the debug heap. Using malloc
56 // here mixes allocators and can deadlock during static initialization.
57 return _malloc_dbg(size, _NORMAL_BLOCK, nullptr, 0);
58#else
59 return std::malloc(size);
60#endif
61}
62
63[[nodiscard]] void* RawAlignedAlloc(size_t size,
64 std::align_val_t alignment) noexcept {
65#ifdef HELIOS_GLOBAL_ALLOC_USE_MIMALLOC_BACKEND
66 return mi_malloc_aligned(size, static_cast<size_t>(alignment));
67#elif defined(_MSC_VER) && defined(_DEBUG)
68 return _aligned_malloc_dbg(size, static_cast<size_t>(alignment), nullptr, 0);
69#elif defined(_MSC_VER)
70 return _aligned_malloc(size, static_cast<size_t>(alignment));
71#else
72 const auto align = static_cast<size_t>(alignment);
73 const size_t aligned = (size + align - 1) & ~(align - 1);
74 return std::aligned_alloc(align, aligned);
75#endif
76}
77
78void RawFree(void* ptr) noexcept {
79#ifdef HELIOS_GLOBAL_ALLOC_USE_MIMALLOC_BACKEND
80 mi_free(ptr);
81#elif defined(_MSC_VER) && defined(_DEBUG)
82 _free_dbg(ptr, _NORMAL_BLOCK);
83#else
84 std::free(ptr);
85#endif
86}
87
88void RawAlignedFree(void* ptr) noexcept {
89#ifdef HELIOS_GLOBAL_ALLOC_USE_MIMALLOC_BACKEND
90 mi_free(ptr);
91#elif defined(_MSC_VER) && defined(_DEBUG)
92 _aligned_free_dbg(ptr);
93#elif defined(_MSC_VER)
94 _aligned_free(ptr);
95#else
96 std::free(ptr);
97#endif
98}
99
100void ProfileAlloc([[maybe_unused]] const void* ptr,
101 [[maybe_unused]] size_t size) noexcept {
102#if defined(HELIOS_MEMORY_ENABLE_PROFILE) && \
103 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
104 if (ptr == nullptr || g_in_global_alloc_hook) {
105 return;
106 }
107
108 g_in_global_alloc_hook = true;
109 HELIOS_MEMORY_GLOBAL_ALLOC_PROFILE_ALLOC(ptr, size);
110 g_in_global_alloc_hook = false;
111#endif
112}
113
114void ProfileFree([[maybe_unused]] const void* ptr) noexcept {
115#if defined(HELIOS_MEMORY_ENABLE_PROFILE) && \
116 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
117 if (ptr == nullptr || g_in_global_alloc_hook) {
118 return;
119 }
120
121 g_in_global_alloc_hook = true;
122 HELIOS_MEMORY_GLOBAL_ALLOC_PROFILE_FREE(ptr);
123 g_in_global_alloc_hook = false;
124#endif
125}
126
127} // namespace
128
129HELIOS_GLOBAL_ALLOC_KEEP void* operator new(size_t size) {
130 void* const ptr = RawAlloc(size);
131 if (ptr == nullptr) [[unlikely]] {
132 throw std::bad_alloc{};
133 }
134
135 ProfileAlloc(ptr, size);
136 return ptr;
137}
138
139HELIOS_GLOBAL_ALLOC_KEEP void* operator new[](size_t size) {
140 return ::operator new(size);
141}
142
143HELIOS_GLOBAL_ALLOC_KEEP void* operator new(size_t size,
144 const std::nothrow_t&) noexcept {
145 void* const ptr = RawAlloc(size);
146 if (ptr != nullptr) {
147 ProfileAlloc(ptr, size);
148 }
149 return ptr;
150}
151
152HELIOS_GLOBAL_ALLOC_KEEP void* operator new[](size_t size,
153 const std::nothrow_t&) noexcept {
154 return ::operator new(size, std::nothrow);
155}
156
157HELIOS_GLOBAL_ALLOC_KEEP void operator delete(void* ptr) noexcept {
158 if (ptr == nullptr) [[unlikely]] {
159 return;
160 }
161
162 ProfileFree(ptr);
163 RawFree(ptr);
164}
165
166HELIOS_GLOBAL_ALLOC_KEEP void operator delete[](void* ptr) noexcept {
167 ::operator delete(ptr);
168}
169
170HELIOS_GLOBAL_ALLOC_KEEP void operator delete(void* ptr, size_t) noexcept {
171 ::operator delete(ptr);
172}
173
174HELIOS_GLOBAL_ALLOC_KEEP void operator delete[](void* ptr, size_t) noexcept {
175 ::operator delete(ptr);
176}
177
178HELIOS_GLOBAL_ALLOC_KEEP void operator delete(void* ptr,
179 const std::nothrow_t&) noexcept {
180 ::operator delete(ptr);
181}
182
183HELIOS_GLOBAL_ALLOC_KEEP void operator delete[](
184 void* ptr, const std::nothrow_t& /*nothrow*/) noexcept {
185 ::operator delete(ptr);
186}
187
188HELIOS_GLOBAL_ALLOC_KEEP void* operator new(size_t size,
189 std::align_val_t alignment) {
190 void* const ptr = RawAlignedAlloc(size, alignment);
191 if (ptr == nullptr) [[unlikely]] {
192 throw std::bad_alloc{};
193 }
194
195 ProfileAlloc(ptr, size);
196 return ptr;
197}
198
199HELIOS_GLOBAL_ALLOC_KEEP void* operator new[](size_t size,
200 std::align_val_t alignment) {
201 return ::operator new(size, alignment);
202}
203
204HELIOS_GLOBAL_ALLOC_KEEP void* operator new(
205 size_t size, std::align_val_t alignment,
206 const std::nothrow_t& /*nothrow*/) noexcept {
207 void* const ptr = RawAlignedAlloc(size, alignment);
208 if (ptr != nullptr) {
209 ProfileAlloc(ptr, size);
210 }
211 return ptr;
212}
213
214HELIOS_GLOBAL_ALLOC_KEEP void* operator new[](
215 size_t size, std::align_val_t alignment,
216 const std::nothrow_t& /*nothrow*/) noexcept {
217 return ::operator new(size, alignment, std::nothrow);
218}
219
220HELIOS_GLOBAL_ALLOC_KEEP void operator delete(
221 void* ptr, std::align_val_t /*alignment*/) noexcept {
222 if (ptr == nullptr) [[unlikely]] {
223 return;
224 }
225
226 ProfileFree(ptr);
227 RawAlignedFree(ptr);
228}
229
230HELIOS_GLOBAL_ALLOC_KEEP void operator delete[](
231 void* ptr, std::align_val_t alignment) noexcept {
232 ::operator delete(ptr, alignment);
233}
234
235HELIOS_GLOBAL_ALLOC_KEEP void operator delete(
236 void* ptr, size_t /*size*/, std::align_val_t alignment) noexcept {
237 ::operator delete(ptr, alignment);
238}
239
240HELIOS_GLOBAL_ALLOC_KEEP void operator delete[](
241 void* ptr, size_t size, std::align_val_t alignment) noexcept {
242 ::operator delete(ptr, size, alignment);
243}
#define HELIOS_GLOBAL_ALLOC_KEEP
void ProfileAlloc(const void *ptr, size_t size) noexcept
void * RawAlloc(size_t size) noexcept
void ProfileFree(const void *ptr) noexcept
void * RawAlignedAlloc(size_t size, std::align_val_t alignment) noexcept
void RawAlignedFree(void *ptr) noexcept