The documentation for write_volatile
says:
Volatile operations are intended to act on I/O memory, and are guaranteed to not be elided or reordered by the compiler across other volatile operations.
I'm also assuming it's also not allowed to duplicate operations, or generate spurious stores.
For primitive types, I assume it will use a store instruction which matches the size of the type where possible, which will result in a correspondingly sized bus operation. (There's still a question of how primitive types that are too large for the hardware are handled, like u64 on a 32 bit CPU - but I'll leave that for now.)
(Other questions which I'm leaving aside for now: what is "I/O memory" precisely, whether write_volatile
makes any guarantees on visible store ordering, or specifically sized bus transactions.)
But what are the semantics for compound types?
Structs
If T
is a struct, will it treat this as a single monolithic object with a given size, and generate as few store operations as possible, or will it write each separate field as a distinct volatile store operation?
In the first case, what happens if the structure has padding? Will it also generate stores for the padding fields? Typically these are considered uninitialized, and accessing their memory explicitly is UB, but the implementation can choose to read/write them if its more convenient to do so. But if it's a write_volatile
, then generating store instructions which cover uninitialized padding will effectively be generating spurious stores which don't correspond to any Rust-level object, possibly resulting in unintended behaviour.
This can be resolved by treating each field separately, so that they're individually stored in a volatile way. But this raises the question of order since "volatile operations are [...] guaranteed to not be [...] reordered by the compiler across other volatile operations". So what's the canonical order of the fields? Is it the textual order in the source, or the layout the compiler chooses for the structure? (Any case where the precise layout matters will use #[repr(C)]
, so maybe this is moot, as a matter of specification it would be good to clarify.)
Or is it simply that if you do a write_volatile
on a structure then you're giving up all this fine-grained control, and you should do your own per-field volatile stores if it really matters to you?
Arrays
Likewise with arrays, if T
is an array or slice, will it attempt to write the whole thing as a single object, or each element individually? The same questions about padding apply to gaps between elements. And if each element is treated as a separate store, what order are they stored in? Is it guaranteed to be low index to high?
Or, like structures, if you're doing volatile writes on a whole array then you're giving up control, and you should write each element individually if you care? (And if its an array of structures, each field in each element.)
(Others)
I'm not as concerned about tuples/enums/unions since they're much less likely to be used in a way where the exact volatile semantics matter, but it would be nice to resolve all these questions for them as well for completeness.