yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Darren WihandiAdd full support for SPV_NV_shader_subgroup_partitioned (#7103)0476b57fa

master
17.0 KiB423 linesraw

Wave Intrinsics

Slang has support for Wave intrinsics introduced to HLSL in SM6.0 and SM6.5. All intrinsics are available on D3D12 and Vulkan.

On GLSL targets such as Vulkan wave intrinsics map to ['subgroup' extension] (https://github.com/KhronosGroup/GLSL/blob/master/extensions/khr/GL_KHR_shader_subgroup.txt). Vulkan supports a number of masked wave operations through SPV_NV_shader_subgroup_partitioned that are not supported by HLSL.

There is no subgroup support for Matrix types, and currently this means that Matrix is not a supported type for Wave intrinsics on Vulkan, but may be in the future.

Also introduced are some 'non standard' Wave intrinsics which are only available on Slang. All WaveMask intrinsics are non standard. Other non standard intrinsics expose more accurately different behaviours which are either not distinguished on HLSL, or perhaps currently unavailable. Two examples would be WaveShuffle and WaveBroadcastLaneAt.

There are three styles of wave intrinsics...

WaveActive

The majority of 'regular' HLSL Wave intrinsics which operate on implicit 'active' lanes.

In the DXC Wiki active lanes are described as

These intrinsics are dependent on active lanes and therefore flow control. In the model of this document, implementations must enforce that the number of active lanes exactly corresponds to the programmer’s view of flow control.

In practice this appears to imply that the programming model is that all lanes operate in 'lock step'. That the 'active lanes' are the lanes doing processing at a particular point in the control flow. On some hardware this may match how processing actually works. There is also a large amount of hardware in the field that doesn't follow this model, and allows lanes to diverge and not necessarily on flow control. On this style of hardware Active intrinsics may act to also converge lanes to give the appearance of 'in step' ness.

WaveMask

The WaveMask intrinsics take an explicit mask of lanes to operate on, in the same vein as CUDA. Requesting data from a from an inactive lane, can lead to undefined behavior, that includes locking up the shader. The WaveMask is an integer type that can hold the maximum amount of active lanes for this model - currently 32. In the future the WaveMask type may be made an opaque type, but can largely be operated on as if it is an integer.

Using WaveMask intrinsics is generally more verbose and prone to error than the 'Active' style, but it does have a few advantages

  • It works across all supported targets - including CUDA (currently WaveActive intrinics do not)
  • Gives more fine control
  • Might allow for higher performance (for example it gives more control of divergence)
  • Maps most closely to CUDA

For this to work across targets including CUDA, the mask must be calculated such that it exactly matches that of HLSL defined 'active' lanes, else the behavior is undefined.

On D3D12 and Vulkan the WaveMask intrinsics can be used, but the mask may be ignored depending on target's support for partitioned/masked wave intrinsics. SPIRV provides support for a wide variety of operations through the SPV_NV_shader_subgroup_partitioned extension while HLSL only provides a small subset of operations through WaveMultiPrefix* intrinsics. The difference between Slang's WaveMask and these targets' partitioned wave intrinsics is that they accept a uint4 mask instead of a uint mask. WaveMask* intrinsics effectively gets translated to WaveMulti* intrinsics when targeting SPIRV/GLSL and HLSL. Please consult Wave Multi Intrinsics for more details, including what masked operations are supported by each target.

The WaveMask intrinsics are a non standard Slang feature, and may change in the future.

RWStructuredBuffer<int> outputBuffer;

[numthreads(4, 1, 1)]
void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
{
    // It is the programmers responsibility to determine the initial mask, and that is dependent on the launch
    // It's common to launch such that all lanes are active - with CUDA this would mean 32 lanes. 
    // Here the launch only has 4 lanes active, and so the initial mask is 0xf.
    const WaveMask mask0 = 0xf;
    
    int idx = int(dispatchThreadID.x);
    
    int value = 0;
    
    // When there is a conditional/flow control we typically need to work out a new mask.
    // This can be achieved by calling WaveMaskBallot with the current mask, and the condition
    // used in the flow control - here the subsequent 'if'.
    const WaveMask mask1 = WaveMaskBallot(mask0, idx == 2);
    
    if (idx == 2)
    {
        // At this point the mask is `mask1`, although no WaveMask intrinsics are used along this path, 
        // so it's not used.
    
        // diverge
        return;
    }
    
    // If we get here, the active lanes must be the opposite of mask1 (because we took the other side of the condition), but cannot include
    // any lanes which were not active before. We can calculate this as mask0 & ~mask1.
    
    const WaveMask mask2 = mask0 & ~mask1;
    
    // mask2 holds the correct active mask to use with WaveMaskMin
    value = WaveMaskMin(mask2, idx + 1);
    
    // Write out the result
    outputBuffer[idx] = value;
}

Many of the nuances of writing code in this way are discussed in the CUDA documentation.

The above example written via the regular intrinsics is significantly simpler, as we do not need to track 'active lanes' in the masks.

RWStructuredBuffer<int> outputBuffer;

[numthreads(4, 1, 1)]
void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
{
    int idx = int(dispatchThreadID.x);
    
    int value = 0;
    
    if (idx == 2)
    {    
        // diverge
        return;
    }
    
    value = WaveActiveMin(idx + 1);
    
    // Write out the result
    outputBuffer[idx] = value;
}

WaveMulti

The standard 'Multi' intrinsics were added to HLSL is SM 6.5 and are available in SPIRV through SPV_NV_shader_subgroup_partitioned, they can specify a mask of lanes via uint4. SPIRV provide non-prefix (reduction) and prefix (scan) intrinsics for arithmetic and min/max operations, while HLSL only provides a subset of these, namely exclusive prefix arithmetic operations.

Standard Wave intrinsics

The Wave Intrinsics supported on Slang are listed below. Note that typically T generic types also include vector and matrix forms.

// Lane info

uint WaveGetLaneCount();

uint WaveGetLaneIndex();

bool WaveIsFirstLane();

// Ballot

bool WaveActiveAllTrue(bool condition);

bool WaveActiveAnyTrue(bool condition);

uint4 WaveActiveBallot(bool condition);

uint WaveActiveCountBits(bool value);

// Across Lanes

T WaveActiveBitAnd<T>(T expr);

T WaveActiveBitOr<T>(T expr);

T WaveActiveBitXor<T>(T expr);

T WaveActiveMax<T>(T expr);

T WaveActiveMin<T>(T expr);

T WaveActiveProduct<T>(T expr);

T WaveActiveSum<T>(T expr);

bool WaveActiveAllEqual<T>(T value);

// Prefix

T WavePrefixProduct<T>(T expr);

T WavePrefixSum<T>(T expr);

// Communication

T WaveReadLaneFirst<T>(T expr);

T WaveReadLaneAt<T>(T value, int lane);

// Prefix

uint WavePrefixCountBits(bool value);

// Shader model 6.5 stuff
// https://github.com/microsoft/DirectX-Specs/blob/master/d3d/HLSL_ShaderModel6_5.md

uint4 WaveMatch<T>(T value);

uint WaveMultiPrefixCountBits(bool value, uint4 mask);

T WaveMultiPrefixBitAnd<T>(T expr, uint4 mask);

T WaveMultiPrefixBitOr<T>(T expr, uint4 mask);

T WaveMultiPrefixBitXor<T>(T expr, uint4 mask);

T WaveMultiPrefixProduct<T>(T value, uint4 mask);

T WaveMultiPrefixSum<T>(T value, uint4 mask);

Non Standard Wave Intrinsics

The following intrinsics are not part of the HLSL Wave intrinsics standard, but were added to Slang for a variety of reasons. Within the following signatures T can be scalar, vector or matrix, except on Vulkan which doesn't (currently) support Matrix.

T WaveBroadcastLaneAt<T>(T value, constexpr int lane);

T WaveShuffle<T>(T value, int lane);

uint4 WaveGetActiveMulti();

uint4 WaveGetConvergedMulti();

// Barriers 

void AllMemoryBarrierWithWaveSync();

void GroupMemoryBarrierWithWaveSync();

Description

T WaveBroadcastLaneAt<T>(T value, constexpr int lane);

All lanes receive the value specified in lane. Lane must be an active lane, otherwise the result is undefined. This is a more restrictive version of WaveReadLaneAt - which can take a non constexpr lane, but must be the same value for all lanes in the warp. Or 'dynamically uniform' as described in the HLSL documentation.

T WaveShuffle<T>(T value, int lane);

Shuffle is a less restrictive version of WaveReadLaneAt in that it has no restriction on the lane value - it does not require the value to be same on all lanes.

There isn't explicit support for WaveShuffle in HLSL, and for now it will emit WaveReadLaneAt. As it turns out for a sizable set of hardware WaveReadLaneAt does work correctly when the lane is not 'dynamically uniform'. This is not necessarily the case for hardware general though, so if targeting HLSL it is important to make sure that this does work correctly on your target hardware.

Our intention is that Slang will support the appropriate HLSL mechanism that makes this work on all hardware when it's available.

void AllMemoryBarrierWithWaveSync();

Synchronizes all lanes to the same AllMemoryBarrierWithWaveSync in program flow. Orders all memory accesses such that accesses after the barrier can be seen by writes before.

void GroupMemoryBarrierWithWaveSync();

Synchronizes all lanes to the same GroupMemoryBarrierWithWaveSync in program flow. Orders group shared memory accesses such that accesses after the barrier can be seen by writes before.

Wave Rotate Intrinsics

These intrinsics are specific to Slang and were added to support the subgroup rotate functionalities provided by SPIRV (through the GroupNonUniformRotateKHR capability), GLSL (through the GL_KHR_shader_subgroup_rotate extension), and Metal.

// Supported on SPIRV, GLSL, and Metal targets.
T WaveRotate(T value, uint delta);

// Supported on SPIRV and GLSL targets.
T WaveClusteredRotate(T value, uint delta, constexpr uint clusterSize);

Wave Multi Intrinsics

WaveMulti intrinsics take an explicit uint4 mask of lanes to operate on. They correspond to the subgroup partitioned intrinsics provided by SPV_NV_shader_subgroup_partitioned and the WaveMultiPrefix* intrinsics provided by HLSL SM 6.5. HLSL's WaveMulti* intrinsics only provide operations for exclusive prefix arithmetic operations, while Vulkan's SPV_NV_shader_subgroup_partitioned provides operations for both inclusive/exclusive prefix (scan) and non-prefix (reduction) arithmetic and min/max operations.

Slang adds new WaveMulti* intrinsics in addition to HLSL's WaveMultiPrefix* to allow generating all partitioned intrinsics supported in SPIRV. The new, non-standard HLSL, WaveMulti* intrinsics are only supported when targeting SPIRV, GLSL and CUDA. The inclusive variants of HLSL's WaveMultiPrefix* intrinsics are emulated by Slang by performing an additional operation in the current invocation. Metal and WGSL targets do not support WaveMulti intrinsics.

// Across lane ops. These are only supported when targeting SPIRV, GLSL and CUDA.

T WaveMultiSum(T value, uint4 mask);

T WaveMultiProduct(T value, uint4 mask);

T WaveMultiMin(T value, uint4 mask);

T WaveMultiMax(T value, uint4 mask);

T WaveMultiBitAnd(T value, uint4 mask);

T WaveMultiBitOr(T value, uint4 mask);

T WaveMultiBitXor(T value, uint4 mask);


// Prefix arithmetic operations. Supported when targeting SPIRV, GLSL, CUDA and HLSL.
// In addition to these non-HLSL standard intrinsics are the standard `WaveMultiPrefix*`
// intrinsics provided by SM 6.5, detailed in the `Standard Wave Intrinsics` section.

T WaveMultiPrefixInclusiveSum(T value, uint4 mask);

T WaveMultiPrefixInclusiveProduct(T value, uint4 mask);

T WaveMultiPrefixInclusiveBitAnd(T value, uint4 mask);

T WaveMultiPrefixInclusiveBitOr(T value, uint4 mask);

T WaveMultiPrefixInclusiveBitXor(T value, uint4 mask);

T WaveMultiPrefixExclusiveSum(T value, uint4 mask);

T WaveMultiPrefixExclusiveProduct(T value, uint4 mask);

T WaveMultiPrefixExclusiveBitAnd(T value, uint4 mask);

T WaveMultiPrefixExclusiveBitOr(T value, uint4 mask);

T WaveMultiPrefixExclusiveBitXor(T value, uint4 mask);


// Prefix min/max operations. Supported when targeting SPIRV and GLSL.

T WaveMultiPrefixInclusiveMin(T value, uint4 mask);

T WaveMultiPrefixInclusiveMax(T value, uint4 mask);

T WaveMultiPrefixExclusiveMin(T value, uint4 mask);

T WaveMultiPrefixExclusiveMax(T value, uint4 mask);

Wave Mask Intrinsics

CUDA has a different programming model for inter warp/wave communication based around masks of active lanes. This is because the CUDA programming model allows for divergence that is more granualar than just on program flow, and that there isn't implied reconvergence at the end of a conditional.

In the future Slang may have the capability to work out the masks required such that the regular HLSL Wave intrinsics work. As it stands there does not appear to be any way to implement the regular Wave intrinsics directly. To work around this problem we introduce 'WaveMask' intrinsics, which are essentially the same as the regular HLSL Wave intrinsics with the first parameter as the WaveMask which identifies the participating lanes.

The WaveMask intrinsics will work across targets, but only if on CUDA targets the mask captures exactly the same lanes as the 'Active' lanes concept in HLSL. If the masks deviate then the behavior is undefined. On non CUDA based targets currently the mask may be ignored depending on the intrinsics supported by the target.

Most of the WaveMask functions are identical to the regular Wave intrinsics, but they take a WaveMask as the first parameter, and the intrinsic name starts with WaveMask. Also note that the WaveMask functions are introduced in Slang before the WaveMulti intrinsics, and they effectively function the same other than the mask width in bits (uint vs uint4). The WaveMulti intrinsics map closer to SPIRV and HLSL, and are recommended to be used over WaveMask intrinsics whenever possible. We plan to deprecate the WaveMask intrinsics some time in the future.

WaveMask WaveGetConvergedMask();

Gets the mask of lanes which are converged within the Wave. Note that this is not the same as Active threads, and may be some subset of that. It is equivalent to the __activemask() in CUDA.

On non CUDA targets the the function will return all lanes as active - even though this is not the case. This is 'ok' in so far as on non CUDA targets the mask is ignored. It is not okay if the code uses the value other than as a superset of the 'really converged' lanes. For example testing the bit's and changing behavior would likely not work correctly on non CUDA targets.

void AllMemoryBarrierWithWaveMaskSync(WaveMask mask);

Same as AllMemoryBarrierWithWaveSync but takes a mask of active lanes to sync with.

void GroupMemoryBarrierWithWaveMaskSync(WaveMask mask);

Same as GroupMemoryBarrierWithWaveSync but takes a mask of active lanes to sync with.

The intrinsics that make up the Slang WaveMask extension.

// Lane info

WaveMask WaveGetConvergedMask();

WaveMask WaveGetActiveMask();

bool WaveMaskIsFirstLane(WaveMask mask);

// Ballot

bool WaveMaskAllTrue(WaveMask mask, bool condition);

bool WaveMaskAnyTrue(WaveMask mask, bool condition);

WaveMask WaveMaskBallot(WaveMask mask, bool condition);

WaveMask WaveMaskCountBits(WaveMask mask, bool value);

WaveMask WaveMaskMatch<T>(WaveMask mask, T value);

// Barriers

void AllMemoryBarrierWithWaveMaskSync(WaveMask mask);

void GroupMemoryBarrierWithWaveMaskSync(WaveMask mask);

// Across lane ops

T WaveMaskBitAnd<T>(WaveMask mask, T expr);

T WaveMaskBitOr<T>(WaveMask mask, T expr);

T WaveMaskBitXor<T>(WaveMask mask, T expr);

T WaveMaskMax<T>(WaveMask mask, T expr);

T WaveMaskMin<T>(WaveMask mask, T expr);

T WaveMaskProduct<T>(WaveMask mask, T expr);

T WaveMaskSum<T>(WaveMask mask, T expr);

bool WaveMaskAllEqual<T>(WaveMask mask, T value);

// Prefix

T WaveMaskPrefixProduct<T>(WaveMask mask, T expr);

T WaveMaskPrefixSum<T>(WaveMask mask, T expr);

T WaveMaskPrefixBitAnd<T>(WaveMask mask, T expr);

T WaveMaskPrefixBitOr<T>(WaveMask mask, T expr);

T WaveMaskPrefixBitXor<T>(WaveMask mask, T expr);

uint WaveMaskPrefixCountBits(WaveMask mask, bool value);

// Communication

T WaveMaskReadLaneFirst<T>(WaveMask mask, T expr);

T WaveMaskBroadcastLaneAt<T>(WaveMask mask, T value, constexpr int lane);

T WaveMaskReadLaneAt<T>(WaveMask mask, T value, int lane);

T WaveMaskShuffle<T>(WaveMask mask, T value, int lane);
1
2Wave Intrinsics
3===============
4
5Slang has support for Wave intrinsics introduced to HLSL in [SM6.0](https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/hlsl-shader-model-6-0-features-for-direct3d-12) and [SM6.5](https://github.com/microsoft/DirectX-Specs/blob/master/d3d/HLSL_ShaderModel6_5.md). All intrinsics are available on D3D12 and Vulkan.
6
7On GLSL targets such as Vulkan wave intrinsics map to ['subgroup' extension] (https://github.com/KhronosGroup/GLSL/blob/master/extensions/khr/GL_KHR_shader_subgroup.txt).  Vulkan supports a number of masked wave operations through `SPV_NV_shader_subgroup_partitioned` that are not supported by HLSL.
8
9There is no subgroup support for Matrix types, and currently this means that Matrix is not a supported type for Wave intrinsics on Vulkan, but may be in the future.
10
11
12Also introduced are some 'non standard' Wave intrinsics which are only available on Slang. All WaveMask intrinsics are non standard. Other non standard intrinsics expose more accurately different behaviours which are either not distinguished on HLSL, or perhaps currently unavailable. Two examples would be `WaveShuffle` and `WaveBroadcastLaneAt`. 
13
14There are three styles of wave intrinsics...
15
16## WaveActive
17
18The majority of 'regular' HLSL Wave intrinsics which operate on implicit 'active' lanes. 
19
20In the [DXC Wiki](https://github.com/Microsoft/DirectXShaderCompiler/wiki/Wave-Intrinsics) active lanes are described as
21
22> These intrinsics are dependent on active lanes and therefore flow control. In the model of this document, implementations
23> must enforce that the number of active lanes exactly corresponds to the programmer’s view of flow control.
24 
25In practice this appears to imply that the programming model is that all lanes operate in 'lock step'. That the 'active lanes' are the lanes doing processing at a particular point in the control flow. On some hardware this may match how processing actually works. There is also a large amount of hardware in the field that doesn't follow this model, and allows lanes to diverge and not necessarily on flow control. On this style of hardware Active intrinsics may act to also converge lanes to give the appearance of 'in step' ness. 
26 
27## WaveMask
28
29The WaveMask intrinsics take an explicit mask of lanes to operate on, in the same vein as CUDA. Requesting data from a from an inactive lane, can lead to undefined behavior, that includes locking up the shader. The WaveMask is an integer type that can hold the maximum amount of active lanes for this model - currently 32. In the future the WaveMask type may be made an opaque type, but can largely be operated on as if it is an integer.
30
31Using WaveMask intrinsics is generally more verbose and prone to error than the 'Active' style, but it does have a few advantages
32
33* It works across all supported targets - including CUDA (currently WaveActive intrinics do not)
34* Gives more fine control
35* Might allow for higher performance (for example it gives more control of divergence)
36* Maps most closely to CUDA
37
38For this to work across targets including CUDA, the mask must be calculated such that it exactly matches that of HLSL defined 'active' lanes, else the behavior is undefined.
39
40On D3D12 and Vulkan the WaveMask intrinsics can be used, but the mask may be ignored depending on target's support for partitioned/masked wave intrinsics. SPIRV provides support for a wide variety of operations through the `SPV_NV_shader_subgroup_partitioned` extension while HLSL only provides a small subset of operations through `WaveMultiPrefix*` intrinsics. The difference between Slang's `WaveMask`  and these targets' partitioned wave intrinsics is that they accept a `uint4` mask instead of a `uint` mask. `WaveMask*` intrinsics effectively gets translated  to `WaveMulti*` intrinsics when targeting SPIRV/GLSL and HLSL. Please consult [Wave Multi Intrinsics](#wave-multi-intrinsics) for more details, including what masked operations are supported by each target.
41
42
43The WaveMask intrinsics are a non standard Slang feature, and may change in the future. 
44
45```
46RWStructuredBuffer<int> outputBuffer;
47
48[numthreads(4, 1, 1)]
49void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
50{
51    // It is the programmers responsibility to determine the initial mask, and that is dependent on the launch
52    // It's common to launch such that all lanes are active - with CUDA this would mean 32 lanes. 
53    // Here the launch only has 4 lanes active, and so the initial mask is 0xf.
54    const WaveMask mask0 = 0xf;
55    
56    int idx = int(dispatchThreadID.x);
57    
58    int value = 0;
59    
60    // When there is a conditional/flow control we typically need to work out a new mask.
61    // This can be achieved by calling WaveMaskBallot with the current mask, and the condition
62    // used in the flow control - here the subsequent 'if'.
63    const WaveMask mask1 = WaveMaskBallot(mask0, idx == 2);
64    
65    if (idx == 2)
66    {
67        // At this point the mask is `mask1`, although no WaveMask intrinsics are used along this path, 
68        // so it's not used.
69    
70        // diverge
71        return;
72    }
73    
74    // If we get here, the active lanes must be the opposite of mask1 (because we took the other side of the condition), but cannot include
75    // any lanes which were not active before. We can calculate this as mask0 & ~mask1.
76    
77    const WaveMask mask2 = mask0 & ~mask1;
78    
79    // mask2 holds the correct active mask to use with WaveMaskMin
80    value = WaveMaskMin(mask2, idx + 1);
81    
82    // Write out the result
83    outputBuffer[idx] = value;
84}
85```
86
87Many of the nuances of writing code in this way are discussed in the [CUDA documentation](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#warp-vote-functions).
88
89The above example written via the regular intrinsics is significantly simpler, as we do not need to track 'active lanes' in the masks. 
90
91```
92RWStructuredBuffer<int> outputBuffer;
93
94[numthreads(4, 1, 1)]
95void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
96{
97    int idx = int(dispatchThreadID.x);
98    
99    int value = 0;
100    
101    if (idx == 2)
102    {    
103        // diverge
104        return;
105    }
106    
107    value = WaveActiveMin(idx + 1);
108    
109    // Write out the result
110    outputBuffer[idx] = value;
111}
112```
113## WaveMulti
114
115The standard 'Multi' intrinsics were added to HLSL is SM 6.5 and are available in SPIRV through `SPV_NV_shader_subgroup_partitioned`, they can specify a mask of lanes via uint4. SPIRV provide non-prefix (reduction) and prefix (scan) intrinsics for arithmetic and min/max operations, while HLSL only provides a subset of these, namely exclusive prefix arithmetic operations.
116
117
118Standard Wave intrinsics
119=========================
120
121The Wave Intrinsics supported on Slang are listed below. Note that typically T generic types also include vector and matrix forms. 
122
123```
124// Lane info
125
126uint WaveGetLaneCount();
127
128uint WaveGetLaneIndex();
129
130bool WaveIsFirstLane();
131
132// Ballot
133
134bool WaveActiveAllTrue(bool condition);
135
136bool WaveActiveAnyTrue(bool condition);
137
138uint4 WaveActiveBallot(bool condition);
139
140uint WaveActiveCountBits(bool value);
141
142// Across Lanes
143
144T WaveActiveBitAnd<T>(T expr);
145
146T WaveActiveBitOr<T>(T expr);
147
148T WaveActiveBitXor<T>(T expr);
149
150T WaveActiveMax<T>(T expr);
151
152T WaveActiveMin<T>(T expr);
153
154T WaveActiveProduct<T>(T expr);
155
156T WaveActiveSum<T>(T expr);
157
158bool WaveActiveAllEqual<T>(T value);
159
160// Prefix
161
162T WavePrefixProduct<T>(T expr);
163
164T WavePrefixSum<T>(T expr);
165
166// Communication
167
168T WaveReadLaneFirst<T>(T expr);
169
170T WaveReadLaneAt<T>(T value, int lane);
171
172// Prefix
173
174uint WavePrefixCountBits(bool value);
175
176// Shader model 6.5 stuff
177// https://github.com/microsoft/DirectX-Specs/blob/master/d3d/HLSL_ShaderModel6_5.md
178
179uint4 WaveMatch<T>(T value);
180
181uint WaveMultiPrefixCountBits(bool value, uint4 mask);
182
183T WaveMultiPrefixBitAnd<T>(T expr, uint4 mask);
184
185T WaveMultiPrefixBitOr<T>(T expr, uint4 mask);
186
187T WaveMultiPrefixBitXor<T>(T expr, uint4 mask);
188
189T WaveMultiPrefixProduct<T>(T value, uint4 mask);
190
191T WaveMultiPrefixSum<T>(T value, uint4 mask);
192```
193
194Non Standard Wave Intrinsics
195============================
196
197The following intrinsics are not part of the HLSL Wave intrinsics standard, but were added to Slang for a variety of reasons. Within the following signatures T can be scalar, vector or matrix, except on Vulkan which doesn't (currently) support Matrix.
198
199```
200T WaveBroadcastLaneAt<T>(T value, constexpr int lane);
201
202T WaveShuffle<T>(T value, int lane);
203
204uint4 WaveGetActiveMulti();
205
206uint4 WaveGetConvergedMulti();
207
208// Barriers 
209
210void AllMemoryBarrierWithWaveSync();
211
212void GroupMemoryBarrierWithWaveSync();
213```
214
215## Description
216
217```
218T WaveBroadcastLaneAt<T>(T value, constexpr int lane);
219```
220
221All lanes receive the value specified in lane. Lane must be an active lane, otherwise the result is undefined. 
222This is a more restrictive version of `WaveReadLaneAt` - which can take a non constexpr lane, *but* must be the same value for all lanes in the warp. Or 'dynamically uniform' as described in the HLSL documentation.
223
224```
225T WaveShuffle<T>(T value, int lane);
226```
227
228Shuffle is a less restrictive version of `WaveReadLaneAt` in that it has no restriction on the lane value - it does *not* require the value to be same on all lanes. 
229
230There isn't explicit support for WaveShuffle in HLSL, and for now it will emit `WaveReadLaneAt`. As it turns out for a sizable set of hardware WaveReadLaneAt does work correctly when the lane is not 'dynamically uniform'. This is not necessarily the case for hardware general though, so if targeting HLSL it is important to make sure that this does work correctly on your target hardware.
231
232Our intention is that Slang will support the appropriate HLSL mechanism that makes this work on all hardware when it's available.  
233
234```
235void AllMemoryBarrierWithWaveSync();
236```
237
238Synchronizes all lanes to the same AllMemoryBarrierWithWaveSync in program flow. Orders all memory accesses such that accesses after the barrier can be seen by writes before.  
239
240```
241void GroupMemoryBarrierWithWaveSync();
242```
243
244Synchronizes all lanes to the same GroupMemoryBarrierWithWaveSync in program flow. Orders group shared memory accesses such that accesses after the barrier can be seen by writes before.  
245
246
247Wave Rotate Intrinsics
248======================
249
250These intrinsics are specific to Slang and were added to support the subgroup rotate functionalities provided by SPIRV (through the `GroupNonUniformRotateKHR` capability), GLSL (through the `GL_KHR_shader_subgroup_rotate
251` extension), and Metal.
252
253```
254// Supported on SPIRV, GLSL, and Metal targets.
255T WaveRotate(T value, uint delta);
256
257// Supported on SPIRV and GLSL targets.
258T WaveClusteredRotate(T value, uint delta, constexpr uint clusterSize);
259```
260
261Wave Multi Intrinsics
262======================
263
264`WaveMulti` intrinsics take an explicit  `uint4` mask of lanes to operate on. They correspond to the subgroup partitioned intrinsics provided by `SPV_NV_shader_subgroup_partitioned`  and the `WaveMultiPrefix*` intrinsics provided by HLSL SM 6.5.  HLSL's `WaveMulti*` intrinsics only provide operations for exclusive prefix arithmetic operations, while Vulkan's `SPV_NV_shader_subgroup_partitioned` provides operations for both inclusive/exclusive prefix (scan) and non-prefix (reduction) arithmetic and min/max operations. 
265
266Slang adds new `WaveMulti*` intrinsics in addition to HLSL's  `WaveMultiPrefix*` to allow generating all partitioned intrinsics supported in SPIRV. The new, non-standard HLSL,  `WaveMulti*` intrinsics are only supported when targeting SPIRV, GLSL and CUDA. The inclusive variants of HLSL's `WaveMultiPrefix*` intrinsics are emulated by Slang by performing an additional operation in the current invocation. Metal and WGSL targets do not support `WaveMulti` intrinsics.
267```
268// Across lane ops. These are only supported when targeting SPIRV, GLSL and CUDA.
269
270T WaveMultiSum(T value, uint4 mask);
271
272T WaveMultiProduct(T value, uint4 mask);
273
274T WaveMultiMin(T value, uint4 mask);
275
276T WaveMultiMax(T value, uint4 mask);
277
278T WaveMultiBitAnd(T value, uint4 mask);
279
280T WaveMultiBitOr(T value, uint4 mask);
281
282T WaveMultiBitXor(T value, uint4 mask);
283
284
285// Prefix arithmetic operations. Supported when targeting SPIRV, GLSL, CUDA and HLSL.
286// In addition to these non-HLSL standard intrinsics are the standard `WaveMultiPrefix*`
287// intrinsics provided by SM 6.5, detailed in the `Standard Wave Intrinsics` section.
288
289T WaveMultiPrefixInclusiveSum(T value, uint4 mask);
290
291T WaveMultiPrefixInclusiveProduct(T value, uint4 mask);
292
293T WaveMultiPrefixInclusiveBitAnd(T value, uint4 mask);
294
295T WaveMultiPrefixInclusiveBitOr(T value, uint4 mask);
296
297T WaveMultiPrefixInclusiveBitXor(T value, uint4 mask);
298
299T WaveMultiPrefixExclusiveSum(T value, uint4 mask);
300
301T WaveMultiPrefixExclusiveProduct(T value, uint4 mask);
302
303T WaveMultiPrefixExclusiveBitAnd(T value, uint4 mask);
304
305T WaveMultiPrefixExclusiveBitOr(T value, uint4 mask);
306
307T WaveMultiPrefixExclusiveBitXor(T value, uint4 mask);
308
309
310// Prefix min/max operations. Supported when targeting SPIRV and GLSL.
311
312T WaveMultiPrefixInclusiveMin(T value, uint4 mask);
313
314T WaveMultiPrefixInclusiveMax(T value, uint4 mask);
315
316T WaveMultiPrefixExclusiveMin(T value, uint4 mask);
317
318T WaveMultiPrefixExclusiveMax(T value, uint4 mask);
319```
320
321
322Wave Mask Intrinsics
323====================
324
325CUDA has a different programming model for inter warp/wave communication based around masks of active lanes. This is because the CUDA programming model allows for divergence that is more granualar than just on program flow, and that there isn't implied reconvergence at the end of a conditional. 
326
327In the future Slang may have the capability to work out the masks required such that the regular HLSL Wave intrinsics work. As it stands there does not appear to be any way to implement the regular Wave intrinsics directly. To work around this problem we introduce 'WaveMask' intrinsics, which are essentially the same as the regular HLSL Wave intrinsics with the first parameter as the WaveMask which identifies the participating lanes.
328
329The WaveMask intrinsics will work across targets, but *only* if on CUDA targets the mask captures exactly the same lanes as the 'Active' lanes concept in HLSL. If the masks deviate then the behavior is undefined. On non CUDA based targets currently the mask *may* be ignored depending on the intrinsics supported by the target.
330
331Most of the `WaveMask` functions are identical to the regular Wave intrinsics, but they take a WaveMask as the first parameter, and the intrinsic name starts with `WaveMask`. Also note that the `WaveMask` functions are introduced in Slang before the `WaveMulti` intrinsics, and they effectively function the same other than the mask width in bits (`uint` vs `uint4`). The `WaveMulti` intrinsics map closer to SPIRV and HLSL, and are recommended to be used over `WaveMask` intrinsics whenever possible. We plan to deprecate the `WaveMask` intrinsics some time in the future.
332
333```
334WaveMask WaveGetConvergedMask();
335```
336
337Gets the mask of lanes which are converged within the Wave. Note that this is *not* the same as Active threads, and may be some subset of that. It is equivalent to the `__activemask()` in CUDA.
338
339On non CUDA targets the the function will return all lanes as active - even though this is not the case. This is 'ok' in so far as on non CUDA targets the mask is ignored. It is *not* okay if the code uses the value other than as a superset of the 'really converged' lanes. For example testing the bit's and changing behavior would likely not work correctly on non CUDA targets. 
340
341```
342void AllMemoryBarrierWithWaveMaskSync(WaveMask mask);
343```
344
345Same as AllMemoryBarrierWithWaveSync but takes a mask of active lanes to sync with. 
346
347```
348void GroupMemoryBarrierWithWaveMaskSync(WaveMask mask);
349```
350
351Same as GroupMemoryBarrierWithWaveSync but takes a mask of active lanes to sync with. 
352 
353The intrinsics that make up the Slang `WaveMask` extension. 
354 
355```
356// Lane info
357
358WaveMask WaveGetConvergedMask();
359
360WaveMask WaveGetActiveMask();
361
362bool WaveMaskIsFirstLane(WaveMask mask);
363
364// Ballot
365
366bool WaveMaskAllTrue(WaveMask mask, bool condition);
367
368bool WaveMaskAnyTrue(WaveMask mask, bool condition);
369
370WaveMask WaveMaskBallot(WaveMask mask, bool condition);
371
372WaveMask WaveMaskCountBits(WaveMask mask, bool value);
373
374WaveMask WaveMaskMatch<T>(WaveMask mask, T value);
375
376// Barriers
377
378void AllMemoryBarrierWithWaveMaskSync(WaveMask mask);
379
380void GroupMemoryBarrierWithWaveMaskSync(WaveMask mask);
381
382// Across lane ops
383
384T WaveMaskBitAnd<T>(WaveMask mask, T expr);
385
386T WaveMaskBitOr<T>(WaveMask mask, T expr);
387
388T WaveMaskBitXor<T>(WaveMask mask, T expr);
389
390T WaveMaskMax<T>(WaveMask mask, T expr);
391
392T WaveMaskMin<T>(WaveMask mask, T expr);
393
394T WaveMaskProduct<T>(WaveMask mask, T expr);
395
396T WaveMaskSum<T>(WaveMask mask, T expr);
397
398bool WaveMaskAllEqual<T>(WaveMask mask, T value);
399
400// Prefix
401
402T WaveMaskPrefixProduct<T>(WaveMask mask, T expr);
403
404T WaveMaskPrefixSum<T>(WaveMask mask, T expr);
405
406T WaveMaskPrefixBitAnd<T>(WaveMask mask, T expr);
407
408T WaveMaskPrefixBitOr<T>(WaveMask mask, T expr);
409
410T WaveMaskPrefixBitXor<T>(WaveMask mask, T expr);
411
412uint WaveMaskPrefixCountBits(WaveMask mask, bool value);
413
414// Communication
415
416T WaveMaskReadLaneFirst<T>(WaveMask mask, T expr);
417
418T WaveMaskBroadcastLaneAt<T>(WaveMask mask, T value, constexpr int lane);
419
420T WaveMaskReadLaneAt<T>(WaveMask mask, T value, int lane);
421
422T WaveMaskShuffle<T>(WaveMask mask, T value, int lane);
423```