Skip to content

Atomics proposal #1360

@alan-baker

Description

@alan-baker

Atomic Type

Add a new type, atomic<T>, to WGSL. For MVP, T must be either u32 or i32. Atomics may only be placed in variables in the workgroup or storage storage classes. Atomics may only be operated on by atomic builtin functions.

Translation

In both HLSL and SPIR-V, atomic<T> should be translated the same as T. In MSL, atomic<T> should translate as volatile atomic_uint or volatile atomic_int.

Atomic Functions

Add the following builtin functions:

  • atomicAdd(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
  • atomicMax(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
  • atomicMin(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
  • atomicAnd(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
  • atomicOr(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
  • atomicXor(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T
  • atomicExchange(atomic_ptr : ptr<SC, atomic<T>>, value : T) -> T

In all the above cases, the return value is the original value in the atomic prior to the operation.

Also add compare exchange:

  • atomicCompareExchangeWeak(atomic_ptr : ptr<SC, atomic<T>>, expected : T, value : T) -> vec2<T>

Compare exchange returns a vec2 where the first element is the original value in the atomic and the second value is whether or not the comparison was successful (1 for success, 0 otherwise). This uses the weak version of compare exchange which means that the comparison can fail spuriously (i.e. even if expected matches the original value).

The memory ordering for all atomic functions is relaxed (see section 29.3 of the C++14 specification). This means that no synchronization or ordering guarantees are made on non-atomic memory accesses, nor on atomic memory accesses operating on different memory addresses (i.e. unless they are mutually ordered atomics).

For all atomic functions, the storage class of atomic_ptr must be workgroup or storage. Atomics on storage storage class are device atomics (e.g. Device memory scope in SPIR-V), while atomics in workgroup storage class are workgroup atomics (e.g. shared variable atomics in HLSL).

Atomic functions must not be used in vertex shaders.

Example:

[[block]] struct A {
  atomic<u32> a;
  array<u32> rta;
};

var<storage> myVar : A;

// ...

const old : u32 = atomicAdd(myVar.a, 1);

Translation

WGSL SPIR-V HLSL MSL GLES
atomicAdd OpAtomicIAdd InterlockedAdd atomic_fetch_add_explicit atomicAdd
atomicMax OpAtomic[S|U]Max1 InterlockedMax atomic_fetch_max_explicit atomicMax
atomicMin OpAtomic[S|U]Min1 InterlockedMin atomic_fetch_min_explicit atomicMin
atomicAnd OpAtomicAnd InterlockedAnd atomic_fetch_and_explicit atomicAnd
atomicOr OpAtomicOr InterlockedOr atomic_fetch_or_explicit atomicOr
atomicXor OpAtomicXor InterlockedXor atomic_fetch_xor_explicit atomicXor
atomicExchange OpAtomicExchange InterlockedExchange atomic_exchange_explicit atomicExchange
atomicCompareExchangeWeak OpAtomicCompareExchange2 InterlockedCompareExchange3 atomic_compare_exchange_weak_explicit atomicCompSwap
  1. Signedness is based on T.
  2. SPIR-V only provides the strong compare exchange.
  3. If used in a loop, the loop must be annotated with the attribute [allow_uav_condition]

Considerations

The new type favours translations from WGSL to other dialects (as opposed to translation to WGSL). MSL requires C++ style atomic types. If just i32 or u32 were used, then the declaration of the variable would need to be modified by its usage, which should generally be avoided. One implication of this choice is that not all HLSL or SPIR-V programs will be convertible to WGSL as those languages support atomic operations on regular memory addresses (no special types). One example is if the SPIR-V or HLSL atomic operates on 32-bit integer in a vector. Translating to WGSL (from SPIR-V and HLSL) has the problem avoided in avoid translating from WGSL to MSL (the use must modify the declaration).

Survey of Implementations

Metal (MSL)

Provides atomic_int and atomic_uint types, so 32-bit integer atomics only. Atomic functions only support memory_order_relaxed (no synchronization with other memory operations). Atomic functions are defined in <metal_atomic>. These functions are not limited by the shader stage.

Metal 1.0 supports the following functions for volatile threadgroup (workgroup) and device atomic memory objects:

  • Atomic_store_explicit
  • Atomic_load_explicit
  • Atomic_exchange_explicit
  • Atomic_compare_exchange_weak_explicit
  • Atomic_fetch_key_explicit
    • And, or, add, max, min, sub, xor

Signedness based on memory type
Signed versions use two’s complement with silent wrap around (i.e. same as SPIR-V)
Metal 2.0 added support for non-volatile versions of the atomic functions.

The biggest difference from SPIR-V is that atomic types are required. Atomic operations cannot be performed on any memory object. MSL also does not support atomic image operations (i.e. no atomics through OpImageTexelPointer).

DirectX (HLSL)

HLSL supports interlocked (atomic) functions on int and uint data types since Shader Model 5. HLSL also supports atomic exchanges on any scalar data type. There are no restrictions on shader stage for any of the functions. Atomics can be performed on shared (workgroup) or resource (storage and texture) variables. All atomic operations use relaxed memory ordering.

The following functions are supported:

  • InterlockedAdd
  • InterlockedAnd
  • InterlockedCompareExchange
    • When used in a loop, the loop must be annotated with [allow_uav_condition] (in compute shaders)
    • Unclear if this is a strong or weak compare exchange
  • InterlockedCompareStore
    • Could be mapped to compare exchange
  • InterlockedExchange
  • InterlockedMax
  • InterlockedMin
  • InterlockedOr
  • InterlockedXor

Vulkan (SPIR-V)

Vulkan supports 32-bit integer atomics in Vulkan 1.0. There are extensions to support 64-bit integer atomics and float/half/double atomics (load, store, exchange, min, max, add). Vulkan also supports integer atomic operations on texel components (via OpImageTexelPointer). The memory scope for atomics must be Workgroup or Device (QueueFamily). The storage class of pointers must be: Workgroup, StorageBuffer (and PhysicalStorageBuffer), Uniform (BufferBlock) or Image. Vulkan supports atomics in compute shaders unconditionally, but requires feature bits (fragmentStoresAndAtomics and vertexStoresAndAtomics) to work in Fragment and Vertex shaders. WebGPU currently forbids vertexStoresAndAtomics.

Vulkan supports the following operations:

  • OpAtomicLoad
  • OpAtomicStore
  • OpAtomicExchange
  • OpAtomicCompareExchange
    • Strong version
  • OpAtomicIIncrement
  • OpAtomicIDecrement
  • OpAtomic[I|F]Add
  • OpAtomicISub
  • OpAtomic[U|S|F]Min
  • OpAtomic[U|S|F]Max
  • OpAtomicAnd
  • OpAtomicOr
  • OpAtomicXor

Weak Compare Exchange

The weak version of atomic compare exchange may fail spuriously. That is, the comparison may fail even if the expected value matches the value in the atomic memory location. The weak version should generally be used when the cost of failure is low and be put into a loop until it succeeds. The cost of the strong version is much higher than the weak version on some systems and should be used for single tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    wgslWebGPU Shading Language Issues

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions