dispenso 1.6.2
A library for task parallelism
Loading...
Searching...
No Matches
platform.h
Go to the documentation of this file.
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
14#pragma once
15#include <algorithm>
16#include <atomic>
17#include <cassert>
18#include <cstdlib>
19#include <memory>
20#include <thread>
21#include <type_traits>
22
23#if defined(_MSC_VER) && \
24 (defined(_M_AMD64) || defined(_M_IX86) || defined(_M_ARM64) || defined(_M_ARM))
25#include <intrin.h>
26#endif
27
28namespace dispenso {
29
30#define DISPENSO_MAJOR_VERSION 1
31#define DISPENSO_MINOR_VERSION 6
32#define DISPENSO_PATCH_VERSION 2
33
34// C++20 concepts support detection
35#if __cplusplus >= 202002L && defined(__cpp_concepts) && __cpp_concepts >= 201907L
36#define DISPENSO_HAS_CONCEPTS 1
37#include <concepts>
38#else
39#define DISPENSO_HAS_CONCEPTS 0
40#endif
41
56#if DISPENSO_HAS_CONCEPTS
57#define DISPENSO_REQUIRES(...) requires(__VA_ARGS__)
58#else
59#define DISPENSO_REQUIRES(...)
60#endif
61
73#if __cplusplus >= 201703L
74#define DISPENSO_DEPRECATED(msg) [[deprecated(msg)]]
75#else
76#define DISPENSO_DEPRECATED(msg)
77#endif
78
79#if defined(DISPENSO_SHARED_LIB)
80#if defined _WIN32
81
82#if defined(DISPENSO_LIB_EXPORT)
83#define DISPENSO_DLL_ACCESS __declspec(dllexport)
84#else
85#define DISPENSO_DLL_ACCESS __declspec(dllimport)
86#endif // DISPENSO_LIB_EXPORT
87
88#elif defined(__clang__) || defined(__GNUC__)
89#define DISPENSO_DLL_ACCESS __attribute__((visibility("default")))
90#endif // PLATFORM
91#endif // DISPENSO_SHARED_LIB
92
93#if !defined(DISPENSO_DLL_ACCESS)
94#define DISPENSO_DLL_ACCESS
95#endif // DISPENSO_DLL_ACCESS
96
97// Suppresses Clang thread-safety-analysis warnings for a single function.
98// Expands to the attribute on Clang; a no-op on all other compilers (MSVC, GCC, etc.)
99// that do not support thread-safety analysis.
100#if defined(__clang__)
101#define DISPENSO_NO_THREAD_SAFETY_ANALYSIS __attribute__((no_thread_safety_analysis))
102#else
103#define DISPENSO_NO_THREAD_SAFETY_ANALYSIS
104#endif
105
106using ssize_t = std::make_signed<std::size_t>::type;
107
108#if defined(__CUDACC__)
109#define DISPENSO_INLINE __host__ __device__ __forceinline__
110#elif defined(__clang__) || defined(__GNUC__)
111#define DISPENSO_INLINE __attribute__((always_inline)) inline
112#elif defined(_MSC_VER) || defined(__INTEL_COMPILER)
113#define DISPENSO_INLINE __forceinline
114#else
115#define DISPENSO_INLINE inline
116#endif // PLATFORM
117
122#if defined(__APPLE__) && defined(__arm64__)
123constexpr size_t kCacheLineSize = 128;
124#else
125constexpr size_t kCacheLineSize = 64;
126#endif
127
140#define DISPENSO_CACHELINE_ALIGNED alignas(kCacheLineSize)
141
147// TODO(bbudge): Non-gcc/clang/msvc platforms.
148#if defined(_MSC_VER)
149#define DISPENSO_THREAD_LOCAL __declspec(thread)
150#elif defined(__GNUC__) || defined(__clang__)
151#define DISPENSO_THREAD_LOCAL __thread
152#else
153#error Supply lightweight thread-locals for this compiler. Can define to thread_local if lightweight not available
154#endif
155
156#if (defined(__GNUC__) || defined(__clang__))
157#define DISPENSO_EXPECT(a, b) __builtin_expect(a, b)
158#else
159#define DISPENSO_EXPECT(a, b) a
160#endif
161
162// clang-format off
163#if (defined(__GNUC__) || defined(__clang__))
164#define DO_PRAGMA(X) _Pragma(#X)
165#define DISPENSO_DISABLE_WARNING_PUSH DO_PRAGMA(GCC diagnostic push)
166#define DISPENSO_DISABLE_WARNING_POP DO_PRAGMA(GCC diagnostic pop)
167#define DISPENSO_DISABLE_WARNING(warningName) DO_PRAGMA(GCC diagnostic ignored #warningName)
168#if !defined(__clang__)
169#define DISPENSO_DISABLE_WARNING_ZERO_VARIADIC_MACRO_ARGUMENTS
170#define DISPENSO_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
171#define DISPENSO_DISABLE_WARNING_FREE_NONHEAP_OBJECT \
172 DISPENSO_DISABLE_WARNING(-Wfree-nonheap-object)
173#else
174#define DISPENSO_DISABLE_WARNING_ZERO_VARIADIC_MACRO_ARGUMENTS \
175 DISPENSO_DISABLE_WARNING(-Wgnu-zero-variadic-macro-arguments)
176#define DISPENSO_DISABLE_WARNING_GLOBAL_CONSTRUCTORS \
177 DISPENSO_DISABLE_WARNING(-Wglobal-constructors)
178#define DISPENSO_DISABLE_WARNING_FREE_NONHEAP_OBJECT
179#endif
180#elif defined(_MSC_VER)
181#define DISPENSO_DISABLE_WARNING_PUSH __pragma(warning(push))
182#define DISPENSO_DISABLE_WARNING_POP __pragma(warning(pop))
183#define DISPENSO_DISABLE_WARNING(warningNumber) __pragma(warning(disable : warningNumber))
184#define DISPENSO_DISABLE_WARNING_ZERO_VARIADIC_MACRO_ARGUMENTS
185#define DISPENSO_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
186#define DISPENSO_DISABLE_WARNING_FREE_NONHEAP_OBJECT
187#else
188#define DISPENSO_DISABLE_WARNING_PUSH
189#define DISPENSO_DISABLE_WARNING_POP
190#define DISPENSO_DISABLE_WARNING_ZERO_VARIADIC_MACRO_ARGUMENTS
191#define DISPENSO_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
192#define DISPENSO_DISABLE_WARNING_FREE_NONHEAP_OBJECT
193#endif
194// clang-format on
195
203template <typename T>
205 public:
206 CacheAligned() = default;
208 CacheAligned(T t) : t_(t) {}
209 operator T&() {
210 return t_;
211 }
212
213 operator const T&() const {
214 return t_;
215 }
216
217 private:
218 alignas(kCacheLineSize) T t_;
219};
220
221namespace detail {
222
223template <typename T>
224struct AlignedBuffer {
225 alignas(alignof(T)) char b[sizeof(T)];
226};
227
228template <typename T>
229struct alignas(kCacheLineSize) AlignedAtomic : public std::atomic<T*> {};
230
231inline void* alignedMalloc(size_t bytes, size_t alignment) {
232 alignment = std::max(alignment, sizeof(uintptr_t));
233 char* ptr = reinterpret_cast<char*>(::malloc(bytes + alignment));
234 uintptr_t base = reinterpret_cast<uintptr_t>(ptr);
235 uintptr_t oldBase = base;
236 uintptr_t mask = alignment - 1;
237 base += alignment;
238 base &= ~mask;
239
240 uintptr_t* recovery = reinterpret_cast<uintptr_t*>(base - sizeof(uintptr_t));
241 *recovery = oldBase;
242 return reinterpret_cast<void*>(base);
243}
244
245inline void* alignedMalloc(size_t bytes) {
246 return alignedMalloc(bytes, kCacheLineSize);
247}
248
249inline void alignedFree(void* ptr) {
250 if (!ptr) {
251 return;
252 }
253 char* p = reinterpret_cast<char*>(ptr);
254 uintptr_t recovered = *reinterpret_cast<uintptr_t*>(p - sizeof(uintptr_t));
255 ::free(reinterpret_cast<void*>(recovered));
256}
257
258template <typename T>
259struct AlignedFreeDeleter {
260 void operator()(T* ptr) {
261 ptr->~T();
262 detail::alignedFree(ptr);
263 }
264};
265template <>
266struct AlignedFreeDeleter<void> {
267 void operator()(void* ptr) {
268 detail::alignedFree(ptr);
269 }
270};
271
272// Array deleter for aligned allocations. Destructor loop is elided by
273// the compiler for trivially destructible types.
274template <typename T>
275struct AlignedArrayFreeDeleter {
276 size_t count;
277 void operator()(T* ptr) {
278 for (size_t i = 0; i < count; ++i) {
279 ptr[i].~T();
280 }
281 detail::alignedFree(ptr);
282 }
283};
284
285// Allocate a value-initialized array of T with alignof(T) alignment.
286// Constructor/destructor loops are elided for trivial types.
287template <typename T>
288std::unique_ptr<T[], AlignedArrayFreeDeleter<T>> makeAlignedArray(size_t n) {
289 void* raw = detail::alignedMalloc(sizeof(T) * n, alignof(T));
290 T* arr = static_cast<T*>(raw);
291 for (size_t i = 0; i < n; ++i) {
292 new (&arr[i]) T();
293 }
294 return std::unique_ptr<T[], AlignedArrayFreeDeleter<T>>(arr, AlignedArrayFreeDeleter<T>{n});
295}
296
297// Allocate a single object of T with alignof(T) alignment.
298template <typename T, class... Args>
299std::unique_ptr<T, AlignedFreeDeleter<T>> makeAligned(Args&&... args) {
300 void* raw = detail::alignedMalloc(sizeof(T), alignof(T));
301 T* obj = new (raw) T(std::forward<Args>(args)...);
302 return std::unique_ptr<T, AlignedFreeDeleter<T>>(obj);
303}
304
305template <typename T, class... Args>
306std::shared_ptr<T> make_shared(Args&&... args) {
307 void* tv = alignedMalloc(sizeof(T), alignof(T));
308 T* t = new (tv) T(std::forward<Args>(args)...);
309 return std::shared_ptr<T>(t, AlignedFreeDeleter<T>());
310}
311
312inline constexpr uintptr_t alignToCacheLine(uintptr_t val) {
313 constexpr uintptr_t kMask = kCacheLineSize - 1;
314 val += kMask;
315 val &= ~kMask;
316 return val;
317}
318
319#if defined __x86_64__ || defined __i386__
320inline void cpuRelax() {
321 asm volatile("pause" ::: "memory");
322}
323#elif defined _MSC_VER && (defined _M_AMD64 || defined _M_IX86)
324inline void cpuRelax() {
325 _mm_pause();
326}
327#elif defined __arm64__ || defined __aarch64__
328inline void cpuRelax() {
329 asm volatile("yield" ::: "memory");
330}
331#elif defined _MSC_VER && (defined _M_ARM64 || defined _M_ARM)
332inline void cpuRelax() {
333 __yield();
334}
335#elif defined __powerpc__ || defined __POWERPC__
336#if defined __APPLE__
337inline void cpuRelax() {
338 asm volatile("or r27,r27,r27" ::: "memory");
339}
340#else
341inline void cpuRelax() {
342 asm volatile("or 27,27,27" ::: "memory");
343}
344#endif // APPLE
345#else
346// TODO: provide reasonable relax on other archs.
347inline void cpuRelax() {}
348#endif // ARCH
349
350// When statically chunking a range, it is generally not possible to use a single chunk size plus
351// remainder and get a good load distribution. By estimating too high, we can have idle threads. By
352// estimating too low, the remainder can be several times as large as the chunk for other threads.
353// Instead, we compute the chunk size that is the ceil of the fractional chunk size. That can be
354// used for the first transitionIndex values, while the remaining (chunks - transitionTaskIndex)
355// values will be ceilChunkSize - 1.
356struct StaticChunking {
357 ssize_t transitionTaskIndex;
358 ssize_t ceilChunkSize;
359};
360
361inline StaticChunking staticChunkSize(ssize_t items, ssize_t chunks) {
362 assert(chunks > 0);
363 StaticChunking chunking;
364 chunking.ceilChunkSize = (items + chunks - 1) / chunks;
365 ssize_t numLeft = chunking.ceilChunkSize * chunks - items;
366 chunking.transitionTaskIndex = chunks - numLeft;
367 return chunking;
368}
369
370// Granularity-aware variant: ceilChunkSize is rounded UP to a multiple of
371// `granularity`, so each "ceil" chunk is granularity-aligned. The "floor"
372// chunks (those at index >= transitionTaskIndex) are ceilChunkSize - granularity,
373// also granularity-aligned. Caller must have already trimmed `items` to a
374// multiple of `granularity` so that all chunks (not just intermediate ones)
375// are granularity-multiples.
376inline StaticChunking staticChunkSizeGranular(ssize_t items, ssize_t chunks, uint32_t granularity) {
377 assert(chunks > 0);
378 assert(granularity >= 1);
379 if (granularity <= 1) {
380 return staticChunkSize(items, chunks);
381 }
382 assert(items % static_cast<ssize_t>(granularity) == 0);
383 StaticChunking chunking;
384 // Items measured in "granularity units".
385 ssize_t gUnits = items / static_cast<ssize_t>(granularity);
386 ssize_t ceilG = (gUnits + chunks - 1) / chunks;
387 ssize_t numLeft = ceilG * chunks - gUnits;
388 chunking.ceilChunkSize = ceilG * static_cast<ssize_t>(granularity);
389 chunking.transitionTaskIndex = chunks - numLeft;
390 return chunking;
391}
392
393} // namespace detail
394} // namespace dispenso
constexpr size_t kCacheLineSize
A constant that defines a safe number of bytes+alignment to avoid false sharing.
Definition platform.h:125
detail::AlignedAtomic< T > AlignedAtomic
Cache-line aligned atomic pointer.
Definition util.h:230
detail::AlignedBuffer< T > AlignedBuffer
Buffer with proper alignment for type T.
Definition util.h:213
detail::StaticChunking StaticChunking
Information for statically chunking a range across threads.
Definition util.h:264