SuperDex Physics C++ API
Loading...
Searching...
No Matches
allocator_inl.h
Go to the documentation of this file.
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
21
22#include "allocator.h" // Reverse include for Intellisense
23
24// Supported standard library features
25#define MOCHI_HAS_MEMORY_RESOURCE 1
26#define MOCHI_HAS_EXPERIMENTAL_MEMORY_RESOURCE 0
27#define MOCHI_HAS_PMR_NEW_DELETE_RESOURCE 1
28
29// Exception for Apple platforms with Clang versions older than 15.0.0.
30#if defined(__APPLE__) && defined(__clang_major__) && !defined(__cpp_lib_memory_resource)
31#if __clang_major__ < 15
32#undef MOCHI_HAS_MEMORY_RESOURCE
33#define MOCHI_HAS_MEMORY_RESOURCE 0
34#undef MOCHI_HAS_PMR_NEW_DELETE_RESOURCE
35#define MOCHI_HAS_PMR_NEW_DELETE_RESOURCE 0
36#undef MOCHI_HAS_EXPERIMENTAL_MEMORY_RESOURCE
37#define MOCHI_HAS_EXPERIMENTAL_MEMORY_RESOURCE 1
38#endif
39#endif
40
41// Exception for Unreal Engine Linux builds. UE5's bundled libc++ lacks <memory_resource>.
42#ifdef UNREALIOS_LINUX
43#if UNREALIOS_LINUX
44#undef MOCHI_HAS_MEMORY_RESOURCE
45#define MOCHI_HAS_MEMORY_RESOURCE 0
46#undef MOCHI_HAS_PMR_NEW_DELETE_RESOURCE
47#define MOCHI_HAS_PMR_NEW_DELETE_RESOURCE 0
48#undef MOCHI_HAS_EXPERIMENTAL_MEMORY_RESOURCE
49#define MOCHI_HAS_EXPERIMENTAL_MEMORY_RESOURCE 0
50#endif
51#endif
52
53// Exception for Android. Necessary because the Unreal Engine Android compiler is old and doesn't
54// support <memory_resource> at all.
55#if MOCHI_PLATFORM_ANDROID
56#undef MOCHI_HAS_MEMORY_RESOURCE
57#define MOCHI_HAS_MEMORY_RESOURCE 0
58#undef MOCHI_HAS_PMR_NEW_DELETE_RESOURCE
59#define MOCHI_HAS_PMR_NEW_DELETE_RESOURCE 0
60#undef MOCHI_HAS_EXPERIMENTAL_MEMORY_RESOURCE
61#define MOCHI_HAS_EXPERIMENTAL_MEMORY_RESOURCE 0
62#endif
63
64#if MOCHI_HAS_MEMORY_RESOURCE
65#include <memory_resource>
66#elif MOCHI_HAS_EXPERIMENTAL_MEMORY_RESOURCE
67#include <experimental/memory_resource>
68#endif
69
70namespace superdex {
71
72/*********************************************************************************************
73 Macros
74*/
75
76#if MOCHI_ALLOCATOR_DEBUG
77#if MOCHI_HAS_VA_OPT
78#define MOCHI_ALLOCATOR_ASSERT(condition_without_side_effects, ...) \
79 MOCHI_ASSERT(condition_without_side_effects __VA_OPT__(, )##__VA_ARGS__)
80#else
81// Strict C++17 compatibility requires __VA_ARGS__ to be non-empty, to prevent a trailing comma.
82// Therefore, MOCHI_ALLOCATOR_ASSERT must always have a message string in C++17 limited headers,
83// even if the information in that string is redundant.
84#define MOCHI_ALLOCATOR_ASSERT(condition_without_side_effects, ...) \
85 MOCHI_ASSERT(condition_without_side_effects, ##__VA_ARGS__)
86#endif
87#else
88#define MOCHI_ALLOCATOR_ASSERT(...)
89#endif
90
91/*********************************************************************************************
92 Allocator
93*/
94
95inline void* Allocator::allocate(std::size_t sizeInBytes, std::size_t alignment) {
96 MOCHI_ALLOCATOR_ASSERT(IsPowerOfTwo(alignment), "Alignment must be a power of two");
97 void* ptr = do_allocate(sizeInBytes, alignment);
99 ptr != nullptr || sizeInBytes == 0, "Allocation failed without throwing an exception");
101 !(reinterpret_cast<intptr_t>(ptr) & (alignment - 1)),
102 "Address does not have the requested alignment of %zu bytes",
103 alignment);
104 return ptr;
105}
106
107inline void Allocator::deallocate(void* ptr, std::size_t sizeInBytes, std::size_t alignment) {
108 MOCHI_ALLOCATOR_ASSERT(IsPowerOfTwo(alignment), "Alignment must be a power of two");
110 !(reinterpret_cast<intptr_t>(ptr) & (alignment - 1)),
111 "Address does not have the stated alignment");
112 return do_deallocate(ptr, sizeInBytes, alignment);
113}
114
115inline bool Allocator::is_equal(Allocator const& other) const noexcept {
116 return do_is_equal(other);
117}
118
119/*********************************************************************************************
120 Default Allocator
121*/
122
123inline void* DefaultAllocator::do_allocate(size_t sizeInBytes, size_t alignment) {
124#if MOCHI_PMR_USES_JEMALLOC
125 // Some fbcode build modes use jemalloc to implement operators new and delete, and thus
126 // std::prm::new_delete_resource. The problem is that jemalloc always rounds up to a minimum size
127 // of 16 bytes. We have to do the same or the following can happen:
128 // 1) void* ptr = myAllocator.allocate(8, 8); // Request 8 bytes
129 // 2) Behind the scene, jemalloc actually allocates 16 bytes
130 // 3) myAllocator.deallocate(ptr, 8, 8); // Matches the call to allocate
131 // 4) jemalloc reports a fatal error because 16 bytes were allocated, but only 8 bytes are being
132 // deallocated.
133 //
134 // Our work-around is to round up to 16 bytes all the time.
135 //
136 sizeInBytes = Max(sizeInBytes, size_t(16));
137#endif
138
139#if MOCHI_HAS_MEMORY_RESOURCE && MOCHI_HAS_PMR_NEW_DELETE_RESOURCE
140 return std::pmr::new_delete_resource()->allocate(sizeInBytes, alignment);
141#elif MOCHI_HAS_EXPERIMENTAL_MEMORY_RESOURCE && MOCHI_HAS_PMR_NEW_DELETE_RESOURCE
142 return std::experimental::pmr::new_delete_resource()->allocate(sizeInBytes, alignment);
143#elif defined(__cpp_lib_aligned_alloc)
144 // Use std::aligned_alloc. Confirmed to work for Apple Clang 14.0.3
145 if (alignment <= 16) {
146 return std::malloc(sizeInBytes);
147 } else {
148 return std::aligned_alloc(alignment, sizeInBytes);
149 }
150#else
151 // Use posix_memalign for larger allocations on Android.
152 if (alignment <= 8) {
153 return malloc(sizeInBytes);
154 } else {
155 void* ptr = nullptr;
156 posix_memalign(&ptr, alignment, sizeInBytes);
157 return ptr;
158 }
159#endif
160}
161
162inline void DefaultAllocator::do_deallocate(
163 void* ptr,
164 [[maybe_unused]] size_t sizeInBytes,
165 [[maybe_unused]] size_t alignment) {
166#if MOCHI_PMR_USES_JEMALLOC
167 // See comment in do_allocate.
168 sizeInBytes = Max(sizeInBytes, size_t(16));
169#endif
170
171#if MOCHI_HAS_MEMORY_RESOURCE && MOCHI_HAS_PMR_NEW_DELETE_RESOURCE
172 return std::pmr::new_delete_resource()->deallocate(ptr, sizeInBytes, alignment);
173#elif MOCHI_HAS_EXPERIMENTAL_MEMORY_RESOURCE && MOCHI_HAS_PMR_NEW_DELETE_RESOURCE
174 return std::experimental::pmr::new_delete_resource()->deallocate(ptr, sizeInBytes, alignment);
175#elif defined(__cpp_lib_aligned_alloc)
176 std::free(ptr); // Free memory from std::alloc_aligned
177#else
178 free(ptr); // Free memory from poxis_memalign
179#endif
180}
181
182inline bool DefaultAllocator::do_is_equal(Allocator const& other) const noexcept {
183 // We can't assume that two DefaultAllocators with different addresses are compatible, beause they
184 // might be compiled into different dynamic libraries with incompatible system allocators. Mochi
185 // code always uses GetDefaultAllocator() which always returns a consistent address within
186 // statically linked libraries (e.g. all Mochi libraries compiled into one DLL). Therefore Mochi
187 // containers using DefaultAllocator can move their allocations efficiently within Mochi code, but
188 // may need to perform a deep copy when crossing DLL-boundaries.
189 return (&other == this);
190}
191
192/*********************************************************************************************
193 Utilities
194*/
195
197 static DefaultAllocator s_allocator;
198 return &s_allocator;
199}
200
201} // namespace superdex
#define MOCHI_ALLOCATOR_ASSERT(condition_without_side_effects,...)
Virtual interface class for memory allocation and deallocation.
Definition allocator.h:47
virtual void do_deallocate(void *ptr, std::size_t sizeInBytes, std::size_t alignment)=0
void * allocate(std::size_t sizeInBytes, std::size_t alignment=alignof(std::max_align_t))
Allocate memory of the specified size and alignment.
void deallocate(void *ptr, std::size_t sizeInBytes, std::size_t alignment=alignof(std::max_align_t))
Deallocate a block of memory that was previously allocated by this Allocator instance or by a compati...
virtual void * do_allocate(std::size_t sizeInBytes, std::size_t alignment)=0
virtual bool do_is_equal(Allocator const &other) const noexcept=0
bool is_equal(Allocator const &other) const noexcept
Return true if memory allocated by this Allocator instance can be deallocated by the other Allocator ...
Mochi's default implementation of the Allocator interface.
Definition allocator.h:107
#define MOCHI_FORCE_INLINE
constexpr bool IsPowerOfTwo(T a)
Allocator * GetDefaultAllocator()
Get an instance of the DefaultAllocator class.
constexpr T const & Max(T const &a, T const &b)