Re: [PATCH v5 05/10] rust: sync: atomic: Add atomic {cmp,}xchg operations

"Benno Lossin" <[email protected]>
Newsgroups dev.linux.lists.lkmm,org.kernel.vger.linux-arch,org.kernel.vger.linux-kernel,org.kernel.vger.rust-for-linux
Message-ID <[email protected]>
On Wed Jun 18, 2025 at 6:49 PM CEST, Boqun Feng wrote:
> +impl<T: AllowAtomic> Atomic<T>
> +where
> +    T::Repr: AtomicHasXchgOps,
> +{
> +    /// Atomic exchange.
> +    ///
> +    /// # Examples
> +    ///
> +    /// ```rust
> +    /// use kernel::sync::atomic::{Atomic, Acquire, Relaxed};
> +    ///
> +    /// let x = Atomic::new(42);
> +    ///
> +    /// assert_eq!(42, x.xchg(52, Acquire));
> +    /// assert_eq!(52, x.load(Relaxed));
> +    /// ```
> +    #[doc(alias("atomic_xchg", "atomic64_xchg"))]
> +    #[inline(always)]
> +    pub fn xchg<Ordering: All>(&self, v: T, _: Ordering) -> T {

Can we name this `exchange`?

> +        let v = T::into_repr(v);
> +        let a = self.as_ptr().cast::<T::Repr>();
> +
> +        // SAFETY:
> +        // - For calling the atomic_xchg*() function:
> +        //   - `self.as_ptr()` is a valid pointer, and per the safety requirement of `AllocAtomic`,
> +        //      a `*mut T` is a valid `*mut T::Repr`. Therefore `a` is a valid pointer,
> +        //   - per the type invariants, the following atomic operation won't cause data races.
> +        // - For extra safety requirement of usage on pointers returned by `self.as_ptr():
> +        //   - atomic operations are used here.
> +        let ret = unsafe {
> +            match Ordering::TYPE {
> +                OrderingType::Full => T::Repr::atomic_xchg(a, v),
> +                OrderingType::Acquire => T::Repr::atomic_xchg_acquire(a, v),
> +                OrderingType::Release => T::Repr::atomic_xchg_release(a, v),
> +                OrderingType::Relaxed => T::Repr::atomic_xchg_relaxed(a, v),
> +            }
> +        };
> +
> +        T::from_repr(ret)
> +    }
> +
> +    /// Atomic compare and exchange.
> +    ///
> +    /// Compare: The comparison is done via the byte level comparison between the atomic variables
> +    /// with the `old` value.
> +    ///
> +    /// Ordering: When succeeds, provides the corresponding ordering as the `Ordering` type
> +    /// parameter indicates, and a failed one doesn't provide any ordering, the read part of a
> +    /// failed cmpxchg should be treated as a relaxed read.

This is a bit confusing to me. The operation has a store and a load
operation and both can have different orderings (at least in Rust
userland) depending on the success/failure of the operation. In
userland, I can supply `AcqRel` and `Acquire` to ensure that I always
have Acquire semantics on any read and `Release` semantics on any write
(which I would think is a common case). How do I do this using your API?

Don't I need `Acquire` semantics on the read in order for
`compare_exchange` to give me the correct behavior in this example:

    pub struct Foo {
        data: Atomic<u64>,
        new: Atomic<bool>,
        ready: Atomic<bool>,
    }

    impl Foo {
        pub fn new() -> Self {
            Self {
                data: Atomic::new(0),
                new: Atomic::new(false),
                ready: Atomic::new(false),
            }
        }

        pub fn get(&self) -> Option<u64> {
            if self.new.compare_exchange(true, false, Release).is_ok() {
                let val = self.data.load(Acquire);
                self.ready.store(false, Release);
                Some(val)
            } else {
                None
            }
        }

        pub fn set(&self, val: u64) -> Result<(), u64> {
            if self.ready.compare_exchange(false, true, Release).is_ok() {
                self.data.store(val, Release);
                self.new.store(true, Release);
            } else {
                Err(val)
            }
        }
    }

IIUC, you need `Acquire` ordering on both `compare_exchange` operations'
reads for this to work, right? Because if they are relaxed, this could
happen:

                    Thread 0                    |                    Thread 1
------------------------------------------------|------------------------------------------------
 get() {                                        | set(42) {
                                                |   if ready.cmpxchg(false, true, Rel).is_ok() {
                                                |     data.store(42, Rel)
                                                |     new.store(true, Rel)
   if new.cmpxchg(true, false, Rel).is_ok() {   |
     let val = self.data.load(Acq); // reads 0  |
     ready.store(false, Rel);                   |
     Some(val)                                  |
   }                                            |   }
 }                                              | }
 
So essentially, the `data.store` operation is not synchronized, because
the read on `new` is not `Acquire`.

> +    ///
> +    /// Returns `Ok(value)` if cmpxchg succeeds, and `value` is guaranteed to be equal to `old`,
> +    /// otherwise returns `Err(value)`, and `value` is the value of the atomic variable when
> +    /// cmpxchg was happening.
> +    ///
> +    /// # Examples
> +    ///
> +    /// ```rust
> +    /// use kernel::sync::atomic::{Atomic, Full, Relaxed};
> +    ///
> +    /// let x = Atomic::new(42);
> +    ///
> +    /// // Checks whether cmpxchg succeeded.
> +    /// let success = x.cmpxchg(52, 64, Relaxed).is_ok();
> +    /// # assert!(!success);
> +    ///
> +    /// // Checks whether cmpxchg failed.
> +    /// let failure = x.cmpxchg(52, 64, Relaxed).is_err();
> +    /// # assert!(failure);
> +    ///
> +    /// // Uses the old value if failed, probably re-try cmpxchg.
> +    /// match x.cmpxchg(52, 64, Relaxed) {
> +    ///     Ok(_) => { },
> +    ///     Err(old) => {
> +    ///         // do something with `old`.
> +    ///         # assert_eq!(old, 42);
> +    ///     }
> +    /// }
> +    ///
> +    /// // Uses the latest value regardlessly, same as atomic_cmpxchg() in C.
> +    /// let latest = x.cmpxchg(42, 64, Full).unwrap_or_else(|old| old);
> +    /// # assert_eq!(42, latest);
> +    /// assert_eq!(64, x.load(Relaxed));
> +    /// ```
> +    #[doc(alias(
> +        "atomic_cmpxchg",
> +        "atomic64_cmpxchg",
> +        "atomic_try_cmpxchg",
> +        "atomic64_try_cmpxchg"
> +    ))]
> +    #[inline(always)]
> +    pub fn cmpxchg<Ordering: All>(&self, mut old: T, new: T, o: Ordering) -> Result<T, T> {

`compare_exchange`?

> +    /// Atomic compare and exchange and returns whether the operation succeeds.
> +    ///
> +    /// "Compare" and "Ordering" part are the same as [`Atomic::cmpxchg()`].
> +    ///
> +    /// Returns `true` means the cmpxchg succeeds otherwise returns `false` with `old` updated to
> +    /// the value of the atomic variable when cmpxchg was happening.
> +    #[inline(always)]
> +    fn try_cmpxchg<Ordering: All>(&self, old: &mut T, new: T, _: Ordering) -> bool {

`try_compare_exchange`?

---
Cheers,
Benno

> +        let old = (old as *mut T).cast::<T::Repr>();
> +        let new = T::into_repr(new);
> +        let a = self.0.get().cast::<T::Repr>();
> +
> +        // SAFETY:
> +        // - For calling the atomic_try_cmpchg*() function:
> +        //   - `self.as_ptr()` is a valid pointer, and per the safety requirement of `AllowAtomic`,
> +        //      a `*mut T` is a valid `*mut T::Repr`. Therefore `a` is a valid pointer,
> +        //   - per the type invariants, the following atomic operation won't cause data races.
> +        //   - `old` is a valid pointer to write because it comes from a mutable reference.
> +        // - For extra safety requirement of usage on pointers returned by `self.as_ptr():
> +        //   - atomic operations are used here.
> +        unsafe {
> +            match Ordering::TYPE {
> +                OrderingType::Full => T::Repr::atomic_try_cmpxchg(a, old, new),
> +                OrderingType::Acquire => T::Repr::atomic_try_cmpxchg_acquire(a, old, new),
> +                OrderingType::Release => T::Repr::atomic_try_cmpxchg_release(a, old, new),
> +                OrderingType::Relaxed => T::Repr::atomic_try_cmpxchg_relaxed(a, old, new),
> +            }
> +        }
> +    }
> +}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.