Barriers of the groups of threads that share a system: the barrier deduced from the compilation target, the barrier of a single thread, and the resolution of the barrier argument of the solvers.
More...
|
|
#define | TDLS_DETAIL_GROUP_HOST |
| | The host.
|
| |
|
#define | TDLS_DETAIL_GROUP_NONE "On CPU, it needs std::atomic_ref, missing from this standard library (libc++ before 19)." |
| | No deduced barrier in this pass; the reason, appended to the diagnostic.
|
| |
| #define | TDLS_DETAIL_GROUP_EXPAND(...) |
| | Statement list expanded as is.
|
| |
| #define | TDLS_DETAIL_GROUP_SIDE(device, host) |
| | Picks the statement list of the side of the pass.
|
| |
|
#define | TDLS_DETAIL_GROUP_DISPATCH(device, host) |
| | No deduced barrier: the resolution of the barrier refuses the pass.
|
| |
| #define | TDLS_DETAIL_GROUP_INCOMPLETE |
| | Message of an incomplete group.
|
| |
| #define | TDLS_DETAIL_GROUP_NO_SYNC |
| | Message of tdls::NoSync with a group of several threads.
|
| |
|
| template<typename Sync > |
| constexpr bool | tdls::detail::is_auto_sync = std::is_same_v<std::remove_cvref_t<Sync>, AutoSync> |
| | Whether the barrier argument asks for the deduced barrier.
|
| |
| template<typename Sync > |
| constexpr bool | tdls::detail::is_no_sync = std::is_same_v<std::remove_cvref_t<Sync>, NoSync> |
| | Whether the barrier argument is the absence of barrier.
|
| |
| template<typename Sync , bool = is_auto_sync<Sync>> |
| constexpr bool | tdls::detail::nothrow_sync = noexcept(std::declval<Sync&>()()) |
| | Whether a call of the barrier is noexcept, the condition of the noexcept specification of every entry point. Spelled as a noexcept-expression rather than through std::is_nothrow_invocable_v, whose libstdc++ implementation evaluates the call inside a host function, which nvcc 12.0 rejects for a device lambda. The deduced barrier never throws.
|
| |
| template<typename Sync > |
| constexpr bool | tdls::detail::nothrow_sync< Sync, true > = true |
| | The deduced barrier never throws.
|
| |
| template<typename T > |
| constexpr bool | tdls::detail::group_barrier_available = false |
| | Whether the compilation pass offers the deduced barrier to a group of several threads. Otherwise TDLS_DETAIL_GROUP_NONE holds the reason, appended to the diagnostic.
|
| |
|
constexpr int | tdls::detail::barrier_elements = 2 |
| | Elements at the start of the workspace that hold the deduced CPU barrier: a counter and a flag. They keep their place whatever the dimension and the configuration, so that the barrier state survives a workspace reused by other solves; the solves work on the rest.
|
| |
|
constexpr int | tdls::detail::group_lanes = 0 |
| | Largest group the warp of the pass can hold, checked at compile time when the size of the group is (0: no compile-time bound). The one pass of nvc++ serves the GPU as well, so the bound of its warp holds.
|
| |
Barriers of the groups of threads that share a system: the barrier deduced from the compilation target, the barrier of a single thread, and the resolution of the barrier argument of the solvers.
- Author
- Tristan Chenaille
- Copyright
- Copyright (C) 2026 CEA. All rights reserved. This project is publicly released under the BSD 3-Clause License (see the LICENSE file). CEA may also distribute it under specific licensing conditions.
A cooperative solver takes the barrier of its group as its last argument, sync. Three kinds of value are accepted:
- tdls::AutoSync, the default: the solver deduces the barrier from the target the code is compiled for, as described below, so most calls pass nothing;
- tdls::NoSync: no barrier at all, valid only for a group of one thread, a contract the compile-time solver checks at compile time and the runtime solver at run time;
- any other callable taking no argument: the barrier of the caller, used as is, without any check and at no extra cost.
The deduced barrier. With one thread per system it does nothing, and the call stays a constant expression. With several threads, the group is identified by its workspace, which every thread of the group passes and no other group shares:
- on a GPU, the threads of a group are lanes of one warp (NVIDIA) or one wavefront (AMD). On entry, the lanes that run together match the address of their workspace, and the group must find all its threads among them. An incomplete group stops the program with a message (under SYCL, without one), before any data is exchanged, and so does a workspace in the private memory of each thread: neither yields a wrong result or a deadlock. The barrier then involves exactly the lanes of the group.
- on a CPU, the threads of a group are any threads running concurrently: OpenMP, std::thread, Kokkos team threads and so on. The barrier is a counter and a flag held in the first two elements of the workspace, which must be zero before the first call; the barrier then leaves the counter at zero and the flag at 0 or 1 between calls. A group waiting for more than a second prints a diagnostic once and keeps waiting: a thread may be late for a legitimate reason, so the wait is never cut short.
Targets. The target is a property of the compilation pass, decided by the preprocessor in one place below:
- NVIDIA through the CUDA API: the device pass of nvcc and of clang in CUDA mode, and the device side of nvc++ with -cuda or -stdpar=gpu (nvc++ compiles host and device in one pass and picks the side with NV_IF_TARGET);
- NVIDIA through the clang builtins: the other clang device passes for NVPTX, OpenMP offloading and SYCL;
- AMD through the clang builtins: the device passes of HIP, of OpenMP offloading and of SYCL, the builtins HIP itself uses, from ROCm 5.3 on;
- the host, for every other pass, through std::atomic_ref (libc++ has it from 19 on). A few passes offer no warp primitive callable without a handle passed by the kernel: nvc++ OpenMP or OpenACC offloading without -cuda, its OpenACC backend of the parallel algorithms, the generic mode of AdaptiveCpp, GCC offloading and NVIDIA GPUs before Volta. SPIR-V devices, through SYCL or OpenMP offloading, have the primitives, but no standard way to stop a kernel that finds its group incomplete, so the check could not keep its promise there. A host pass whose standard library lacks std::atomic_ref has none either. In all these passes the deduced barrier accepts only a group of one thread known at compile time, and the compiler asks for an explicit barrier otherwise.
When to pass a barrier. Never for speed: the deduced barrier is the one a caller would write by hand, and make_sync lets a caller run its check on entry once for several calls. A barrier is needed in two cases only:
- the compiler asks for it: on the passes listed above, and on CPU for a scalar type without lock-free atomics, such as long double with GCC and Clang on x86-64. There the compiler refuses the deduced barrier even for the runtime solver with one thread per system, which then takes tdls::NoSync;
- the group is not one the deduced barrier knows: a group larger than a warp, which the compiler refuses for the compile-time solver and the runtime solver stops on entry, or threads that a runtime runs one after the other, such as the work-items of a SYCL kernel on CPU, where the deduced barrier waits and warns after a second. The barrier of the model then serves: a block, work-group or team barrier.
What is checked. Every misuse the solver can tell from a correct use is reported, at compile time when possible:
- at compile time: a pass without the deduced barrier, tdls::NoSync for a group of several threads of the compile-time solver, a group of the compile-time solver larger than a warp, a CPU scalar type without lock-free atomics;
- on GPU, with the deduced barrier: an incomplete group and a private workspace stop the program with a message (under SYCL, without one);
- on CPU, with the deduced barrier: a workspace whose barrier elements hold no barrier state stops the program with a message, and a group that waits for more than a second prints one;
- tdls::NoSync with the runtime solver on a dimension above rows_per_thread stops the program with a message, wherever the pass can stop it: everywhere but on SPIR-V devices, in the generic mode of AdaptiveCpp, in nvc++ offloading without -cuda, in its OpenACC backend of the parallel algorithms and in GCC offloading. Three misuses stay silent, since nothing tells them from a correct use: an explicit barrier that does not synchronize the group, two groups sharing one workspace (except on GPU when their lanes share a warp), and on CPU barrier elements left with values that look like a barrier state.
The GPU checks follow the warp synchronous primitives of each vendor: __match_any_sync on the active lanes, __syncwarp on the lanes of the group, and on AMD the loop of ballots behind the __match_any of HIP. AMD executes the lanes of a wavefront in lockstep, so its barrier is a wavefront fence around a scheduling barrier, the __syncwarp of HIP itself. On NVIDIA, sm_70 or newer is required.