Continuation of Volatile atomic operations? - help - The Rust Programming Language Forum.
C/++ atomics model allows for volatile atomic operations. They’re required when using atomics in volatile memory, e.g. when atomic is in shared memory and is used to synchronize between processes. Currently Rust doesn’t allow volatile atomics, so the only way to do volatile atomic accesses is by using inline assembly. That feels suboptimal.
I see several ways to support volatile atomics in the language:
Just duplicate everything
We could duplicate all the atomic types, i.e. make VolatileAtomicU8
-like types. Alternatively, we could duplicate all the methods, i.e. make .fetch_add_volatile()
-like methods. Both these paths lead to even more code duplication, and, worse, docs duplication near atomics.
Wrapper type
Make a Volatile<T>
wrapper type that makes accesses to the wrapped value volatile. This could allow for Volatile<AtomicU8>
to implement volatile atomic methods. It still kinda duplicates all the methods (unless we add a (private?) trait to abstract over atomics), but it will look better in docs. Potential source for confusion: Volatile<u8>
being a wrapper and AtomicU8
being a separate type may feel kinda arbitrary at the first glance. Upside: such a wrapper could be useful with non-atomic values too.
Special orderings
pub struct OrderingPlusVolatileness {
pub ordering: Ordering,
pub volatile: bool,
}
impl From<Ordering> for OrderingPlusVolatileness {
fn from(ordering: Ordering) -> Self {
Self { ordering, volatile: false }
}
}
impl Ordering {
fn volatile(self) -> OrderingPlusVolatileness {
OrderingPlusVolatileness { ordering: self, volatile: true }
}
}
impl AtomicU8 {
fn fetch_add(self, val: u8, ordering: impl Into<OrderingPlusVolatileness>);
}
That’s not quite backwards-compatible (consider e.g. use_fn_ptr(AtomicU8::fetch_add)
), but I think it could be done in the new edition (crates with old editions will see old non-generic methods).
Pros: looks kinda nice at the call site (x.fetch_add(1, Ordering::Relaxed.volatile())
), almost backwards-compatible, doesn’t create API duplication.
Cons: conflates two nearly unrelated things into one value, not really backwards-compatible.
Fences
volatile_load_fence(&x); // Ensure _next_ load is volatile.
x.fetch_add(1, Ordering::Relaxed);
volatile_store_fence(&x); // Ensure _previous_ store is volatile.
I’m not sure this is easy/possible to implement? But this option has by far smallest API surface, which I think is nice. It’s also completely backwards compatible and not that unpleasant to use (although most useful atomic operations would require two fences, as shown above).
Variation:
// Adds fences above and below.
x.with_volatile(|| {
x.fetch_add(1, Ordering::Relaxed);
});
Don’t Do Anything
Always an option. Volatile atomic accesses remain only accessible (pun not intended) via inline assembly, which is non-portable. Upside: API remains simple and discoverable for the by far most common usecase (non-volatile atomics).
Anything else?
I’m quite interested in pushing this story forward. I’m willing to write an RFC and try to implement this, although I’ll probably need a mentor for a change this complex (I think it would involve libs, MIR optimizations and codegen?).