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 <limits>
20#include <memory>
21#include <new>
22#include <thread>
23#include <type_traits>
24
25#if defined(_MSC_VER) && \
26 (defined(_M_AMD64) || defined(_M_IX86) || defined(_M_ARM64) || defined(_M_ARM))
27#include <intrin.h>
28#endif
29
30#define DISPENSO_MAJOR_VERSION 1
31#define DISPENSO_MINOR_VERSION 6
32#define DISPENSO_PATCH_VERSION 2
33
42#if defined(_MSVC_LANG) && _MSVC_LANG > __cplusplus
43#define DISPENSO_CPLUSPLUS _MSVC_LANG
44#else
45#define DISPENSO_CPLUSPLUS __cplusplus
46#endif
47
48// C++20 concepts support detection
49#if DISPENSO_CPLUSPLUS >= 202002L && defined(__cpp_concepts) && __cpp_concepts >= 201907L
50#define DISPENSO_HAS_CONCEPTS 1
51#include <concepts>
52#else
53#define DISPENSO_HAS_CONCEPTS 0
54#endif
55
56namespace dispenso {
57
72#if DISPENSO_HAS_CONCEPTS
73#define DISPENSO_REQUIRES(...) requires(__VA_ARGS__)
74#else
75#define DISPENSO_REQUIRES(...)
76#endif
77
89#if DISPENSO_CPLUSPLUS >= 201703L
90#define DISPENSO_DEPRECATED(msg) [[deprecated(msg)]]
91#else
92#define DISPENSO_DEPRECATED(msg)
93#endif
94
95#if defined(DISPENSO_SHARED_LIB)
96#if defined _WIN32
97
98#if defined(DISPENSO_LIB_EXPORT)
99#define DISPENSO_DLL_ACCESS __declspec(dllexport)
100#else
101#define DISPENSO_DLL_ACCESS __declspec(dllimport)
102#endif // DISPENSO_LIB_EXPORT
103
104#elif defined(__clang__) || defined(__GNUC__)
105#define DISPENSO_DLL_ACCESS __attribute__((visibility("default")))
106#endif // PLATFORM
107#endif // DISPENSO_SHARED_LIB
108
109#if !defined(DISPENSO_DLL_ACCESS)
110#define DISPENSO_DLL_ACCESS
111#endif // DISPENSO_DLL_ACCESS
112
113// Suppresses Clang thread-safety-analysis warnings for a single function.
114// Expands to the attribute on Clang; a no-op on all other compilers (MSVC, GCC, etc.)
115// that do not support thread-safety analysis.
116#if defined(__clang__)
117#define DISPENSO_NO_THREAD_SAFETY_ANALYSIS __attribute__((no_thread_safety_analysis))
118#else
119#define DISPENSO_NO_THREAD_SAFETY_ANALYSIS
120#endif
121
122using ssize_t = std::make_signed<std::size_t>::type;
123
124#if defined(__CUDACC__)
125#define DISPENSO_INLINE __host__ __device__ __forceinline__
126#elif defined(__clang__) || defined(__GNUC__)
127#define DISPENSO_INLINE __attribute__((always_inline)) inline
128#elif defined(_MSC_VER) || defined(__INTEL_COMPILER)
129#define DISPENSO_INLINE __forceinline
130#else
131#define DISPENSO_INLINE inline
132#endif // PLATFORM
133
138#if defined(__APPLE__) && defined(__arm64__)
139constexpr size_t kCacheLineSize = 128;
140#else
141constexpr size_t kCacheLineSize = 64;
142#endif
143
156#define DISPENSO_CACHELINE_ALIGNED alignas(kCacheLineSize)
157
163// TODO(bbudge): Non-gcc/clang/msvc platforms.
164#if defined(_MSC_VER)
165#define DISPENSO_THREAD_LOCAL __declspec(thread)
166#elif defined(__GNUC__) || defined(__clang__)
167#define DISPENSO_THREAD_LOCAL __thread
168#else
169#error Supply lightweight thread-locals for this compiler. Can define to thread_local if lightweight not available
170#endif
171
172#if (defined(__GNUC__) || defined(__clang__))
173#define DISPENSO_EXPECT(a, b) __builtin_expect(a, b)
174#else
175#define DISPENSO_EXPECT(a, b) a
176#endif
177
178// clang-format off
179#if (defined(__GNUC__) || defined(__clang__))
180#define DISPENSO_DO_PRAGMA(X) _Pragma(#X)
181#define DISPENSO_DISABLE_WARNING_PUSH DISPENSO_DO_PRAGMA(GCC diagnostic push)
182#define DISPENSO_DISABLE_WARNING_POP DISPENSO_DO_PRAGMA(GCC diagnostic pop)
183#define DISPENSO_DISABLE_WARNING(warningName) DISPENSO_DO_PRAGMA(GCC diagnostic ignored #warningName)
184#if !defined(__clang__)
185#define DISPENSO_DISABLE_WARNING_ZERO_VARIADIC_MACRO_ARGUMENTS
186#define DISPENSO_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
187#define DISPENSO_DISABLE_WARNING_FREE_NONHEAP_OBJECT \
188 DISPENSO_DISABLE_WARNING(-Wfree-nonheap-object)
189#else
190#define DISPENSO_DISABLE_WARNING_ZERO_VARIADIC_MACRO_ARGUMENTS \
191 DISPENSO_DISABLE_WARNING(-Wgnu-zero-variadic-macro-arguments)
192#define DISPENSO_DISABLE_WARNING_GLOBAL_CONSTRUCTORS \
193 DISPENSO_DISABLE_WARNING(-Wglobal-constructors)
194#define DISPENSO_DISABLE_WARNING_FREE_NONHEAP_OBJECT
195#endif
196#elif defined(_MSC_VER)
197#define DISPENSO_DISABLE_WARNING_PUSH __pragma(warning(push))
198#define DISPENSO_DISABLE_WARNING_POP __pragma(warning(pop))
199#define DISPENSO_DISABLE_WARNING(warningNumber) __pragma(warning(disable : warningNumber))
200#define DISPENSO_DISABLE_WARNING_ZERO_VARIADIC_MACRO_ARGUMENTS
201#define DISPENSO_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
202#define DISPENSO_DISABLE_WARNING_FREE_NONHEAP_OBJECT
203#else
204#define DISPENSO_DISABLE_WARNING_PUSH
205#define DISPENSO_DISABLE_WARNING_POP
206#define DISPENSO_DISABLE_WARNING_ZERO_VARIADIC_MACRO_ARGUMENTS
207#define DISPENSO_DISABLE_WARNING_GLOBAL_CONSTRUCTORS
208#define DISPENSO_DISABLE_WARNING_FREE_NONHEAP_OBJECT
209#endif
210// clang-format on
211
224template <typename T>
226 public:
227 CacheAligned() = default;
229 CacheAligned(T t) : t_(t) {}
230 operator T&() {
231 return t_;
232 }
233
234 operator const T&() const {
235 return t_;
236 }
237
238 private:
239 alignas(kCacheLineSize) T t_;
240};
241
242namespace detail {
243
244template <typename T>
245struct AlignedBuffer {
246 alignas(alignof(T)) char b[sizeof(T)];
247};
248
249template <typename T>
250struct alignas(kCacheLineSize) AlignedAtomic : public std::atomic<T*> {};
251
252// Returns nullptr if the allocation fails (or bytes + alignment overflows), like malloc.
253inline void* alignedMalloc(size_t bytes, size_t alignment) {
254 alignment = std::max(alignment, sizeof(uintptr_t));
255 if (bytes > ~size_t{0} - alignment) {
256 return nullptr;
257 }
258 char* ptr = reinterpret_cast<char*>(::malloc(bytes + alignment));
259 if (!ptr) {
260 return nullptr;
261 }
262 uintptr_t base = reinterpret_cast<uintptr_t>(ptr);
263 uintptr_t oldBase = base;
264 uintptr_t mask = alignment - 1;
265 base += alignment;
266 base &= ~mask;
267
268 uintptr_t* recovery = reinterpret_cast<uintptr_t*>(base - sizeof(uintptr_t));
269 *recovery = oldBase;
270 return reinterpret_cast<void*>(base);
271}
272
273inline void* alignedMalloc(size_t bytes) {
274 return alignedMalloc(bytes, kCacheLineSize);
275}
276
277// alignedMalloc with operator new semantics, for callers that do not check for nullptr: throws
278// std::bad_alloc on failure (aborts when built without exceptions) and never returns nullptr.
279inline void* alignedMallocOrThrow(size_t bytes, size_t alignment) {
280 void* ptr = alignedMalloc(bytes, alignment);
281 if (DISPENSO_EXPECT(ptr == nullptr, false)) {
282#if defined(__cpp_exceptions)
283 throw std::bad_alloc();
284#else
285 std::abort();
286#endif // __cpp_exceptions
287 }
288 return ptr;
289}
290
291inline void* alignedMallocOrThrow(size_t bytes) {
292 return alignedMallocOrThrow(bytes, kCacheLineSize);
293}
294
295inline void alignedFree(void* ptr) {
296 if (!ptr) {
297 return;
298 }
299 char* p = reinterpret_cast<char*>(ptr);
300 uintptr_t recovered = *reinterpret_cast<uintptr_t*>(p - sizeof(uintptr_t));
301 ::free(reinterpret_cast<void*>(recovered));
302}
303
304// Minimal standard allocator that honours alignof(T). Before C++17 std::allocator ignores
305// over-alignment, so standard containers of over-aligned types (e.g. CacheAligned<T>, or structs
306// with alignas(kCacheLineSize) members) would place elements at misaligned addresses. Deliberately
307// not an alias for std::allocator at C++17, so the container type (and which allocator frees its
308// memory) is the same whichever standard a translation unit is compiled with.
309// TODO: Once C++17 is the minimum, std::allocator honours over-alignment and replaces this.
310template <typename T>
311struct AlignedAllocator {
312 using value_type = T;
313 AlignedAllocator() noexcept = default;
314 template <typename U>
315 AlignedAllocator(const AlignedAllocator<U>&) noexcept {}
316 T* allocate(size_t n) {
317 if (n > std::numeric_limits<size_t>::max() / sizeof(T)) {
318#if defined(__cpp_exceptions)
319 throw std::bad_alloc();
320#else
321 std::abort();
322#endif // __cpp_exceptions
323 }
324 return static_cast<T*>(detail::alignedMallocOrThrow(n * sizeof(T), alignof(T)));
325 }
326 void deallocate(T* p, size_t) noexcept {
327 detail::alignedFree(p);
328 }
329};
330template <typename T, typename U>
331bool operator==(const AlignedAllocator<T>&, const AlignedAllocator<U>&) noexcept {
332 return true;
333}
334template <typename T, typename U>
335bool operator!=(const AlignedAllocator<T>&, const AlignedAllocator<U>&) noexcept {
336 return false;
337}
338
339template <typename T>
340struct AlignedFreeDeleter {
341 void operator()(T* ptr) {
342 ptr->~T();
343 detail::alignedFree(ptr);
344 }
345};
346template <>
347struct AlignedFreeDeleter<void> {
348 void operator()(void* ptr) {
349 detail::alignedFree(ptr);
350 }
351};
352
353// Array deleter for aligned allocations. Destructor loop is elided by
354// the compiler for trivially destructible types.
355template <typename T>
356struct AlignedArrayFreeDeleter {
357 size_t count;
358 void operator()(T* ptr) {
359 for (size_t i = 0; i < count; ++i) {
360 ptr[i].~T();
361 }
362 detail::alignedFree(ptr);
363 }
364};
365
366// Allocate a value-initialized array of T with alignof(T) alignment.
367// Constructor/destructor loops are elided for trivial types.
368template <typename T>
369std::unique_ptr<T[], AlignedArrayFreeDeleter<T>> makeAlignedArray(size_t n) {
370 void* raw = detail::alignedMallocOrThrow(sizeof(T) * n, alignof(T));
371 T* arr = static_cast<T*>(raw);
372 for (size_t i = 0; i < n; ++i) {
373 new (&arr[i]) T();
374 }
375 return std::unique_ptr<T[], AlignedArrayFreeDeleter<T>>(arr, AlignedArrayFreeDeleter<T>{n});
376}
377
378// Allocate a single object of T with alignof(T) alignment.
379template <typename T, class... Args>
380std::unique_ptr<T, AlignedFreeDeleter<T>> makeAligned(Args&&... args) {
381 void* raw = detail::alignedMallocOrThrow(sizeof(T), alignof(T));
382 T* obj = new (raw) T(std::forward<Args>(args)...);
383 return std::unique_ptr<T, AlignedFreeDeleter<T>>(obj);
384}
385
386template <typename T, class... Args>
387std::shared_ptr<T> make_shared(Args&&... args) {
388 void* tv = alignedMallocOrThrow(sizeof(T), alignof(T));
389 T* t = new (tv) T(std::forward<Args>(args)...);
390 return std::shared_ptr<T>(t, AlignedFreeDeleter<T>());
391}
392
393inline constexpr uintptr_t alignToCacheLine(uintptr_t val) {
394 constexpr uintptr_t kMask = kCacheLineSize - 1;
395 val += kMask;
396 val &= ~kMask;
397 return val;
398}
399
400#if defined __x86_64__ || defined __i386__
401inline void cpuRelax() {
402 asm volatile("pause" ::: "memory");
403}
404#elif defined _MSC_VER && (defined _M_AMD64 || defined _M_IX86)
405inline void cpuRelax() {
406 _mm_pause();
407}
408#elif defined __arm64__ || defined __aarch64__
409inline void cpuRelax() {
410 asm volatile("yield" ::: "memory");
411}
412#elif defined _MSC_VER && (defined _M_ARM64 || defined _M_ARM)
413inline void cpuRelax() {
414 __yield();
415}
416#elif defined __powerpc__ || defined __POWERPC__
417#if defined __APPLE__
418inline void cpuRelax() {
419 asm volatile("or r27,r27,r27" ::: "memory");
420}
421#else
422inline void cpuRelax() {
423 asm volatile("or 27,27,27" ::: "memory");
424}
425#endif // APPLE
426#else
427// TODO: provide reasonable relax on other archs.
428inline void cpuRelax() {}
429#endif // ARCH
430
431// When statically chunking a range, it is generally not possible to use a single chunk size plus
432// remainder and get a good load distribution. By estimating too high, we can have idle threads. By
433// estimating too low, the remainder can be several times as large as the chunk for other threads.
434// Instead, we compute the chunk size that is the ceil of the fractional chunk size. That can be
435// used for the first transitionIndex values, while the remaining (chunks - transitionTaskIndex)
436// values will be ceilChunkSize - 1.
437struct StaticChunking {
438 ssize_t transitionTaskIndex;
439 ssize_t ceilChunkSize;
440};
441
442inline StaticChunking staticChunkSize(ssize_t items, ssize_t chunks) {
443 assert(chunks > 0);
444 StaticChunking chunking;
445 chunking.ceilChunkSize = (items + chunks - 1) / chunks;
446 ssize_t numLeft = chunking.ceilChunkSize * chunks - items;
447 chunking.transitionTaskIndex = chunks - numLeft;
448 return chunking;
449}
450
451// Granularity-aware variant: ceilChunkSize is rounded UP to a multiple of
452// `granularity`, so each "ceil" chunk is granularity-aligned. The "floor"
453// chunks (those at index >= transitionTaskIndex) are ceilChunkSize - granularity,
454// also granularity-aligned. Caller must have already trimmed `items` to a
455// multiple of `granularity` so that all chunks (not just intermediate ones)
456// are granularity-multiples.
457inline StaticChunking staticChunkSizeGranular(ssize_t items, ssize_t chunks, uint32_t granularity) {
458 assert(chunks > 0);
459 assert(granularity >= 1);
460 if (granularity <= 1) {
461 return staticChunkSize(items, chunks);
462 }
463 assert(items % static_cast<ssize_t>(granularity) == 0);
464 StaticChunking chunking;
465 // Items measured in "granularity units".
466 ssize_t gUnits = items / static_cast<ssize_t>(granularity);
467 ssize_t ceilG = (gUnits + chunks - 1) / chunks;
468 ssize_t numLeft = ceilG * chunks - gUnits;
469 chunking.ceilChunkSize = ceilG * static_cast<ssize_t>(granularity);
470 chunking.transitionTaskIndex = chunks - numLeft;
471 return chunking;
472}
473
474} // namespace detail
475} // namespace dispenso
constexpr size_t kCacheLineSize
A constant that defines a safe number of bytes+alignment to avoid false sharing.
Definition platform.h:141
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