There are a couple of discussions about how to do MMIO, interprocess-shared memory, etc. efficiently, and with guaranteed semantics without introducing undefined behavior.
The main issue with ptr::read/write_volatile<T>
is that their semantics aren’t precisely specified yet. While we should fix that, the semantics do depend on the actual T
being read, the architecture being targeted, whether the pointer is aligned or not (right now ptr::read/write_volatile<T>
does not support unaligned read or writes, but we could add _unaligned
variants to support that).
One of the options that we have available is to add intrinsics with guaranteed semantics for each architecture to core::arch
. For example, for x86_64
, it could look like this:
/// (Module documentation)
///
/// Semantics of volatile memory operations: volatile memory
/// operations (reads and writes) must be emitted by the
/// compiler and cannot be re-ordered across other volatile
/// operations.
///
/// The result of a volatile read is frozen (`freeze` is applied to it).
/// That is, reading uninitialized memory via a volatile read never
/// returns uninitialized memory, the result of the read is picked
/// non-deterministically.
///
/// When volatile reads participate in data-races with any write
/// operation, the result of what is read is picked non-deterministically.
/// When volatile writes participate in data-races with other
/// volatile-write operations, the result that is written is picked
/// non-deterministically. If volatile writes participate in data-races
/// with other write operations, the behavior is undefined.
/// A race betweeen:
///
/// * a volatile atomic read with volatile atomic writes reads the
/// content of memory either before or after any of the writes - the
/// read will not observe partial modifications because the reads
/// and the writes are atomic
/// * a volatile atomic write with other volatile atomic writes results
/// in the content of any of the writes being written to memory - the
/// memory won't contain partial results of the different writes
///
/// Non-atomic volatile operation perform the reads and writes as a
/// sequence of smaller volatile atomic reads or writes. Data races
/// result in partial results being read from or written to memory.
/// Volatile atomic 8-bit load
///
/// 8-bit volaitle atomic load from `x`.
///
/// If the load introduces a data-race, the result is picked
/// non-deterministically.
unsafe fn volatile_atomic_load_u8(x: *const u8) -> u8;
/// Volatile aligned atomic load u16/u32/u64
///
/// 16/32/64-bit volatile atomic load from aligned `x`.
///
/// If the load introduces a data-race, the result is picked
/// non-deterministically.
///
/// If `x` is not aligned, the behavior is undefined.
unsafe fn volatile_atomic_load_u16(x: *const u16) -> u16;
unsafe fn volatile_atomic_load_u32(x: *const u32) -> u32;
unsafe fn volatile_atomic_load_u64(x: *const u64) -> u64;
/// Volatile unaligned load u16/u32/u64/u128/256/512
///
/// Volatile 16/32/64/128/256/512-bit unaligned load.
///
/// This operation is not necessarily a single atomic load.
/// The memory is read in a data-race free way by performing
/// either a single volatile atomic load, or multiple smaller volatile
/// atomic loads in an unspecified order .
///
/// If the load introduces a data-race, the result is picked
/// non-deterministically.
unsafe fn volatile_load_u16_unaligned(x: *const u16) -> u16;
unsafe fn volatile_load_u32_unaligned(x: *const u32) -> u32;
unsafe fn volatile_load_u64_unaligned(x: *const u64) -> u64;
#[target_feature(enable = "sse")]
unsafe fn volatile_load_u128_unaligned(x: *const u128) -> u128;
#[target_feature(enable = "avx")]
unsafe fn volatile_load_256_unaligned(x: *const [u8; 32]) -> [u8; 32];
#[target_feature(enable = "avx512")]
unsafe fn volatile_load_512_unaligned(x: *const [u8; 64]) -> [u8; 64];
/// Volatile atomic 8-bit write
///
/// 8-bit volaitle atomic write of `x` to `ptr`.
///
/// If there is a data-race with another volatile atomic write to `ptr`,
/// the memory written to `ptr` is picked
/// non-deterministically.
unsafe fn volatile_atomic_write_u8(x: *mut ptr, x: u8);
/// Volatile aligned atomic write u16/u32/u64
///
/// 16/32/64-bit volatile atomic wrote of `x` to `ptr`.
///
/// If there is a data-race with another volatile atomic write to `ptr`,
/// the memory written to `ptr` is picked non-deterministically.
///
/// If `x` is not aligned, the behavior is undefined.
unsafe fn volatile_atomic_write_u16(ptr: *mut u16, x: u16);
unsafe fn volatile_atomic_write_u32(ptr: *mut u32, x: u32);
unsafe fn volatile_atomic_write_u64(ptr: *mut u64, x: u64);
/// Volatile unaligned write u16/u32/u64/u128/256/512
///
/// Volatile 16/32/64/128/256/512-bit unaligned write of `x` to `ptr`.
///
/// This operation is not necessarily a single atomic write. The
/// memory is written to `ptr` in a data-race free way by performing
/// either a single volatile atomic write, or multiple
/// smaller volatile atomic writes in an unspecified order .
///
/// If there is a data-race with another volatile write to `ptr`,
/// the memory written to `ptr` is picked non-deterministically.
unsafe fn volatile_write_u16_unaligned(ptr: *mut u16, x: u16);
unsafe fn volatile_write_u32_unaligned(ptr: *mut u32, x: u32);
unsafe fn volatile_write_u64_unaligned(ptr: *mut u64, x: u64);
// Optionally:
#[target_feature(enable = "sse")]
unsafe fn volatile_load_128(x: *const [u8; 16]) -> __m128;
#[target_feature(enable = "avx")]
unsafe fn volatile_load_256(x: *const [u8; 32]) -> __m256;
#[target_feature(enable = "avx512")]
unsafe fn volatile_load_512(x: *const [u8; 64]) -> __m512;
Users could then either use these intrinsics directly to get guaranteed semantics, or use them to build more generic abstractions in a library.
For example, one could use these to build a generic read_volatile_atomic<T: SizeBoundt>
API in a library that rejects reads not supported by the target, e.g., by only implementing the trait SizeBound
for , e.g., u64
, on targets that support the operation on that type.