OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
pod_vector.hpp
1#pragma once
2#include <boost/container/vector.hpp>
3#include <boost/predef.h>
4
5#include <cinttypes>
6#include <cstddef>
7#include <cstdint>
8#include <cstdlib>
9#include <new>
10#include <type_traits>
11#include <vector>
12
13#if defined(_WIN32)
14#include <malloc.h> // _aligned_malloc / _aligned_free
15#endif
16
17// posix_memalign: available on Linux (incl. Android), the BSDs, macOS / iOS and
18// emscripten. Note that Apple does not define __unix__, hence the separate
19// check.
20#if defined(__linux__) || defined(__unix__) || defined(__unix) || defined(__APPLE__) \
21 || defined(__EMSCRIPTEN__)
22#define OSSIA_HAS_POSIX_MEMALIGN 1
23#else
24#define OSSIA_HAS_POSIX_MEMALIGN 0
25#endif
26
27// __builtin_assume_aligned: gcc >= 4.7 and clang. MSVC has no equivalent -
28// std::assume_aligned is a no-op there too, so going through <memory> would not
29// buy us anything.
30#if defined(__has_builtin)
31#if __has_builtin(__builtin_assume_aligned)
32#define OSSIA_HAS_ASSUME_ALIGNED 1
33#endif
34#elif defined(__GNUC__)
35#define OSSIA_HAS_ASSUME_ALIGNED 1
36#endif
37#if !defined(OSSIA_HAS_ASSUME_ALIGNED)
38#define OSSIA_HAS_ASSUME_ALIGNED 0
39#endif
40
41// Same thing on the allocation function itself, so that the alignment is known
42// at every call site and not only where ossia::assume_aligned is used.
43#if defined(__has_cpp_attribute)
44#if __has_cpp_attribute(gnu::assume_aligned)
45#define OSSIA_ASSUME_ALIGNED_RETURN(N) [[gnu::assume_aligned(N)]]
46#endif
47#endif
48#if !defined(OSSIA_ASSUME_ALIGNED_RETURN)
49#define OSSIA_ASSUME_ALIGNED_RETURN(N)
50#endif
51
52namespace ossia
53{
55inline constexpr std::size_t pod_vector_alignment = 32;
56
65template <std::size_t Align = pod_vector_alignment, typename T>
66[[nodiscard]] inline T* assume_aligned(T* p) noexcept
67{
68 static_assert(Align > 0 && (Align & (Align - 1)) == 0, "Align must be a power of two");
69#if OSSIA_HAS_ASSUME_ALIGNED
70 return static_cast<T*>(__builtin_assume_aligned(p, Align));
71#else
72 return p;
73#endif
74}
75
76#if defined(_MSC_VER) && defined(_DEBUG)
77template <class T>
78struct pod_allocator
79{
80 using value_type = T;
81
82 template <typename... Args>
83 explicit pod_allocator(Args&&...) noexcept
84 {
85 }
86
87 pod_allocator() noexcept = default;
88 pod_allocator(const pod_allocator&) noexcept = default;
89 pod_allocator(pod_allocator&&) noexcept = default;
90 pod_allocator& operator=(const pod_allocator&) noexcept = default;
91 pod_allocator& operator=(pod_allocator&&) noexcept = default;
92
93 static inline T* allocate(std::size_t num) noexcept { return new T[num]; }
94
95 static inline void deallocate(T* p, std::size_t) noexcept { delete[] p; }
96
97 friend inline bool
98 operator==(const pod_allocator& lhs, const pod_allocator& rhs) noexcept
99 {
100 return true;
101 }
102 friend inline bool
103 operator!=(const pod_allocator& lhs, const pod_allocator& rhs) noexcept
104 {
105 return false;
106 }
107};
108
109#else
110template <class T>
111struct pod_allocator
112{
113 using value_type = T;
114
115 pod_allocator() noexcept = default;
116 pod_allocator(const pod_allocator&) noexcept = default;
117 pod_allocator(pod_allocator&&) noexcept = default;
118 pod_allocator& operator=(const pod_allocator&) noexcept = default;
119 pod_allocator& operator=(pod_allocator&&) noexcept = default;
120
121 static inline T* allocate(std::size_t num) noexcept
122 {
123 static_assert(
124 std::is_standard_layout_v<T> && std::is_trivial_v<T>,
125 "can only be used with POD types");
126 static_assert(
127 alignof(T) <= alignof(std::max_align_t),
128 "type must not have specific alignment requirements");
129
130 return (T*)std::malloc(sizeof(T) * num);
131 }
132
133 static inline void deallocate(T* p, std::size_t) noexcept { std::free(p); }
134
135 friend inline bool operator==(pod_allocator lhs, pod_allocator rhs) noexcept
136 {
137 return true;
138 }
139 friend inline bool operator!=(pod_allocator lhs, pod_allocator rhs) noexcept
140 {
141 return false;
142 }
143};
144#endif
145
153template <class T, std::size_t Align>
155{
156 template <class U>
157 struct rebind
158 {
160 };
161
162 using value_type = T;
163
164 static constexpr std::size_t alignment = Align;
165 static_assert(Align > 0 && (Align & (Align - 1)) == 0, "Align must be a power of two");
166
167 // Whether we have to go out of our way to obtain the requested alignment.
168 // This is compared against alignof(std::max_align_t) and *not* against
169 // __STDCPP_DEFAULT_NEW_ALIGNMENT__: the plain path below goes through
170 // std::malloc, whose only guarantee is max_align_t, and there are targets
171 // where operator new is more aligned than malloc.
172 static constexpr bool needs_aligned_alloc = Align > alignof(std::max_align_t);
173
174 aligned_pod_allocator() noexcept = default;
175 aligned_pod_allocator(const aligned_pod_allocator&) noexcept = default;
176 aligned_pod_allocator(aligned_pod_allocator&&) noexcept = default;
177 aligned_pod_allocator& operator=(const aligned_pod_allocator&) noexcept = default;
178 aligned_pod_allocator& operator=(aligned_pod_allocator&&) noexcept = default;
179
180 template <class U>
182 {
183 }
184
185 OSSIA_ASSUME_ALIGNED_RETURN(Align)
186 static inline T* allocate(std::size_t num) noexcept
187 {
188 static_assert(
189 std::is_standard_layout_v<T> && std::is_trivial_v<T>,
190 "can only be used with POD types");
191 static_assert(
192 alignof(T) <= alignof(std::max_align_t),
193 "type must not have specific alignment requirements");
194
195 const std::size_t bytes = sizeof(T) * num;
196 void* p{};
197
198 if constexpr(needs_aligned_alloc)
199 {
200#if defined(_WIN32)
201 p = ::_aligned_malloc(bytes, Align);
202#elif OSSIA_HAS_POSIX_MEMALIGN
203 // posix_memalign requires the alignment to be a multiple of sizeof(void*)
204 static_assert(Align >= sizeof(void*));
205 if(::posix_memalign(&p, Align, bytes) != 0)
206 p = nullptr;
207#elif defined(__cpp_aligned_new)
208 p = ::operator new(bytes, std::align_val_t(Align), std::nothrow);
209#else
210 // Over-allocate, and store the offset to the real block in the byte just
211 // before the pointer we return.
212 static_assert(Align <= 255, "the offset has to fit in one byte");
213 if(void* const root = std::malloc(bytes + Align))
214 {
215 const auto pb = std::uintptr_t(root);
216 // Smallest aligned address strictly greater than pb, so that there is
217 // always at least one byte in front of it to store the offset in.
218 const auto pptr = (pb + Align) & ~std::uintptr_t(Align - 1);
219 *((unsigned char*)pptr - 1) = (unsigned char)(pptr - pb);
220 p = (void*)pptr;
221 }
222#endif
223 }
224 else
225 {
226 p = std::malloc(bytes);
227 }
228
229 return ossia::assume_aligned<Align>(static_cast<T*>(p));
230 }
231
232 static inline void deallocate(T* p, std::size_t) noexcept
233 {
234 if constexpr(needs_aligned_alloc)
235 {
236#if defined(_WIN32)
237 ::_aligned_free(p);
238#elif OSSIA_HAS_POSIX_MEMALIGN
239 std::free(p);
240#elif defined(__cpp_aligned_new)
241 ::operator delete(p, std::align_val_t(Align), std::nothrow);
242#else
243 if(p)
244 {
245 const auto count = *((unsigned char*)p - 1);
246 std::free((unsigned char*)p - count);
247 }
248#endif
249 }
250 else
251 {
252 std::free(p);
253 }
254 }
255
256 friend inline bool
257 operator==(aligned_pod_allocator lhs, aligned_pod_allocator rhs) noexcept
258 {
259 return true;
260 }
261 friend inline bool
262 operator!=(aligned_pod_allocator lhs, aligned_pod_allocator rhs) noexcept
263 {
264 return false;
265 }
266};
267
268template <typename T>
269struct pod_allocator_avx2 : aligned_pod_allocator<T, pod_vector_alignment>
270{
271 template <class U>
272 struct rebind
273 {
274 using other = pod_allocator_avx2<U>;
275 };
276 using aligned_pod_allocator<T, pod_vector_alignment>::aligned_pod_allocator;
277
278 pod_allocator_avx2() noexcept = default;
279
280 template <class U>
281 pod_allocator_avx2(const pod_allocator_avx2<U>&) noexcept
282 {
283 }
284};
285
286template <typename T>
287using pod_vector = boost::container::vector<T, pod_allocator_avx2<T>>;
288
289using int_vector = pod_vector<int>;
290using float_vector = pod_vector<float>;
291using double_vector = pod_vector<double>;
292
294template <typename T>
295[[nodiscard]] inline T* aligned_data(pod_vector<T>& v) noexcept
296{
297 return ossia::assume_aligned<pod_vector_alignment>(v.data());
298}
299template <typename T>
300[[nodiscard]] inline const T* aligned_data(const pod_vector<T>& v) noexcept
301{
302 return ossia::assume_aligned<pod_vector_alignment>(v.data());
303}
304}
Definition git_info.h:7
T * assume_aligned(T *p) noexcept
Tell the optimizer that p is aligned on Align bytes.
Definition pod_vector.hpp:66
constexpr std::size_t pod_vector_alignment
Alignment guaranteed by pod_allocator_avx2, and thus by ossia::pod_vector.
Definition pod_vector.hpp:55
T * aligned_data(pod_vector< T > &v) noexcept
v.data(), with the alignment guarantee made visible to the optimizer
Definition pod_vector.hpp:295
An allocator which guarantees that the memory is aligned on Align.
Definition pod_vector.hpp:155