From 1deb3f753c5cf23ff3f84ea483aa400ee9c9c74f Mon Sep 17 00:00:00 2001 From: Yong He Date: Tue, 17 Sep 2024 22:53:44 -0700 Subject: [PATCH 1/4] Add design doc for atomic type. --- docs/proposals/003-atomic-t.md | 104 +++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 docs/proposals/003-atomic-t.md diff --git a/docs/proposals/003-atomic-t.md b/docs/proposals/003-atomic-t.md new file mode 100644 index 0000000000..b67ec171d6 --- /dev/null +++ b/docs/proposals/003-atomic-t.md @@ -0,0 +1,104 @@ +SP #003 - `Atomic` type +============== + + +Status +------ + +Author: Yong He + +Status: Design Discussion. + +Implementation: N/A + +Reviewed by: N/A + +Background +---------- + +HLSL defines atomic intrinsics to work on free references to ordinary values such as `int` and `float`. However, this doesn't translate well to Metal and WebGPU, +which defines `atomic` type and only allow atomic operations to be applied on values of `atomic` types. + +Slang's Metal backend follows the same technique in SPIRV-Cross and DXIL->Metal converter that relies on a C++ undefined behavior that casts an ordinary `int*` pointer to a `atomic*` pointer +and then call atomic intrinsic on the reinterpreted pointer. This is fragile and not guaranteed to work in the future. + +To make the situation worse, WebGPU bans all possible ways to cast a normal pointer into an `atomic` pointer. In order to provide a truly portable way to define +atomic operations and allow them to be translatable to all targets, we will also need an `atomic` type in Slang that maps to `atomic` in WGSL and Metal, and maps to +`T` for HLSL/SPIRV. + + +Proposed Approach +----------------- + +We define an `Atomic` type that functions as a wrapper of `T` and provides atomic operations: +``` +interface IAtomicable {} +extension int : IAtomicable {} +extension uint : IAtomicable {} +extension float : IAtomicable {} +extension half : IAtomicable {} + +struct Atomic +{ + T load(); + [ref] void store(T newValue); // Question: do we really need this? + [ref] T exchange(T newValue); // returns old value + [ref] T compareExchange(T compareValue, T newValue); // returns old value. + [ref] T atomicAdd(T value); // returns original value + [ref] T atomicSub(T value); // returns original value + [ref] T atomicMax(T value); // returns original value + [ref] T atomicMin(T value); // returns original value +} + +extension Atomic + where T : IAtomicable + where T : __BuiltinIntegerType +{ + [ref] T atomicAnd(T value); // returns original value + [ref] T atomicOr(T value); // returns original value + [ref] T atomicXor(T value); // returns original value +} +``` + +We allow `Atomic` to be defined anywhere: as struct fields, as array elements, as elements of `RWStructuredBuffer` types, +or as groupshared variable types. For example, in global memory: + +```hlsl +struct MyType +{ + int ordinaryValue; + Atomic atomicValue; +} + +RWStructuredBuffer atomicBuffer; + +void main() +{ + atomicBuffer[0].atomicValue.atomicAdd(1); + printf("%d", atomicBuffer[0].atomicValue.load()); +} +``` + +In groupshared memory: + +```hlsl +void main() +{ + groupshared atomic c; + c.atomicAdd(1); +} +``` + +When generating WGSL code where `atomic` isn't allowed on local variables or other illegal address spaces, we will lower the type +into its underlying type. This should be handled by a legalization pass similar to `lowerBufferElementTypeToStorageType` but operates +in the opposite direction: the "loaded" value from a buffer is converted into an atomic-free type, and storing a value leads to an +atomic store at the corresponding locations. + +For non-WGSL/Metal targets, we can simply lower the type out of existence into its underlying type. + +# Related Work + +`Atomic` type exists in almost all CPU programming languages and is the proven way to express atomic operations over different +architectures that have different memory models. WGSL and Metal follows this trend to require atomic operations being expressed +this way. This proposal is to make Slang follow this trend and make `Atomic` the recommened way to express atomic operation +going forward. \ No newline at end of file From 26b7233a9723e58dca09b81eaddd1e720a73e5f5 Mon Sep 17 00:00:00 2001 From: Yong He Date: Thu, 19 Sep 2024 10:24:55 -0700 Subject: [PATCH 2/4] Update design doc. --- docs/proposals/003-atomic-t.md | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/proposals/003-atomic-t.md b/docs/proposals/003-atomic-t.md index b67ec171d6..9c918d5b02 100644 --- a/docs/proposals/003-atomic-t.md +++ b/docs/proposals/003-atomic-t.md @@ -32,27 +32,36 @@ Proposed Approach We define an `Atomic` type that functions as a wrapper of `T` and provides atomic operations: ``` -interface IAtomicable {} -extension int : IAtomicable {} -extension uint : IAtomicable {} -extension float : IAtomicable {} -extension half : IAtomicable {} - -struct Atomic +[sealed] interface IAtomicable {} +[sealed] interface IArithmeticAtomicable : IAtomicable {} +[sealed] interface IBitAtomicable : IArithmeticAtomicable {} + +extension int : IArithmeticAtomicable {} +extension uint : IArithmeticAtomicable {} +extension int64_t : IBitAtomicable {} +extension uint64_t : IBitAtomicable {} +extension float : IArithmeticAtomicable {} +extension half : IArithmeticAtomicable {} + +struct Atomic { T load(); [ref] void store(T newValue); // Question: do we really need this? [ref] T exchange(T newValue); // returns old value [ref] T compareExchange(T compareValue, T newValue); // returns old value. +} + +extension Atomic +{ [ref] T atomicAdd(T value); // returns original value [ref] T atomicSub(T value); // returns original value [ref] T atomicMax(T value); // returns original value [ref] T atomicMin(T value); // returns original value + [ref] T atomicIncrement(); + [ref] T atomicDecrement(); } -extension Atomic - where T : IAtomicable - where T : __BuiltinIntegerType +extension Atomic { [ref] T atomicAnd(T value); // returns original value [ref] T atomicOr(T value); // returns original value @@ -61,7 +70,7 @@ extension Atomic ``` We allow `Atomic` to be defined anywhere: as struct fields, as array elements, as elements of `RWStructuredBuffer` types, -or as groupshared variable types. For example, in global memory: +or as local and groupshared variable types. For example, in global memory: ```hlsl struct MyType From 4b6e1eaa561bf7d63b43e7bfeaf957358b162bad Mon Sep 17 00:00:00 2001 From: Yong He Date: Thu, 19 Sep 2024 10:27:21 -0700 Subject: [PATCH 3/4] Fix. --- docs/proposals/003-atomic-t.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proposals/003-atomic-t.md b/docs/proposals/003-atomic-t.md index 9c918d5b02..f0e8234cb5 100644 --- a/docs/proposals/003-atomic-t.md +++ b/docs/proposals/003-atomic-t.md @@ -70,7 +70,7 @@ extension Atomic ``` We allow `Atomic` to be defined anywhere: as struct fields, as array elements, as elements of `RWStructuredBuffer` types, -or as local and groupshared variable types. For example, in global memory: +or as local, global and groupshared variable types or function parameter types. For example, in global memory: ```hlsl struct MyType From b878c80eba83f052367522b9d939f3689eaff878 Mon Sep 17 00:00:00 2001 From: Yong He Date: Thu, 19 Sep 2024 10:32:32 -0700 Subject: [PATCH 4/4] More comment on parameters. --- docs/proposals/003-atomic-t.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/proposals/003-atomic-t.md b/docs/proposals/003-atomic-t.md index f0e8234cb5..03df9bb5e5 100644 --- a/docs/proposals/003-atomic-t.md +++ b/docs/proposals/003-atomic-t.md @@ -99,7 +99,10 @@ void main() ``` When generating WGSL code where `atomic` isn't allowed on local variables or other illegal address spaces, we will lower the type -into its underlying type. This should be handled by a legalization pass similar to `lowerBufferElementTypeToStorageType` but operates +into its underlying type. The use of atomic type in these positions will simply have no meaning. A caveat is what the semantics should be +when there is a function that takes `inout Atomic` as parameter. This likely need to be a warning or error. + +This should be handled by a legalization pass similar to `lowerBufferElementTypeToStorageType` but operates in the opposite direction: the "loaded" value from a buffer is converted into an atomic-free type, and storing a value leads to an atomic store at the corresponding locations.