TDLS 0.4.0
Tiny Device-callable Linear Solvers
Loading...
Searching...
No Matches
group.hpp File Reference

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...

#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <thread>
#include <type_traits>
#include <utility>
#include <tdls/core/macros.hpp>
#include <tdls/core/target.hpp>
Include dependency graph for group.hpp:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Classes

struct  tdls::AutoSync
 Barrier argument of the cooperative solvers that asks them to deduce the barrier from the compilation target: the default, so most calls pass nothing. A barrier is needed only where the compiler asks for one, or for a group that is not the lanes of one warp on GPU nor threads running concurrently on CPU (see core/group.hpp, which also lists what the solvers check). More...
 
struct  tdls::NoSync
 Barrier that does nothing, valid only when one thread solves a system. The compile-time solver refuses it for a group of several threads at compile time. The runtime solver, whose group size depends on n, stops the program when n exceeds rows_per_thread, wherever the pass can stop it (see core/group.hpp). More...
 
class  tdls::detail::DeducedSync< T >
 The barrier deduced from the compilation target, for a group of several threads. A group of one thread, possible with the runtime solver, does nothing. More...
 

Macros

#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.
 

Enumerations

enum class  tdls::detail::LanesFailure { incomplete , no_sync }
 Failures that stop a kernel, each with its message. More...
 

Functions

void tdls::detail::host_refuse_no_sync () noexcept
 Stops the program: the runtime solver runs a group of several threads with tdls::NoSync.
 
TDLS_HOST_DEVICE TDLS_FORCE_INLINE_GPU void tdls::detail::refuse_no_sync () noexcept
 Stops the program: the runtime solver runs a group of several threads with tdls::NoSync. A pass that can stop neither a kernel nor the program, one without a side, lets it run.
 
template<int static_threads, typename Sync , typename T >
TDLS_HOST_DEVICE TDLS_FORCE_INLINE_GPU constexpr decltype(auto) tdls::detail::resolve_sync (Sync &sync, T *slots, const int threads) noexcept
 Resolves the barrier argument of an entry point: the caller's barrier as is, or the deduced barrier.
 

Variables

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.
 

Detailed Description

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

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.

Macro Definition Documentation

◆ TDLS_DETAIL_GROUP_EXPAND

#define TDLS_DETAIL_GROUP_EXPAND ( ...)
Value:
__VA_ARGS__

Statement list expanded as is.

◆ TDLS_DETAIL_GROUP_INCOMPLETE

#define TDLS_DETAIL_GROUP_INCOMPLETE
Value:
"TDLS: a group of threads is incomplete in its warp, or its workspace is private to each " \
"thread. The threads of a group must call the solver together, as lanes of one warp or " \
"wavefront, with a workspace in shared or global memory.\n"

Message of an incomplete group.

◆ TDLS_DETAIL_GROUP_NO_SYNC

#define TDLS_DETAIL_GROUP_NO_SYNC
Value:
"TDLS: the runtime solver runs with tdls::NoSync, valid for one thread per system, on a " \
"dimension above rows_per_thread. Pass a barrier, or a rows_per_thread reaching the " \
"dimension.\n"

Message of tdls::NoSync with a group of several threads.

◆ TDLS_DETAIL_GROUP_SIDE

#define TDLS_DETAIL_GROUP_SIDE ( device,
host )
Value:
#define TDLS_DETAIL_GROUP_EXPAND(...)
Statement list expanded as is.
Definition group.hpp:209

Picks the statement list of the side of the pass.

Enumeration Type Documentation

◆ LanesFailure

enum class tdls::detail::LanesFailure
strong

Failures that stop a kernel, each with its message.

Enumerator
incomplete 

a group incomplete in its warp, or a private workspace

no_sync 

tdls::NoSync with a group of several threads

Function Documentation

◆ resolve_sync()

template<int static_threads, typename Sync , typename T >
TDLS_HOST_DEVICE TDLS_FORCE_INLINE_GPU constexpr decltype(auto) tdls::detail::resolve_sync ( Sync & sync,
T * slots,
const int threads )
constexprnoexcept

Resolves the barrier argument of an entry point: the caller's barrier as is, or the deduced barrier.

tdls::NoSync is checked against the size of the group: at compile time when the size is known there, at run time otherwise. With a group size known at compile time, a group of one thread resolves the deduced barrier to tdls::NoSync and costs nothing. Otherwise the pass must offer the deduced barrier, and a GPU warp must be able to hold the group.

Template Parameters
static_threadssize of the group when known at compile time, 0 for the runtime solver
Synctype of the barrier argument
Tscalar type of the workspace
Parameters
[in]syncbarrier argument of the entry point
[in]slotsthe two barrier elements of the workspace
[in]threadssize of the group
Returns
a reference to sync, or the deduced barrier

Variable Documentation

◆ group_barrier_available

template<typename T >
bool tdls::detail::group_barrier_available = false
inlineconstexpr

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.

A template, so that the check of resolve_sync waits for a group of several threads: GCC before 13 and nvcc evaluate a static_assert that depends on no template parameter, even in a discarded branch.

Template Parameters
Tscalar type of the workspace

◆ is_auto_sync

template<typename Sync >
bool tdls::detail::is_auto_sync = std::is_same_v<std::remove_cvref_t<Sync>, AutoSync>
inlineconstexpr

Whether the barrier argument asks for the deduced barrier.

Template Parameters
Synctype of the barrier argument

◆ is_no_sync

template<typename Sync >
bool tdls::detail::is_no_sync = std::is_same_v<std::remove_cvref_t<Sync>, NoSync>
inlineconstexpr

Whether the barrier argument is the absence of barrier.

Template Parameters
Synctype of the barrier argument

◆ nothrow_sync

template<typename Sync , bool = is_auto_sync<Sync>>
bool tdls::detail::nothrow_sync = noexcept(std::declval<Sync&>()())
inlineconstexpr

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 Parameters
Synccallable type of the barrier

◆ nothrow_sync< Sync, true >

template<typename Sync >
bool tdls::detail::nothrow_sync< Sync, true > = true
inlineconstexpr

The deduced barrier never throws.

Template Parameters
Synctdls::AutoSync, possibly qualified