Skip to main content

cubecl_core/frontend/element/
atomic.rs

1use cubecl_ir::{ConstantValue, ExpandValue, dialect::atomic::*, types::AtomicType};
2use cubecl_macros::intrinsic;
3use half::{bf16, f16};
4use pliron::{
5    builtin::op_interfaces::OneResultInterface, context::Context, op::Op, r#type::TypeHandle,
6    value::Value,
7};
8
9use super::{NativeAssign, NativeExpand};
10use crate::{
11    self as cubecl,
12    frontend::{CubePrimitive, CubeType},
13    ir::Scope,
14    prelude::*,
15};
16
17/// An atomic numerical type wrapping a normal numeric primitive. Enables the use of atomic
18/// operations, while disabling normal operations. In WGSL, this is a separate type - on CUDA/SPIR-V
19/// it can theoretically be bitcast to a normal number, but this isn't recommended.
20#[derive(Clone, Copy, Hash, PartialEq, Eq)]
21pub struct Atomic<Inner: CubePrimitive> {
22    pub val: Inner,
23}
24
25type AtomicExpand<Inner> = NativeExpand<Atomic<Inner>>;
26
27pub trait AtomicNumeric {
28    fn __expand_fetch_add(scope: &Scope, ptr: ExpandValue, value: ExpandValue) -> ExpandValue;
29    fn __expand_fetch_sub(scope: &Scope, ptr: ExpandValue, value: ExpandValue) -> ExpandValue;
30    fn __expand_fetch_min(scope: &Scope, ptr: ExpandValue, value: ExpandValue) -> ExpandValue;
31    fn __expand_fetch_max(scope: &Scope, ptr: ExpandValue, value: ExpandValue) -> ExpandValue;
32}
33
34macro_rules! atomic_numeric {
35    ($($ty: ty),*; $add: ty, $sub: ty, $min: ty, $max: ty) => {
36        $(impl AtomicNumeric for $ty {
37            fn __expand_fetch_add(scope: &Scope, ptr: ExpandValue, value: ExpandValue) -> ExpandValue {
38                atomic_binary_expand(scope, ptr, value, <$add>::new)
39            }
40            fn __expand_fetch_sub(scope: &Scope, ptr: ExpandValue, value: ExpandValue) -> ExpandValue {
41                atomic_binary_expand(scope, ptr, value, <$sub>::new)
42            }
43            fn __expand_fetch_min(scope: &Scope, ptr: ExpandValue, value: ExpandValue) -> ExpandValue {
44                atomic_binary_expand(scope, ptr, value, <$min>::new)
45            }
46            fn __expand_fetch_max(scope: &Scope, ptr: ExpandValue, value: ExpandValue) -> ExpandValue {
47                atomic_binary_expand(scope, ptr, value, <$max>::new)
48            }
49        })*
50    };
51}
52
53atomic_numeric!(i8, i16, i32, i64, isize; AtomicIAddOp, AtomicISubOp, AtomicSMinOp, AtomicSMaxOp);
54atomic_numeric!(u8, u16, u32, u64, usize; AtomicIAddOp, AtomicISubOp, AtomicUMinOp, AtomicUMaxOp);
55atomic_numeric!(f16, bf16, f32, flex32, tf32, f64; AtomicFAddOp, AtomicFSubOp, AtomicFMinOp, AtomicFMaxOp);
56
57fn atomic_binary_expand<F, O>(
58    scope: &Scope,
59    ptr: ExpandValue,
60    value: ExpandValue,
61    func: F,
62) -> ExpandValue
63where
64    F: Fn(&mut Context, Value, Value) -> O,
65    O: Op + OneResultInterface,
66{
67    let op = func(scope.ctx_mut(), ptr.value(scope), value.read_value(scope));
68    scope.register_with_result(&op).into()
69}
70
71#[cube]
72impl<Inner: CubePrimitive<Scalar: AtomicNumeric>> Atomic<Inner> {
73    /// Load the value of the atomic.
74    pub fn load(&self) -> Inner {
75        intrinsic!(|scope| {
76            let ptr = self.value(scope);
77            let op = AtomicLoadOp::new(scope.ctx_mut(), ptr);
78            scope.register_with_result(&op).into()
79        })
80    }
81
82    /// Store the value of the atomic.
83    pub fn store(&self, value: Inner) {
84        intrinsic!(|scope| {
85            let ptr = self.value(scope);
86            let value = value.read_value(scope);
87            scope.register(&AtomicStoreOp::new(scope.ctx_mut(), ptr, value));
88        })
89    }
90
91    /// Atomically stores the value into the atomic and returns the old value.
92    pub fn exchange(&self, value: Inner) -> Inner {
93        intrinsic!(|scope| {
94            let ptr = self.value(scope);
95            let value = value.read_value(scope);
96            let op = AtomicExchangeOp::new(scope.ctx_mut(), ptr, value);
97            scope.register_with_result(&op).into()
98        })
99    }
100
101    /// Atomically add a number to the atomic variable. Returns the old value.
102    pub fn fetch_add(&self, value: Inner) -> Inner {
103        intrinsic!(
104            |scope| Inner::Scalar::__expand_fetch_add(scope, self.expand, value.expand).into()
105        )
106    }
107
108    /// Atomically subtracts a number from the atomic variable. Returns the old value.
109    pub fn fetch_sub(&self, value: Inner) -> Inner {
110        intrinsic!(
111            |scope| Inner::Scalar::__expand_fetch_sub(scope, self.expand, value.expand).into()
112        )
113    }
114
115    /// Atomically sets the value of the atomic variable to `max(current_value, value)`. Returns
116    /// the old value.
117    pub fn fetch_max(&self, value: Inner) -> Inner {
118        intrinsic!(
119            |scope| Inner::Scalar::__expand_fetch_max(scope, self.expand, value.expand).into()
120        )
121    }
122
123    /// Atomically sets the value of the atomic variable to `min(current_value, value)`. Returns the
124    /// old value.
125    pub fn fetch_min(&self, value: Inner) -> Inner {
126        intrinsic!(
127            |scope| Inner::Scalar::__expand_fetch_min(scope, self.expand, value.expand).into()
128        )
129    }
130}
131
132#[cube]
133impl<Inner: CubePrimitive<Scalar: Int>> Atomic<Inner> {
134    /// Compare the value at `pointer` to `cmp` and set it to `value` only if they are the same.
135    /// Returns the old value of the pointer before the store.
136    ///
137    /// ### Tip
138    /// Compare the returned value to `cmp` to determine whether the store was successful.
139    pub fn compare_exchange_weak(&self, cmp: Inner, value: Inner) -> Inner {
140        intrinsic!(|scope| {
141            let ptr = self.value(scope);
142            let cmp = cmp.read_value(scope);
143            let value = value.read_value(scope);
144            let op = AtomicCompareExchangeWeakOp::new(scope.ctx_mut(), ptr, cmp, value);
145            scope.register_with_result(&op).into()
146        })
147    }
148
149    /// Executes an atomic bitwise and operation on the atomic variable. Returns the old value.
150    pub fn fetch_and(&self, value: Inner) -> Inner {
151        intrinsic!(|scope| {
152            let ptr = self.value(scope);
153            let value = value.read_value(scope);
154            let op = AtomicAndOp::new(scope.ctx_mut(), ptr, value);
155            scope.register_with_result(&op).into()
156        })
157    }
158
159    /// Executes an atomic bitwise or operation on the atomic variable. Returns the old value.
160    pub fn fetch_or(&self, value: Inner) -> Inner {
161        intrinsic!(|scope| {
162            let ptr = self.value(scope);
163            let value = value.read_value(scope);
164            let op = AtomicOrOp::new(scope.ctx_mut(), ptr, value);
165            scope.register_with_result(&op).into()
166        })
167    }
168
169    /// Executes an atomic bitwise xor operation on the atomic variable. Returns the old value.
170    pub fn fetch_xor(&self, value: Inner) -> Inner {
171        intrinsic!(|scope| {
172            let ptr = self.value(scope);
173            let value = value.read_value(scope);
174            let op = AtomicXorOp::new(scope.ctx_mut(), ptr, value);
175            scope.register_with_result(&op).into()
176        })
177    }
178}
179
180impl<Inner: CubePrimitive> CubeType for Atomic<Inner> {
181    type ExpandType = NativeExpand<Self>;
182}
183
184impl<Inner: CubePrimitive> CubeDebug for Atomic<Inner> {}
185impl<Inner: CubePrimitive> CubePrimitive for Atomic<Inner> {
186    type Scalar = Inner::Scalar;
187    type Size = Const<1>;
188    type WithScalar<S: Scalar> = Atomic<S>;
189
190    fn __expand_as_type(scope: &Scope) -> TypeHandle {
191        let inner = Inner::__expand_as_type(scope);
192        AtomicType::get(scope.ctx(), inner).into()
193    }
194
195    fn from_expand_elem(elem: ExpandValue) -> Self::ExpandType {
196        NativeExpand::new(elem)
197    }
198
199    fn from_const_value(_value: ConstantValue) -> Self {
200        panic!("Can't have constant atomic");
201    }
202}
203
204impl<Inner: CubePrimitive> NativeAssign for Atomic<Inner> {}