[PATCH 0/2] Provide Rust atomic helpers over raw pointers

1 view
Skip to first unread message

Boqun Feng

unread,
Jan 20, 2026, 6:52:18 AMJan 20
to linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, Marco Elver, FUJITA Tomonori
[1] indicates that there might be need for Rust to synchronize with C
over atomic, currently the recommendation is using
`Atomic::from_ptr().op()`, however, it's more convenient to have helper
wrapper that directly perform operation over pointers. Hence add them
for load()/store()/xchg()/cmpxgh().

While working on this, I also found an issue in `from_ptr()`, therefore
fix it.

[1]: https://lore.kernel.org/rust-for-linux/20251231-rwonce-...@google.com/

Regards,
Boqun

Boqun Feng (2):
rust: sync: atomic: Remove bound `T: Sync` for `Atomci::from_ptr()`
rust: sync: atomic: Add atomic operation helpers over raw pointers

rust/kernel/sync/atomic.rs | 109 ++++++++++++++++++++++++++-
rust/kernel/sync/atomic/predefine.rs | 46 +++++++++++
2 files changed, 151 insertions(+), 4 deletions(-)

--
2.51.0

Boqun Feng

unread,
Jan 20, 2026, 6:52:20 AMJan 20
to linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, Marco Elver, FUJITA Tomonori
Originally, `Atomic::from_ptr()` requires `T` being a `Sync` because I
thought having the ability to do `from_ptr()` meant multiplle
`&Atomic<T>`s shared by different threads, which was identical (or
similar) to multiple `&T`s shared by different threads. Hence `T` was
required to be `Sync`. However this is not true, since `&Atomic<T>` is
not the same at `&T`. Moreover, having this bound makes `Atomic::<*mut
T>::from_ptr()` impossible, which is definitely not intended. Therefore
remove the `T: Sync` bound.

Fixes: 29c32c405e53 ("rust: sync: atomic: Add generic atomics")
Signed-off-by: Boqun Feng <boqun...@gmail.com>
---
rust/kernel/sync/atomic.rs | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/rust/kernel/sync/atomic.rs b/rust/kernel/sync/atomic.rs
index 224bd57da1ab..d49ee45c6eb7 100644
--- a/rust/kernel/sync/atomic.rs
+++ b/rust/kernel/sync/atomic.rs
@@ -215,10 +215,7 @@ pub const fn new(v: T) -> Self {
/// // no data race.
/// unsafe { Atomic::from_ptr(foo_a_ptr) }.store(2, Release);
/// ```
- pub unsafe fn from_ptr<'a>(ptr: *mut T) -> &'a Self
- where
- T: Sync,
- {
+ pub unsafe fn from_ptr<'a>(ptr: *mut T) -> &'a Self {
// CAST: `T` and `Atomic<T>` have the same size, alignment and bit validity.
// SAFETY: Per function safety requirement, `ptr` is a valid pointer and the object will
// live long enough. It's safe to return a `&Atomic<T>` because function safety requirement
--
2.51.0

Boqun Feng

unread,
Jan 20, 2026, 6:52:23 AMJan 20
to linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, Marco Elver, FUJITA Tomonori
In order to synchronize with C or external, atomic operations over raw
pointers, althought previously there is always an `Atomic::from_ptr()`
to provide a `&Atomic<T>`. However it's more convenient to have helpers
that directly perform atomic operations on raw pointers. Hence a few are
added, which are basically a `Atomic::from_ptr().op()` wrapper.

Note: for naming, since `atomic_xchg()` and `atomic_cmpxchg()` has a
conflict naming to 32bit C atomic xchg/cmpxchg, hence they are just
named as `xchg()` and `cmpxchg()`. For `atomic_load()` and
`atomic_store()`, their 32bit C counterparts are `atomic_read()` and
`atomic_set()`, so keep the `atomic_` prefix.

Signed-off-by: Boqun Feng <boqun...@gmail.com>
---
rust/kernel/sync/atomic.rs | 104 +++++++++++++++++++++++++++
rust/kernel/sync/atomic/predefine.rs | 46 ++++++++++++
2 files changed, 150 insertions(+)

diff --git a/rust/kernel/sync/atomic.rs b/rust/kernel/sync/atomic.rs
index d49ee45c6eb7..6c46335bdb8c 100644
--- a/rust/kernel/sync/atomic.rs
+++ b/rust/kernel/sync/atomic.rs
@@ -611,3 +611,107 @@ pub fn cmpxchg<Ordering: ordering::Ordering>(
}
}
}
+
+/// Atomic load over raw pointers.
+///
+/// This function provides a short-cut of `Atomic::from_ptr().load(..)`, and can be used to work
+/// with C side on synchronizations:
+///
+/// - `atomic_load(.., Relaxed)` maps to `READ_ONCE()` when using for inter-thread communication.
+/// - `atomic_load(.., Acquire)` maps to `smp_load_acquire()`.
+///
+/// # Safety
+///
+/// - `ptr` is a valid pointer to `T` and aligned to `align_of::<T>()`.
+/// - If there is a concurrent store from kernel (C or Rust), it has to be atomic.
+#[doc(alias("READ_ONCE", "smp_load_acquire"))]
+#[inline(always)]
+pub unsafe fn atomic_load<T: AtomicType, Ordering: ordering::AcquireOrRelaxed>(
+ ptr: *mut T,
+ o: Ordering,
+) -> T
+where
+ T::Repr: AtomicBasicOps,
+{
+ // SAFETY: Per the function safety requirement, `ptr` is valid and aligned to
+ // `align_of::<T>()`, and all concurrent stores from kernel are atomic, hence no data race per
+ // LKMM.
+ unsafe { Atomic::from_ptr(ptr) }.load(o)
+}
+
+/// Atomic store over raw pointers.
+///
+/// This function provides a short-cut of `Atomic::from_ptr().load(..)`, and can be used to work
+/// with C side on synchronizations:
+///
+/// - `atomic_store(.., Relaxed)` maps to `WRITE_ONCE()` when using for inter-thread communication.
+/// - `atomic_load(.., Release)` maps to `smp_store_release()`.
+///
+/// # Safety
+///
+/// - `ptr` is a valid pointer to `T` and aligned to `align_of::<T>()`.
+/// - If there is a concurrent access from kernel (C or Rust), it has to be atomic.
+#[doc(alias("WRITE_ONCE", "smp_store_release"))]
+#[inline(always)]
+pub unsafe fn atomic_store<T: AtomicType, Ordering: ordering::ReleaseOrRelaxed>(
+ ptr: *mut T,
+ v: T,
+ o: Ordering,
+) where
+ T::Repr: AtomicBasicOps,
+{
+ // SAFETY: Per the function safety requirement, `ptr` is valid and aligned to
+ // `align_of::<T>()`, and all concurrent accesses from kernel are atomic, hence no data race
+ // per LKMM.
+ unsafe { Atomic::from_ptr(ptr) }.store(v, o);
+}
+
+/// Atomic exchange over raw pointers.
+///
+/// This function provides a short-cut of `Atomic::from_ptr().xchg(..)`, and can be used to work
+/// with C side on synchronizations.
+///
+/// # Safety
+///
+/// - `ptr` is a valid pointer to `T` and aligned to `align_of::<T>()`.
+/// - If there is a concurrent access from kernel (C or Rust), it has to be atomic.
+#[inline(always)]
+pub unsafe fn xchg<T: AtomicType, Ordering: ordering::Ordering>(
+ ptr: *mut T,
+ new: T,
+ o: Ordering,
+) -> T
+where
+ T::Repr: AtomicExchangeOps,
+{
+ // SAFETY: Per the function safety requirement, `ptr` is valid and aligned to
+ // `align_of::<T>()`, and all concurrent accesses from kernel are atomic, hence no data race
+ // per LKMM.
+ unsafe { Atomic::from_ptr(ptr) }.xchg(new, o)
+}
+
+/// Atomic compare and exchange over raw pointers.
+///
+/// This function provides a short-cut of `Atomic::from_ptr().cmpxchg(..)`, and can be used to work
+/// with C side on synchronizations.
+///
+/// # Safety
+///
+/// - `ptr` is a valid pointer to `T` and aligned to `align_of::<T>()`.
+/// - If there is a concurrent access from kernel (C or Rust), it has to be atomic.
+#[doc(alias("try_cmpxchg"))]
+#[inline(always)]
+pub unsafe fn cmpxchg<T: AtomicType, Ordering: ordering::Ordering>(
+ ptr: *mut T,
+ old: T,
+ new: T,
+ o: Ordering,
+) -> Result<T, T>
+where
+ T::Repr: AtomicExchangeOps,
+{
+ // SAFETY: Per the function safety requirement, `ptr` is valid and aligned to
+ // `align_of::<T>()`, and all concurrent accesses from kernel are atomic, hence no data race
+ // per LKMM.
+ unsafe { Atomic::from_ptr(ptr) }.cmpxchg(old, new, o)
+}
diff --git a/rust/kernel/sync/atomic/predefine.rs b/rust/kernel/sync/atomic/predefine.rs
index 5faa2fe2f4b6..11bc67ab70a3 100644
--- a/rust/kernel/sync/atomic/predefine.rs
+++ b/rust/kernel/sync/atomic/predefine.rs
@@ -235,6 +235,14 @@ fn atomic_basic_tests() {

assert_eq!(v, x.load(Relaxed));
});
+
+ for_each_type!(42 in [i8, i16, i32, i64, u32, u64, isize, usize] |v| {
+ let x = Atomic::new(v);
+ let ptr = x.as_ptr();
+
+ // SAFETY: `ptr` is a valid pointer and no concurrent access.
+ assert_eq!(v, unsafe { atomic_load(ptr, Relaxed) });
+ });
}

#[test]
@@ -245,6 +253,17 @@ fn atomic_acquire_release_tests() {
x.store(v, Release);
assert_eq!(v, x.load(Acquire));
});
+
+ for_each_type!(42 in [i8, i16, i32, i64, u32, u64, isize, usize] |v| {
+ let x = Atomic::new(0);
+ let ptr = x.as_ptr();
+
+ // SAFETY: `ptr` is a valid pointer and no concurrent access.
+ unsafe { atomic_store(ptr, v, Release) };
+
+ // SAFETY: `ptr` is a valid pointer and no concurrent access.
+ assert_eq!(v, unsafe { atomic_load(ptr, Acquire) });
+ });
}

#[test]
@@ -258,6 +277,18 @@ fn atomic_xchg_tests() {
assert_eq!(old, x.xchg(new, Full));
assert_eq!(new, x.load(Relaxed));
});
+
+ for_each_type!(42 in [i8, i16, i32, i64, u32, u64, isize, usize] |v| {
+ let x = Atomic::new(v);
+ let ptr = x.as_ptr();
+
+ let old = v;
+ let new = v + 1;
+
+ // SAFETY: `ptr` is a valid pointer and no concurrent access.
+ assert_eq!(old, unsafe { xchg(ptr, new, Full) });
+ assert_eq!(new, x.load(Relaxed));
+ });
}

#[test]
@@ -273,6 +304,21 @@ fn atomic_cmpxchg_tests() {
assert_eq!(Ok(old), x.cmpxchg(old, new, Relaxed));
assert_eq!(new, x.load(Relaxed));
});
+
+ for_each_type!(42 in [i8, i16, i32, i64, u32, u64, isize, usize] |v| {
+ let x = Atomic::new(v);
+ let ptr = x.as_ptr();
+
+ let old = v;
+ let new = v + 1;
+
+ // SAFETY: `ptr` is a valid pointer and no concurrent access.
+ assert_eq!(Err(old), unsafe { cmpxchg(ptr, new, new, Full) });
+ assert_eq!(old, x.load(Relaxed));
+ // SAFETY: `ptr` is a valid pointer and no concurrent access.
+ assert_eq!(Ok(old), unsafe { cmpxchg(ptr, old, new, Relaxed) });
+ assert_eq!(new, x.load(Relaxed));
+ });
}

#[test]
--
2.51.0

Alice Ryhl

unread,
Jan 20, 2026, 7:38:50 AMJan 20
to Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Gary Guo, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, Marco Elver, FUJITA Tomonori
On Tue, Jan 20, 2026 at 07:52:06PM +0800, Boqun Feng wrote:
> Originally, `Atomic::from_ptr()` requires `T` being a `Sync` because I
> thought having the ability to do `from_ptr()` meant multiplle
> `&Atomic<T>`s shared by different threads, which was identical (or
> similar) to multiple `&T`s shared by different threads. Hence `T` was
> required to be `Sync`. However this is not true, since `&Atomic<T>` is
> not the same at `&T`. Moreover, having this bound makes `Atomic::<*mut
> T>::from_ptr()` impossible, which is definitely not intended. Therefore
> remove the `T: Sync` bound.
>
> Fixes: 29c32c405e53 ("rust: sync: atomic: Add generic atomics")
> Signed-off-by: Boqun Feng <boqun...@gmail.com>

Reviewed-by: Alice Ryhl <alic...@google.com>

Alice Ryhl

unread,
Jan 20, 2026, 7:39:17 AMJan 20
to Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Gary Guo, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, Marco Elver, FUJITA Tomonori
On Tue, Jan 20, 2026 at 07:52:06PM +0800, Boqun Feng wrote:
> Originally, `Atomic::from_ptr()` requires `T` being a `Sync` because I
> thought having the ability to do `from_ptr()` meant multiplle
> `&Atomic<T>`s shared by different threads, which was identical (or
> similar) to multiple `&T`s shared by different threads. Hence `T` was
> required to be `Sync`. However this is not true, since `&Atomic<T>` is
> not the same at `&T`. Moreover, having this bound makes `Atomic::<*mut
> T>::from_ptr()` impossible, which is definitely not intended. Therefore
> remove the `T: Sync` bound.
>
> Fixes: 29c32c405e53 ("rust: sync: atomic: Add generic atomics")
> Signed-off-by: Boqun Feng <boqun...@gmail.com>

there is a typo in patch title

Alice Ryhl

unread,
Jan 20, 2026, 7:41:01 AMJan 20
to Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Gary Guo, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, Marco Elver, FUJITA Tomonori
On Tue, Jan 20, 2026 at 07:52:07PM +0800, Boqun Feng wrote:
> In order to synchronize with C or external, atomic operations over raw
> pointers, althought previously there is always an `Atomic::from_ptr()`
> to provide a `&Atomic<T>`. However it's more convenient to have helpers
> that directly perform atomic operations on raw pointers. Hence a few are
> added, which are basically a `Atomic::from_ptr().op()` wrapper.
>
> Note: for naming, since `atomic_xchg()` and `atomic_cmpxchg()` has a
> conflict naming to 32bit C atomic xchg/cmpxchg, hence they are just
> named as `xchg()` and `cmpxchg()`. For `atomic_load()` and
> `atomic_store()`, their 32bit C counterparts are `atomic_read()` and
> `atomic_set()`, so keep the `atomic_` prefix.
>
> Signed-off-by: Boqun Feng <boqun...@gmail.com>

Reviewed-by: Alice Ryhl <alic...@google.com>

> +/// - `atomic_store(.., Relaxed)` maps to `WRITE_ONCE()` when using for inter-thread communication.

typo: "when used for"

Alice

Gary Guo

unread,
Jan 20, 2026, 8:09:56 AMJan 20
to Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Gary Guo, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, Marco Elver, FUJITA Tomonori
On Tue Jan 20, 2026 at 11:52 AM GMT, Boqun Feng wrote:
> Originally, `Atomic::from_ptr()` requires `T` being a `Sync` because I
> thought having the ability to do `from_ptr()` meant multiplle
> `&Atomic<T>`s shared by different threads, which was identical (or
> similar) to multiple `&T`s shared by different threads. Hence `T` was
> required to be `Sync`. However this is not true, since `&Atomic<T>` is
> not the same at `&T`. Moreover, having this bound makes `Atomic::<*mut
> T>::from_ptr()` impossible, which is definitely not intended. Therefore
> remove the `T: Sync` bound.
>
> Fixes: 29c32c405e53 ("rust: sync: atomic: Add generic atomics")
> Signed-off-by: Boqun Feng <boqun...@gmail.com>

Title has "Atomci" instead of "Atomic"

Reviewed-by: Gary Guo <ga...@garyguo.net>

Gary Guo

unread,
Jan 20, 2026, 8:26:05 AMJan 20
to Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Gary Guo, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, Marco Elver, FUJITA Tomonori
On Tue Jan 20, 2026 at 11:52 AM GMT, Boqun Feng wrote:
> In order to synchronize with C or external, atomic operations over raw

The sentence feels incomplete. Maybe "external memory"? Also "atomic operations
over raw pointers" isn't a full setence.

> pointers, althought previously there is always an `Atomic::from_ptr()`

You mean "already an"?

> to provide a `&Atomic<T>`. However it's more convenient to have helpers
> that directly perform atomic operations on raw pointers. Hence a few are
> added, which are basically a `Atomic::from_ptr().op()` wrapper.
>
> Note: for naming, since `atomic_xchg()` and `atomic_cmpxchg()` has a
> conflict naming to 32bit C atomic xchg/cmpxchg, hence they are just
> named as `xchg()` and `cmpxchg()`. For `atomic_load()` and
> `atomic_store()`, their 32bit C counterparts are `atomic_read()` and
> `atomic_set()`, so keep the `atomic_` prefix.

I still have reservation on if this is actually needed. Directly reading from C
should be rare enough that `Atomic::from_ptr().op()` isn't a big issue. To me,
`Atomic::from_ptr` has the meaning of "we know this is a field that needs atomic
access, but bindgen can't directly generate a `Atomic<T>`", and it will
encourage one to check if this is actually true, while `atomic_op` doesn't feel
the same.

That said, if it's decided that this is indeed needed, then

Reviewed-by: Gary Guo <ga...@garyguo.net>

with the grammar in the commit message fixed.

Best,
Gary

Boqun Feng

unread,
Jan 20, 2026, 8:46:27 AMJan 20
to Gary Guo, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, Marco Elver, FUJITA Tomonori
On Tue, Jan 20, 2026 at 01:25:58PM +0000, Gary Guo wrote:
> On Tue Jan 20, 2026 at 11:52 AM GMT, Boqun Feng wrote:
> > In order to synchronize with C or external, atomic operations over raw
>
> The sentence feels incomplete. Maybe "external memory"? Also "atomic operations
> over raw pointers" isn't a full setence.
>

Ah, my bad, should be "atomic operations over raw pointers are needed",

> > pointers, althought previously there is always an `Atomic::from_ptr()`
>
> You mean "already an"?
>

To me, it's kinda similar, but let's use "already"

> > to provide a `&Atomic<T>`. However it's more convenient to have helpers
> > that directly perform atomic operations on raw pointers. Hence a few are
> > added, which are basically a `Atomic::from_ptr().op()` wrapper.
> >
> > Note: for naming, since `atomic_xchg()` and `atomic_cmpxchg()` has a
> > conflict naming to 32bit C atomic xchg/cmpxchg, hence they are just
> > named as `xchg()` and `cmpxchg()`. For `atomic_load()` and
> > `atomic_store()`, their 32bit C counterparts are `atomic_read()` and
> > `atomic_set()`, so keep the `atomic_` prefix.
>
> I still have reservation on if this is actually needed. Directly reading from C
> should be rare enough that `Atomic::from_ptr().op()` isn't a big issue. To me,
> `Atomic::from_ptr` has the meaning of "we know this is a field that needs atomic
> access, but bindgen can't directly generate a `Atomic<T>`", and it will
> encourage one to check if this is actually true, while `atomic_op` doesn't feel
> the same.
>

These are valid points, but personally I feel it's hard to prevent
people to add these themselves ;)

> That said, if it's decided that this is indeed needed, then
>
> Reviewed-by: Gary Guo <ga...@garyguo.net>
>

Thank you.

> with the grammar in the commit message fixed.
>

The new commit log now:

In order to synchronize with C or external memory, atomic operations
over raw pointers are need. Although there is already an
`Atomic::from_ptr()` to provide a `&Atomic<T>`, it's more convenient to
have helpers that directly perform atomic operations on raw pointers.
Hence a few are added, which are basically an `Atomic::from_ptr().op()`
wrapper.

Note: for naming, since `atomic_xchg()` and `atomic_cmpxchg()` have a
conflict naming to 32bit C atomic xchg/cmpxchg, hence the helpers are
just named as `xchg()` and `cmpxchg()`. For `atomic_load()` and
`atomic_store()`, their 32bit C counterparts are `atomic_read()` and
`atomic_set()`, so keep the `atomic_` prefix.


Regards,
Boqun

Marco Elver

unread,
Jan 20, 2026, 11:24:04 AMJan 20
to Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Gary Guo, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
I'm late to the party and may have missed some discussion, but it might
want restating in the documentation and/or commit log:

READ_ONCE is meant to be a dependency-ordering primitive, i.e. be more
like memory_order_consume than it is memory_order_relaxed. This has, to
the best of my knowledge, not changed; otherwise lots of kernel code
would be broken. It is known to be brittle [1]. So the recommendation
above is unsound; well, it's as unsound as implementing READ_ONCE with a
volatile load.

While Alice's series tried to expose READ_ONCE as-is to the Rust side
(via volatile), so that Rust inherits the exact same semantics (including
its implementation flaw), the recommendation above is doubling down on
the unsoundness by proposing Relaxed to map to READ_ONCE.

[1] https://lpc.events/event/16/contributions/1174/attachments/1108/2121/Status%20Report%20-%20Broken%20Dependency%20Orderings%20in%20the%20Linux%20Kernel.pdf

Furthermore, LTO arm64 promotes READ_ONCE to an acquire (see
arch/arm64/include/asm/rwonce.h):

/*
* When building with LTO, there is an increased risk of the compiler
* converting an address dependency headed by a READ_ONCE() invocation
* into a control dependency and consequently allowing for harmful
* reordering by the CPU.
*
* Ensure that such transformations are harmless by overriding the generic
* READ_ONCE() definition with one that provides RCpc acquire semantics
* when building with LTO.
*/

So for all intents and purposes, the only sound mapping when pairing
READ_ONCE() with an atomic load on the Rust side is to use Acquire
ordering.

Gary Guo

unread,
Jan 20, 2026, 11:47:06 AMJan 20
to Marco Elver, Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Gary Guo, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
On the Rust-side documentation we mentioned that `Relaxed` always preserve
dependency ordering, so yes, it is closer to `consume` in the C11 model.

> It is known to be brittle [1]. So the recommendation
> above is unsound; well, it's as unsound as implementing READ_ONCE with a
> volatile load.

Sorry, which part of this is unsound? You mean that the dependency ordering is
actually lost when it's not supposed to be? Even so, it'll be only a problem on
specific users that uses `Relaxed` to carry ordering?

Users that use `Relaxed` for things that don't require any ordering would still
be fine?

>
> While Alice's series tried to expose READ_ONCE as-is to the Rust side
> (via volatile), so that Rust inherits the exact same semantics (including
> its implementation flaw), the recommendation above is doubling down on
> the unsoundness by proposing Relaxed to map to READ_ONCE.
>
> [1] https://lpc.events/event/16/contributions/1174/attachments/1108/2121/Status%20Report%20-%20Broken%20Dependency%20Orderings%20in%20the%20Linux%20Kernel.pdf
>

I think this is a longstanding debate on whether we should actually depend on
dependency ordering or just upgrade everything needs it to acquire. But this
isn't really specific to Rust, and whatever is decided is global to the full
LKMM.

> Furthermore, LTO arm64 promotes READ_ONCE to an acquire (see
> arch/arm64/include/asm/rwonce.h):
>
> /*
> * When building with LTO, there is an increased risk of the compiler
> * converting an address dependency headed by a READ_ONCE() invocation
> * into a control dependency and consequently allowing for harmful
> * reordering by the CPU.
> *
> * Ensure that such transformations are harmless by overriding the generic
> * READ_ONCE() definition with one that provides RCpc acquire semantics
> * when building with LTO.
> */
>
> So for all intents and purposes, the only sound mapping when pairing
> READ_ONCE() with an atomic load on the Rust side is to use Acquire
> ordering.

LLVM handles address dependency much saner than GCC does. It for example won't
turn address comparing equal into meaning that the pointer can be interchanged
(as provenance won't match). Currently only address comparision to NULL or
static can have effect on pointer provenance.

Although, last time I asked if we can rely on this for address dependency, I
didn't get an affirmitive answer -- but I think in practice it won't be lost (as
currently implemented).

Furthermore, Rust code currently does not participate in LTO.

Best,
Gary

Marco Elver

unread,
Jan 20, 2026, 12:11:20 PMJan 20
to Gary Guo, Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
Alright, I missed this.
Is this actually enforced, or like the C side's use of "volatile",
relies on luck?

> > It is known to be brittle [1]. So the recommendation
> > above is unsound; well, it's as unsound as implementing READ_ONCE with a
> > volatile load.
>
> Sorry, which part of this is unsound? You mean that the dependency ordering is
> actually lost when it's not supposed to be? Even so, it'll be only a problem on
> specific users that uses `Relaxed` to carry ordering?

Correct.

> Users that use `Relaxed` for things that don't require any ordering would still
> be fine?

Yes.

> > While Alice's series tried to expose READ_ONCE as-is to the Rust side
> > (via volatile), so that Rust inherits the exact same semantics (including
> > its implementation flaw), the recommendation above is doubling down on
> > the unsoundness by proposing Relaxed to map to READ_ONCE.
> >
> > [1] https://lpc.events/event/16/contributions/1174/attachments/1108/2121/Status%20Report%20-%20Broken%20Dependency%20Orderings%20in%20the%20Linux%20Kernel.pdf
> >
>
> I think this is a longstanding debate on whether we should actually depend on
> dependency ordering or just upgrade everything needs it to acquire. But this
> isn't really specific to Rust, and whatever is decided is global to the full
> LKMM.

Indeed, but the implementation on the C vs. Rust side differ
substantially, so assuming it'll work on the Rust side just because
"volatile" works more or less on the C side is a leap I wouldn't want
to take in my codebase.

> > Furthermore, LTO arm64 promotes READ_ONCE to an acquire (see
> > arch/arm64/include/asm/rwonce.h):
> >
> > /*
> > * When building with LTO, there is an increased risk of the compiler
> > * converting an address dependency headed by a READ_ONCE() invocation
> > * into a control dependency and consequently allowing for harmful
> > * reordering by the CPU.
> > *
> > * Ensure that such transformations are harmless by overriding the generic
> > * READ_ONCE() definition with one that provides RCpc acquire semantics
> > * when building with LTO.
> > */
> >
> > So for all intents and purposes, the only sound mapping when pairing
> > READ_ONCE() with an atomic load on the Rust side is to use Acquire
> > ordering.
>
> LLVM handles address dependency much saner than GCC does. It for example won't
> turn address comparing equal into meaning that the pointer can be interchanged
> (as provenance won't match). Currently only address comparision to NULL or
> static can have effect on pointer provenance.
>
> Although, last time I asked if we can rely on this for address dependency, I
> didn't get an affirmitive answer -- but I think in practice it won't be lost (as
> currently implemented).

There is no guarantee here, and this can change with every new
release. In most cases where it matters it works today, but the
compiler (specifically LLVM) does break dependencies even if rarely
[1].

> Furthermore, Rust code currently does not participate in LTO.

LTO is not the problem, aggressive compiler optimizations (as
discussed in [1]) are. And Rust, by virtue of its strong type system,
appears to give the compiler a lot more leeway how it optimizes code.
So I think the Rust side is in greater danger here than the C with LTO
side. But I'm speculating (pun intended) ...

However, given "Relaxed" for the Rust side is already defined to
"carry dependencies" then in isolation my original comment is moot and
does not apply to this particular patch. At face value the promised
semantics are ok, but the implementation (just like "volatile" for C)
probably are not. But that appears to be beyond this patch, so feel
free to ignore.

Gary Guo

unread,
Jan 20, 2026, 12:12:41 PMJan 20
to Marco Elver, Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Gary Guo, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
On Tue Jan 20, 2026 at 4:23 PM GMT, Marco Elver wrote:
Just to add on this part:

If the idea is to add an explicit `Consume` ordering on the Rust side to
document the intent clearly, then I am actually somewhat in favour.

This way, we can for example, map it to a `READ_ONCE` in most cases, but we can
also provide an option to upgrade such calls to `smp_load_acquire` in certain
cases when needed, e.g. LTO arm64.

However this will mean that Rust code will have one more ordering than the C
API, so I am keen on knowing how Boqun, Paul, Peter and others think about this.

> So for all intents and purposes, the only sound mapping when pairing
> READ_ONCE() with an atomic load on the Rust side is to use Acquire
> ordering.

Forget to reply to this part in my other email, but this is definitely not true.
There're use cases for a fully relaxed load on pointer too (in hazard pointer
impl, a few READ_ONCE need depedendency ordering, a few doesn't), not to mention
that this API that Boqun is introducing works for just integers, too.

Best,
Gary



Gary Guo

unread,
Jan 20, 2026, 12:32:53 PMJan 20
to Marco Elver, Gary Guo, Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
Ultimately it's down to same LLVM IR as ClangBuiltLinux, so if it works for C,
it'll work for Rust.
This is a 2022 slide, how much of it is still true today? Nikita has improved
how LLVM handles pointers quite significant in the past few years, so this might
not even apply anymore?

I'd like to see examples of LLVM still breaking address dependencies today, so
at least I'm aware when writing code that depends on them.

>
>> Furthermore, Rust code currently does not participate in LTO.
>
> LTO is not the problem, aggressive compiler optimizations (as
> discussed in [1]) are. And Rust, by virtue of its strong type system,
> appears to give the compiler a lot more leeway how it optimizes code.
> So I think the Rust side is in greater danger here than the C with LTO
> side. But I'm speculating (pun intended) ...

That's actually not the case. Rust people have long recognize that provenance is
a thing and it actually matters. The pointers have a full set of
provenance-aware APIs, and pointer-integer casts are discouraged.

Pointer comparison, for example, is explicitly defined as comparing address and
ignore the provenance, so it's invalid for compiler to do GVN on pointers.

Implementation side, Rust is extremely conservative in optimizing anything that
relates to the memory model currently and when pointers are involved, currently
it's up to LLVM to do most of work. This is mostly due to the lack of full
specification on the memory model, so may change in the future, but I am
optimisitc overall.

Best,
Gary

Boqun Feng

unread,
Jan 20, 2026, 5:29:55 PMJan 20
to Marco Elver, Gary Guo, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
On Tue, Jan 20, 2026 at 06:10:40PM +0100, Marco Elver wrote:
> On Tue, 20 Jan 2026 at 17:47, Gary Guo <ga...@garyguo.net> wrote:
> >
[...]
> > >> +
> > >> +/// Atomic load over raw pointers.
> > >> +///
> > >> +/// This function provides a short-cut of `Atomic::from_ptr().load(..)`, and can be used to work
> > >> +/// with C side on synchronizations:
> > >> +///
> > >> +/// - `atomic_load(.., Relaxed)` maps to `READ_ONCE()` when using for inter-thread communication.
> > >> +/// - `atomic_load(.., Acquire)` maps to `smp_load_acquire()`.
> > >
> > > I'm late to the party and may have missed some discussion, but it might

Thanks for bringing this up ;-)

> > > want restating in the documentation and/or commit log:
> > >
> > > READ_ONCE is meant to be a dependency-ordering primitive, i.e. be more
> > > like memory_order_consume than it is memory_order_relaxed. This has, to
> > > the best of my knowledge, not changed; otherwise lots of kernel code
> > > would be broken.

Our C's atomic_long_read() is the same, that is it's like
memory_order_consume instead memory_order_relaxed.

> >
> > On the Rust-side documentation we mentioned that `Relaxed` always preserve
> > dependency ordering, so yes, it is closer to `consume` in the C11 model.
>
> Alright, I missed this.
> Is this actually enforced, or like the C side's use of "volatile",
> relies on luck?
>

I wouldn't call it luck ;-) but we rely on the same thing that C has:
implementing by using READ_ONCE().

> > > It is known to be brittle [1]. So the recommendation
> > > above is unsound; well, it's as unsound as implementing READ_ONCE with a
> > > volatile load.
> >
> > Sorry, which part of this is unsound? You mean that the dependency ordering is
> > actually lost when it's not supposed to be? Even so, it'll be only a problem on
> > specific users that uses `Relaxed` to carry ordering?
>
> Correct.
>
> > Users that use `Relaxed` for things that don't require any ordering would still
> > be fine?
>
> Yes.
>
> > > While Alice's series tried to expose READ_ONCE as-is to the Rust side
> > > (via volatile), so that Rust inherits the exact same semantics (including
> > > its implementation flaw), the recommendation above is doubling down on
> > > the unsoundness by proposing Relaxed to map to READ_ONCE.
> > >
> > > [1] https://lpc.events/event/16/contributions/1174/attachments/1108/2121/Status%20Report%20-%20Broken%20Dependency%20Orderings%20in%20the%20Linux%20Kernel.pdf
> > >
> >
> > I think this is a longstanding debate on whether we should actually depend on
> > dependency ordering or just upgrade everything needs it to acquire. But this
> > isn't really specific to Rust, and whatever is decided is global to the full
> > LKMM.
>
> Indeed, but the implementation on the C vs. Rust side differ
> substantially, so assuming it'll work on the Rust side just because
> "volatile" works more or less on the C side is a leap I wouldn't want
> to take in my codebase.
>

Which part of the implementation is different between C and Rust? We
implement all Relaxed atomics in Rust the same way as C: using C's
READ_ONCE() and WRITE_ONCE().

> > > Furthermore, LTO arm64 promotes READ_ONCE to an acquire (see
> > > arch/arm64/include/asm/rwonce.h):

So are our C's atomic_read() and Rust's Atomic::load().
Implementation-wise, READ_ONCE() is used the same as C for
atomic_read(), so Rust and C are on the same boat.

Regards,
Boqun

> free to ignore.

Marco Elver

unread,
Jan 21, 2026, 7:14:37 AMJan 21
to Boqun Feng, Gary Guo, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
On Tue, 20 Jan 2026 at 23:29, Boqun Feng <boqun...@gmail.com> wrote:
[..]
> > > > READ_ONCE is meant to be a dependency-ordering primitive, i.e. be more
> > > > like memory_order_consume than it is memory_order_relaxed. This has, to
> > > > the best of my knowledge, not changed; otherwise lots of kernel code
> > > > would be broken.
>
> Our C's atomic_long_read() is the same, that is it's like
> memory_order_consume instead memory_order_relaxed.

I see; so it's Rust's Atomic::load(Relaxed) -> atomic_read() ->
READ_ONCE (for most architectures).

> > > On the Rust-side documentation we mentioned that `Relaxed` always preserve
> > > dependency ordering, so yes, it is closer to `consume` in the C11 model.
> >
> > Alright, I missed this.
> > Is this actually enforced, or like the C side's use of "volatile",
> > relies on luck?
> >
>
> I wouldn't call it luck ;-) but we rely on the same thing that C has:
> implementing by using READ_ONCE().

It's the age-old problem of wanting dependently-ordered atomics, but
no compiler actually providing that. Implementing that via "volatile"
is unsound, and always has been. But that's nothing new.

[...]
> > > I think this is a longstanding debate on whether we should actually depend on
> > > dependency ordering or just upgrade everything needs it to acquire. But this
> > > isn't really specific to Rust, and whatever is decided is global to the full
> > > LKMM.
> >
> > Indeed, but the implementation on the C vs. Rust side differ
> > substantially, so assuming it'll work on the Rust side just because
> > "volatile" works more or less on the C side is a leap I wouldn't want
> > to take in my codebase.
> >
>
> Which part of the implementation is different between C and Rust? We
> implement all Relaxed atomics in Rust the same way as C: using C's
> READ_ONCE() and WRITE_ONCE().

I should clarify: Even if the source of the load is "volatile"
(through atomic_read() FFI) and carries through to Rust code, the
compilers, despite sharing LLVM as the code generator, are different
enough that making the assumption just because it works on the C side,
it'll also work on the Rust side, appears to be a stretch for me. Gary
claimed that Rust is more conservative -- in the absence of any
guarantees, being able to quantify the problem would be nice though.

[..]
> > However, given "Relaxed" for the Rust side is already defined to
> > "carry dependencies" then in isolation my original comment is moot and
> > does not apply to this particular patch. At face value the promised
> > semantics are ok, but the implementation (just like "volatile" for C)
> > probably are not. But that appears to be beyond this patch, so feel
>
> Implementation-wise, READ_ONCE() is used the same as C for
> atomic_read(), so Rust and C are on the same boat.

That's fair enough.

Longer term, I understand the need for claiming "it's all fine", but
IMHO none of this is fine until compilers (both for C and Rust)
promise the semantics that the LKMM wants. Nothing new per-se, the
only new thing here that makes me anxious is that we do not understand
the real impact of this lack of guarantee on Linux Rust code (the C
side remains unclear, too, but has a lot more flight miles). Perhaps
the work originally investigating broken dependency ordering in Clang,
could be used to do a study on Rust in the kernel, too.

Alice Ryhl

unread,
Jan 21, 2026, 7:19:10 AMJan 21
to Gary Guo, Marco Elver, Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
Like in the other thread, I still think this is a mistake. Let's be
explicit about intent and call things that they are.
https://lore.kernel.org/all/aXDCTvyn...@google.com/

> If the idea is to add an explicit `Consume` ordering on the Rust side to
> document the intent clearly, then I am actually somewhat in favour.
>
> This way, we can for example, map it to a `READ_ONCE` in most cases, but we can
> also provide an option to upgrade such calls to `smp_load_acquire` in certain
> cases when needed, e.g. LTO arm64.

It always maps to READ_ONCE(), no? It's just that on LTO arm64 the
READ_ONCE() macro is implemented like smp_load_acquire().

> However this will mean that Rust code will have one more ordering than the C
> API, so I am keen on knowing how Boqun, Paul, Peter and others think about this.

On that point, my suggestion would be to use the standard LKMM naming
such as rcu_dereference() or READ_ONCE().

I'm told that READ_ONCE() apparently has stronger guarantees than an
atomic consume load, but I'm not clear on what they are.

Alice

Marco Elver

unread,
Jan 21, 2026, 7:36:45 AMJan 21
to Alice Ryhl, Gary Guo, Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
On Wed, 21 Jan 2026 at 13:19, Alice Ryhl <alic...@google.com> wrote:
[...]
> > On the Rust-side documentation we mentioned that `Relaxed` always preserve
> > dependency ordering, so yes, it is closer to `consume` in the C11 model.
>
> Like in the other thread, I still think this is a mistake. Let's be
> explicit about intent and call things that they are.
> https://lore.kernel.org/all/aXDCTvyn...@google.com/
>
> > If the idea is to add an explicit `Consume` ordering on the Rust side to
> > document the intent clearly, then I am actually somewhat in favour.

That'd be a mistake, too, as the semantics is not equivalent to "C++
consume" either, but arguably closer to it than "C++ relaxed" (I
clearly got confused by the Linux Rust Relaxed != Normal Rust
Relaxed).
It's also known that consume or any variant of it, has been deemed
unimplementable, since the compiler would have to be able to reason
about whole-program dependency chains.

> > This way, we can for example, map it to a `READ_ONCE` in most cases, but we can
> > also provide an option to upgrade such calls to `smp_load_acquire` in certain
> > cases when needed, e.g. LTO arm64.
>
> It always maps to READ_ONCE(), no? It's just that on LTO arm64 the
> READ_ONCE() macro is implemented like smp_load_acquire().
>
> > However this will mean that Rust code will have one more ordering than the C
> > API, so I am keen on knowing how Boqun, Paul, Peter and others think about this.
>
> On that point, my suggestion would be to use the standard LKMM naming
> such as rcu_dereference() or READ_ONCE().
>
> I'm told that READ_ONCE() apparently has stronger guarantees than an
> atomic consume load, but I'm not clear on what they are.

It's also meant to enforce ordering through control-dependencies, such as:

if (READ_ONCE(x)) WRITE_ONCE(y, 1);

Boqun Feng

unread,
Jan 21, 2026, 7:51:55 AMJan 21
to Marco Elver, Alice Ryhl, Gary Guo, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
On Wed, Jan 21, 2026 at 01:36:04PM +0100, Marco Elver wrote:
[..]
> >
> > > However this will mean that Rust code will have one more ordering than the C
> > > API, so I am keen on knowing how Boqun, Paul, Peter and others think about this.
> >
> > On that point, my suggestion would be to use the standard LKMM naming
> > such as rcu_dereference() or READ_ONCE().

I don't think we should confuse Rust users that `READ_ONCE()` has
dependency orderings but `atomc_load()` doesn't. They are the same on
the aspect. One of the reasons that I don't want to introduce
rcu_dereference() and READ_ONCE() on Rust side is exactly this, they are
the same at LKMM level, so should not be treated differently.

> >
> > I'm told that READ_ONCE() apparently has stronger guarantees than an
> > atomic consume load, but I'm not clear on what they are.
>
> It's also meant to enforce ordering through control-dependencies, such as:
>
> if (READ_ONCE(x)) WRITE_ONCE(y, 1);

Note that it also applies to atomic_read() and atomic_set() as well.

Regards,
Boqun

Alice Ryhl

unread,
Jan 21, 2026, 8:07:39 AMJan 21
to Boqun Feng, Marco Elver, Gary Guo, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
On Wed, Jan 21, 2026 at 08:51:48PM +0800, Boqun Feng wrote:
> On Wed, Jan 21, 2026 at 01:36:04PM +0100, Marco Elver wrote:
> [..]
> > >
> > > > However this will mean that Rust code will have one more ordering than the C
> > > > API, so I am keen on knowing how Boqun, Paul, Peter and others think about this.
> > >
> > > On that point, my suggestion would be to use the standard LKMM naming
> > > such as rcu_dereference() or READ_ONCE().
>
> I don't think we should confuse Rust users that `READ_ONCE()` has
> dependency orderings but `atomc_load()` doesn't. They are the same on
> the aspect. One of the reasons that I don't want to introduce
> rcu_dereference() and READ_ONCE() on Rust side is exactly this, they are
> the same at LKMM level, so should not be treated differently.

That's okay with me - I just don't think "relaxed" is a good name for
atomic_load() if that's the case.

> > > I'm told that READ_ONCE() apparently has stronger guarantees than an
> > > atomic consume load, but I'm not clear on what they are.
> >
> > It's also meant to enforce ordering through control-dependencies, such as:
> >
> > if (READ_ONCE(x)) WRITE_ONCE(y, 1);
>
> Note that it also applies to atomic_read() and atomic_set() as well.

Just to be completely clear ... am I to understand this that READ_ONCE()
and the LKMM's atomic_load() *are* the exact same thing? Because if so,
then this was really confusing:

> my argument was not about naming, it's
> about READ_ONCE() being more powerful than atomic load (no, not because
> of address dependency, they are the same on that, it's because of the
> behaviors of them regarding a current access on the same memory
> location)
> https://lore.kernel.org/all/aWuV858w...@tardis-2.local/

Are they the *exact* same thing or not? Do you mean that they are the
same under LKMM, but different under some other context?

Alice

Alice Ryhl

unread,
Jan 21, 2026, 8:09:36 AMJan 21
to Marco Elver, Boqun Feng, Gary Guo, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
We did already have discussions with the Rust compiler folks about this
topic, and they said that they are comfortable with Rust doing the exact
same hacks as C since that should "work" in Rust for the same reasons it
"works" in C.

Alice

Gary Guo

unread,
Jan 21, 2026, 8:42:11 AMJan 21
to Alice Ryhl, Gary Guo, Marco Elver, Boqun Feng, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
If we split out two separate orderings then we can make things that don't need
dependency ordering not be upgraded to `smp_load_acquire` and still be
implemented using volatile read.

>
>> However this will mean that Rust code will have one more ordering than the C
>> API, so I am keen on knowing how Boqun, Paul, Peter and others think about this.
>
> On that point, my suggestion would be to use the standard LKMM naming
> such as rcu_dereference() or READ_ONCE().
>
> I'm told that READ_ONCE() apparently has stronger guarantees than an
> atomic consume load, but I'm not clear on what they are.

The semantic is different for a 64-bit read on 32-bit platforms; our
`Atomic::from_ptr().load()` will be atomic (backed by atomic64 where `READ_ONCE`
will tear) -- so if you actually want a atomicity then `READ_ONCE` can be a
pitfall.

On the other hand, if you don't want atomicity (and dependency ordering), e.g.
just doing MMIO read / reading DMA allocation where you only need the "once"
semantics of `READ_ONCE`, then `READ_ONCE` provides you with too much guarantees
that you don't care about.

We'd better not to mix them together, because confusion lead to bugs. I have
described such an example in the HrTimer expires patch where the code assumes
`READ_ONCE()` to be atomic and it actually could break in 32-bit systems, but
probably nobody noticed because 32-bit systems using DRM is rare and the race
condition is hard to trigger.

My suggestion is just use things with atomic in its name for anything that
requires atomicity or ordering, and UB-free volatile access to
`read_volatile()`.

Best,
Gary

Boqun Feng

unread,
Jan 21, 2026, 9:39:54 AMJan 21
to Marco Elver, Gary Guo, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
On Wed, Jan 21, 2026 at 01:13:57PM +0100, Marco Elver wrote:
I don't disagree and share the similar concern as you do.

> [..]
> > > However, given "Relaxed" for the Rust side is already defined to
> > > "carry dependencies" then in isolation my original comment is moot and
> > > does not apply to this particular patch. At face value the promised
> > > semantics are ok, but the implementation (just like "volatile" for C)
> > > probably are not. But that appears to be beyond this patch, so feel
> >
> > Implementation-wise, READ_ONCE() is used the same as C for
> > atomic_read(), so Rust and C are on the same boat.
>
> That's fair enough.
>
> Longer term, I understand the need for claiming "it's all fine", but
> IMHO none of this is fine until compilers (both for C and Rust)
> promise the semantics that the LKMM wants. Nothing new per-se, the
> only new thing here that makes me anxious is that we do not understand
> the real impact of this lack of guarantee on Linux Rust code (the C
> side remains unclear, too, but has a lot more flight miles). Perhaps
> the work originally investigating broken dependency ordering in Clang,
> could be used to do a study on Rust in the kernel, too.

You mean this:

https://lpc.events/event/16/contributions/1174/

? If so, that'll be great! I believe if we could learn about how Rust
compiler can mess up with dependency ordering, it'll be a very helpful
resource to understand how we can work with Rust compiler to resolve it
in a pratical way.

I believe that work was LLVM-based, so it should apply to Rust code as
well, except that we may need to figure out what optmization the Rust
front end would do at MIR -> LLVM IR time that could affect dependency
orderings.

Regards,
Boqun

Boqun Feng

unread,
Jan 21, 2026, 10:54:31 AMJan 21
to Alice Ryhl, Marco Elver, Gary Guo, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, linux-...@vger.kernel.org, kasa...@googlegroups.com, Will Deacon, Peter Zijlstra, Mark Rutland, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Danilo Krummrich, Elle Rhumsaa, Paul E. McKenney, FUJITA Tomonori
On Wed, Jan 21, 2026 at 01:07:34PM +0000, Alice Ryhl wrote:
> On Wed, Jan 21, 2026 at 08:51:48PM +0800, Boqun Feng wrote:
> > On Wed, Jan 21, 2026 at 01:36:04PM +0100, Marco Elver wrote:
> > [..]
[...]
> >
> > Note that it also applies to atomic_read() and atomic_set() as well.
>
> Just to be completely clear ... am I to understand this that READ_ONCE()
> and the LKMM's atomic_load() *are* the exact same thing? Because if so,
> then this was really confusing:
>
> > my argument was not about naming, it's
> > about READ_ONCE() being more powerful than atomic load (no, not because
> > of address dependency, they are the same on that, it's because of the
> > behaviors of them regarding a current access on the same memory
> > location)
> > https://lore.kernel.org/all/aWuV858w...@tardis-2.local/
>
> Are they the *exact* same thing or not? Do you mean that they are the
> same under LKMM, but different under some other context?

Right, they are the same thing under LKMM when used for inter-thread
synchronization when they are atomic. But when READ_ONCE() (and
__READ_ONCE()) used on types that are larger than machine word size,
they are not guaranteed to be atomic, hence semantics-wise READ_ONCE()
firstly guarantees "once" (volatile and no data race with WRITE_ONCE())
and then on certain types, it's atomic as well.

* In the case that we need atomicity, we should just use atomic_load().
* In the case that we don't need atomicity and no data race, we can just
use read_volatile().
* In the case that there is a non-atomic concurrent write, that suggests
we have a bug in C code or we simply should have some other
synchronization. Or if we believe the conncurrent write has at last
per-byte atomicity, then we can have `READ_ONCE()`-like function that
returns a `MaybeUninit` to bear the possible tearing.

Regards,
Boqun

>
> Alice
Reply all
Reply to author
Forward
0 new messages