1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#pragma once
4#undef min
5#undef max
6
7#include <new>
8#include <limits>
9#include <cstdint>
10#include <utility>
11
12#if BS_PLATFORM == BS_PLATFORM_LINUX
13# include <malloc.h>
14#endif
15
16namespace bs
17{
18 class MemoryAllocatorBase;
19
20 /** @addtogroup Internal-Utility
21 * @{
22 */
23
24 /** @addtogroup Memory-Internal
25 * @{
26 */
27
28#if BS_PLATFORM == BS_PLATFORM_WIN32
29 inline void* platformAlignedAlloc16(size_t size)
30 {
31 return _aligned_malloc(size, 16);
32 }
33
34 inline void platformAlignedFree16(void* ptr)
35 {
36 _aligned_free(ptr);
37 }
38
39 inline void* platformAlignedAlloc(size_t size, size_t alignment)
40 {
41 return _aligned_malloc(size, alignment);
42 }
43
44 inline void platformAlignedFree(void* ptr)
45 {
46 _aligned_free(ptr);
47 }
48#elif BS_PLATFORM == BS_PLATFORM_LINUX || BS_PLATFORM == BS_PLATFORM_ANDROID
49 inline void* platformAlignedAlloc16(size_t size)
50 {
51 return ::memalign(16, size);
52 }
53
54 inline void platformAlignedFree16(void* ptr)
55 {
56 ::free(ptr);
57 }
58
59 inline void* platformAlignedAlloc(size_t size, size_t alignment)
60 {
61 return ::memalign(alignment, size);
62 }
63
64 inline void platformAlignedFree(void* ptr)
65 {
66 ::free(ptr);
67 }
68#else // 16 byte aligment by default
69 inline void* platformAlignedAlloc16(size_t size)
70 {
71 return ::malloc(size);
72 }
73
74 inline void platformAlignedFree16(void* ptr)
75 {
76 ::free(ptr);
77 }
78
79 inline void* platformAlignedAlloc(size_t size, size_t alignment)
80 {
81 void* data = ::malloc(size + (alignment - 1) + sizeof(void*));
82 if (data == nullptr)
83 return nullptr;
84
85 char* alignedData = ((char*)data) + sizeof(void*);
86 alignedData += (alignment - ((uintptr_t)alignedData) & (alignment - 1)) & (alignment - 1);
87
88 ((void**)alignedData)[-1] = data;
89 return alignedData;
90 }
91
92 inline void platformAlignedFree(void* ptr)
93 {
94 // TODO: Document how this works.
95 ::free(((void**)ptr)[-1]);
96 }
97#endif
98
99 /**
100 * Thread safe class used for storing total number of memory allocations and deallocations, primarily for statistic
101 * purposes.
102 */
103 class MemoryCounter
104 {
105 public:
106 static BS_UTILITY_EXPORT uint64_t getNumAllocs()
107 {
108 return Allocs;
109 }
110
111 static BS_UTILITY_EXPORT uint64_t getNumFrees()
112 {
113 return Frees;
114 }
115
116 private:
117 friend class MemoryAllocatorBase;
118
119 // Threadlocal data can't be exported, so some magic to make it accessible from MemoryAllocator
120 static BS_UTILITY_EXPORT void incAllocCount() { ++Allocs; }
121 static BS_UTILITY_EXPORT void incFreeCount() { ++Frees; }
122
123 static BS_THREADLOCAL uint64_t Allocs;
124 static BS_THREADLOCAL uint64_t Frees;
125 };
126
127 /** Base class all memory allocators need to inherit. Provides allocation and free counting. */
128 class MemoryAllocatorBase
129 {
130 protected:
131 static void incAllocCount() { MemoryCounter::incAllocCount(); }
132 static void incFreeCount() { MemoryCounter::incFreeCount(); }
133 };
134
135 /**
136 * Memory allocator providing a generic implementation. Specialize for specific categories as needed.
137 *
138 * @note For example you might implement a pool allocator for specific types in order
139 * to reduce allocation overhead. By default standard malloc/free are used.
140 */
141 template<class T>
142 class MemoryAllocator : public MemoryAllocatorBase
143 {
144 public:
145 /** Allocates @p bytes bytes. */
146 static void* allocate(size_t bytes)
147 {
148#if BS_PROFILING_ENABLED
149 incAllocCount();
150#endif
151
152 return malloc(bytes);
153 }
154
155 /**
156 * Allocates @p bytes and aligns them to the specified boundary (in bytes). If the aligment is less or equal to
157 * 16 it is more efficient to use the allocateAligned16() alternative of this method. Alignment must be power of two.
158 */
159 static void* allocateAligned(size_t bytes, size_t alignment)
160 {
161#if BS_PROFILING_ENABLED
162 incAllocCount();
163#endif
164
165 return platformAlignedAlloc(bytes, alignment);
166 }
167
168 /** Allocates @p bytes and aligns them to a 16 byte boundary. */
169 static void* allocateAligned16(size_t bytes)
170 {
171#if BS_PROFILING_ENABLED
172 incAllocCount();
173#endif
174
175 return platformAlignedAlloc16(bytes);
176 }
177
178 /** Frees the memory at the specified location. */
179 static void free(void* ptr)
180 {
181#if BS_PROFILING_ENABLED
182 incFreeCount();
183#endif
184
185 ::free(ptr);
186 }
187
188 /** Frees memory allocated with allocateAligned() */
189 static void freeAligned(void* ptr)
190 {
191#if BS_PROFILING_ENABLED
192 incFreeCount();
193#endif
194
195 platformAlignedFree(ptr);
196 }
197
198 /** Frees memory allocated with allocateAligned16() */
199 static void freeAligned16(void* ptr)
200 {
201#if BS_PROFILING_ENABLED
202 incFreeCount();
203#endif
204
205 platformAlignedFree16(ptr);
206 }
207 };
208
209 /**
210 * General allocator provided by the OS. Use for persistent long term allocations, and allocations that don't
211 * happen often.
212 */
213 class GenAlloc
214 { };
215
216 /** @} */
217 /** @} */
218
219 /** @addtogroup Memory
220 * @{
221 */
222
223 /** Allocates the specified number of bytes. */
224 template<class Alloc>
225 void* bs_alloc(size_t count)
226 {
227 return MemoryAllocator<Alloc>::allocate(count);
228 }
229
230 /** Allocates enough bytes to hold the specified type, but doesn't construct it. */
231 template<class T, class Alloc>
232 T* bs_alloc()
233 {
234 return (T*)MemoryAllocator<Alloc>::allocate(sizeof(T));
235 }
236
237 /** Creates and constructs an array of @p count elements. */
238 template<class T, class Alloc>
239 T* bs_newN(size_t count)
240 {
241 T* ptr = (T*)MemoryAllocator<Alloc>::allocate(sizeof(T) * count);
242
243 for(size_t i = 0; i < count; ++i)
244 new (&ptr[i]) T;
245
246 return ptr;
247 }
248
249 /** Create a new object with the specified allocator and the specified parameters. */
250 template<class Type, class Alloc, class... Args>
251 Type* bs_new(Args &&...args)
252 {
253 return new (bs_alloc<Type, Alloc>()) Type(std::forward<Args>(args)...);
254 }
255
256 /** Frees all the bytes allocated at the specified location. */
257 template<class Alloc>
258 void bs_free(void* ptr)
259 {
260 MemoryAllocator<Alloc>::free(ptr);
261 }
262
263 /** Destructs and frees the specified object. */
264 template<class T, class Alloc = GenAlloc>
265 void bs_delete(T* ptr)
266 {
267 (ptr)->~T();
268
269 MemoryAllocator<Alloc>::free(ptr);
270 }
271
272 /** Callable struct that acts as a proxy for bs_delete */
273 template<class T, class Alloc = GenAlloc>
274 struct Deleter
275 {
276 constexpr Deleter() noexcept = default;
277
278 /** Constructor enabling deleter conversion and therefore polymorphism with smart points (if they use the same allocator). */
279 template <class T2, std::enable_if_t<std::is_convertible<T2*, T*>::value, int> = 0>
280 constexpr Deleter(const Deleter<T2, Alloc>& other) noexcept { }
281
282 void operator()(T* ptr) const
283 {
284 bs_delete<T, Alloc>(ptr);
285 }
286 };
287
288 /** Destructs and frees the specified array of objects. */
289 template<class T, class Alloc = GenAlloc>
290 void bs_deleteN(T* ptr, size_t count)
291 {
292 for(size_t i = 0; i < count; ++i)
293 ptr[i].~T();
294
295 MemoryAllocator<Alloc>::free(ptr);
296 }
297
298 /*****************************************************************************/
299 /* Default versions of all alloc/free/new/delete methods which call GenAlloc */
300 /*****************************************************************************/
301
302 /** Allocates the specified number of bytes. */
303 inline void* bs_alloc(size_t count)
304 {
305 return MemoryAllocator<GenAlloc>::allocate(count);
306 }
307
308 /** Allocates enough bytes to hold the specified type, but doesn't construct it. */
309 template<class T>
310 T* bs_alloc()
311 {
312 return (T*)MemoryAllocator<GenAlloc>::allocate(sizeof(T));
313 }
314
315 /**
316 * Allocates the specified number of bytes aligned to the provided boundary. Boundary is in bytes and must be a power
317 * of two.
318 */
319 inline void* bs_alloc_aligned(size_t count, size_t align)
320 {
321 return MemoryAllocator<GenAlloc>::allocateAligned(count, align);
322 }
323
324
325 /** Allocates the specified number of bytes aligned to a 16 bytes boundary. */
326 inline void* bs_alloc_aligned16(size_t count)
327 {
328 return MemoryAllocator<GenAlloc>::allocateAligned16(count);
329 }
330
331 /** Allocates enough bytes to hold an array of @p count elements the specified type, but doesn't construct them. */
332 template<class T>
333 T* bs_allocN(size_t count)
334 {
335 return (T*)MemoryAllocator<GenAlloc>::allocate(count * sizeof(T));
336 }
337
338 /** Creates and constructs an array of @p count elements. */
339 template<class T>
340 T* bs_newN(size_t count)
341 {
342 T* ptr = (T*)MemoryAllocator<GenAlloc>::allocate(count * sizeof(T));
343
344 for(size_t i = 0; i < count; ++i)
345 new (&ptr[i]) T;
346
347 return ptr;
348 }
349
350 /** Create a new object with the specified allocator and the specified parameters. */
351 template<class Type, class... Args>
352 Type* bs_new(Args &&...args)
353 {
354 return new (bs_alloc<Type, GenAlloc>()) Type(std::forward<Args>(args)...);
355 }
356
357 /** Frees all the bytes allocated at the specified location. */
358 inline void bs_free(void* ptr)
359 {
360 MemoryAllocator<GenAlloc>::free(ptr);
361 }
362
363 /** Frees memory previously allocated with bs_alloc_aligned(). */
364 inline void bs_free_aligned(void* ptr)
365 {
366 MemoryAllocator<GenAlloc>::freeAligned(ptr);
367 }
368
369 /** Frees memory previously allocated with bs_alloc_aligned16(). */
370 inline void bs_free_aligned16(void* ptr)
371 {
372 MemoryAllocator<GenAlloc>::freeAligned16(ptr);
373 }
374
375/************************************************************************/
376/* MACRO VERSIONS */
377/* You will almost always want to use the template versions but in some */
378/* cases (private destructor) it is not possible. In which case you may */
379/* use these instead. */
380/************************************************************************/
381#define BS_PVT_DELETE(T, ptr) \
382 (ptr)->~T(); \
383 MemoryAllocator<GenAlloc>::free(ptr);
384
385#define BS_PVT_DELETE_A(T, ptr, Alloc) \
386 (ptr)->~T(); \
387 MemoryAllocator<Alloc>::free(ptr);
388
389 /** @} */
390 /** @addtogroup Internal-Utility
391 * @{
392 */
393
394 /** @addtogroup Memory-Internal
395 * @{
396 */
397
398 /** Allocator for the standard library that internally uses bsf memory allocator. */
399 template <class T, class Alloc = GenAlloc>
400 class StdAlloc
401 {
402 public:
403 using value_type = T;
404 using pointer = value_type*;
405 using const_pointer = const value_type*;
406 using reference = value_type&;
407 using const_reference = const value_type&;
408 using size_type = std::size_t;
409 using difference_type = std::ptrdiff_t;
410
411 constexpr StdAlloc() = default;
412 constexpr StdAlloc(StdAlloc&&) = default;
413 constexpr StdAlloc(const StdAlloc&) = default;
414 template<class U, class Alloc2> constexpr StdAlloc(const StdAlloc<U, Alloc2>&) { };
415 template<class U, class Alloc2> constexpr bool operator==(const StdAlloc<U, Alloc2>&) const noexcept { return true; }
416 template<class U, class Alloc2> constexpr bool operator!=(const StdAlloc<U, Alloc2>&) const noexcept { return false; }
417
418 template<class U> class rebind { public: using other = StdAlloc<U, Alloc>; };
419
420 /** Allocate but don't initialize number elements of type T. */
421 static T* allocate(const size_t num)
422 {
423 if (num == 0)
424 return nullptr;
425
426 if (num > std::numeric_limits<size_t>::max() / sizeof(T))
427 return nullptr; // Error
428
429 void* const pv = bs_alloc<Alloc>(num * sizeof(T));
430 if (!pv)
431 return nullptr; // Error
432
433 return static_cast<T*>(pv);
434 }
435
436 /** Deallocate storage p of deleted elements. */
437 static void deallocate(pointer p, size_type)
438 {
439 bs_free<Alloc>(p);
440 }
441
442 static constexpr size_t max_size() { return std::numeric_limits<size_type>::max() / sizeof(T); }
443 static constexpr void destroy(pointer p) { p->~T(); }
444
445 template<class... Args>
446 static void construct(pointer p, Args&&... args) { new(p) T(std::forward<Args>(args)...); }
447 };
448
449 /** @} */
450 /** @} */
451}
452
453#include "Allocators/BsStackAlloc.h"
454#include "Allocators/BsFreeAlloc.h"
455#include "Allocators/BsFrameAlloc.h"
456#include "Allocators/BsStaticAlloc.h"
457#include "Allocators/BsMemAllocProfiler.h"
458