Read_once(), Write_once(), but Not for Rust
Does rust have or need the equivalent of std::memory_order_consume? Famously this was deemed unimplementable in C++.
But the article says that the suggestion is to convert them to relaxed loads. Is the expectation to YOLO it and hope that the compiler doesn't break control and data dependencies?
edit: and those rules where so complex that compilers decided where not implementable or not worth it.
It is nasty, but it's very similar to how Linux does it (volatile read + __asm__("") compiler barrier).
I guess in practice mainstream compilers do not do it and relaxed+signal fence works for now, but the fact that compilers have been reluctant to use it to implement consume means that they are reluctant to commit to it.
In any case I think you work on GCC, so you probably know the details better than me.
edit: it seems that ARM specifically does not respect control dependencies. But I might misreading the MM.
The only place where consume matters is on relaxed but not too relaxed architectures like ARM and POWER, where consume relies on the implicit #LoadLoad of controls and data dependencies.
In a TSO architecture like x86 or SPARC, every "regular" memory load/store is effectively a release/acquire by default. Using release/consume or relaxed provides no extra speedup on these architectures. In weak memory models, you need to add in acquire barriers to get release/acquire architectures. But also, most weak memory models have a basic rule that a data-dependent load has an implicit ordering dependency on the values that computed it (most notably, loading *p has an implicit dependency on p).
The goal of release/consume is to be able to avoid having an acquire fence if you have only those dependencies--to promote a hardware data dependency semantic rule to a language-level semantic rule. For Alpha's ultra-weak model, you still need the acquire fence in this mode, it doesn't help Alpha one whit. Unfortunately, for various reasons, no one has been able to work out a language-level semantics for consume that compilers are willing to implement (preserving data dependencies through optimizations is a lot more difficult than it appears), so all compilers have remapped consume to acquire, making it useless.
There are a couple of interesting implications from this outcome, should it hold. The first of those is that, as Rust code reaches more deeply into the core kernel, its code for concurrent access to shared data will look significantly different from the equivalent C code, even though the code on both sides may be working with the same data. Understanding lockless data access is challenging enough when dealing with one API; developers may now have to understand two APIs, which will not make the task easier.
The thing is, it'll be far less challenging for the Rust code, which will actually define the ordering semantics explicitly. That's the point of rejecting the READ_ONCE/WRITE_ONCE approach - it's unclear what the goal is when using those, what guarantee you actually want.
I suspect that if Rust continues forward with this approach it will basically end up as the code where someone goes to read the actual semantics to determine what the C code should do.
Most common cases I see are:
1. I'm sharing data between concurrent contexts but they are all on the same CPU (classic is sharing a percpu variable between IRQ and task).
2. I'm reading some isolated piece of data that I know can change any time, but it doesn't form part of a data structure or anything, it can't be "in an inconsistent state" as long as I can avoid load-tearing (classic case: a performance knob that gets mutated via sysfs). I just wanna READ it ONCE into a local variable, so I can do two things with it and know they both operate with the same value.
I actually don't think C++ or Rust have existing semantics that satisfy this kinda thing? So will be interesting to see what they come up with.
({READ,WRITE}_ONCE() only lets the compiler reorder the accesses if they happen in the same C statement).
I think C++ doesn't have an equivalent because it just doesn't make sense as a primitive in very many environments.
I suspect that if Rust continues forward with this approach it will basically end up as the code where someone goes to read the actual semantics to determine what the C code should do.
That will also put it on the unfortunate position of being the place that breaks every time somebody adds a bug to the C code.
Anyway, given the cultures involved, it's probably inevitable.
That will also put it on the unfortunate position of being the place that breaks every time somebody adds a bug to the C code.
Can someone explain charitably what the poster is getting at? To me, the above makes zero sense. If the Rust code is what is implemented correctly, and has the well-defined semantics, then, when the C code breaks, it's obviously the C code's problem?
The truth of the matter, though, is that the Rust community seems to want to take a different approach to concurrent data access.
Not knowing anything about development of the kernel, does this kind of thing create a two tier Linux development experience?
edit to add: and I'm not talking about compilation failures so much as design problems. when the meaning of a value is overloaded, or when there's a "you must do Y after X and never before" and then you can't write equivalent code in all cases, and so on. "but what does this mean?" becomes the question to answer.
An understanding of READ_ONCE() and WRITE_ONCE() is important for kernel developers who will be dealing with any sort of concurrent access to data. So, naturally, they are almost entirely absent from the kernel's documentation.
Made me chuckle. /*
* Yes, this permits 64-bit accesses on 32-bit architectures. These will
* actually be atomic in some cases (namely Armv7 + LPAE), but for others we
* rely on the access being split into 2x32-bit accesses for a 32-bit quantity
* (e.g. a virtual address) and a strong prevailing wind.
*/Read_once and Write_once convey that there's more nuance than that, and tries to convey the nuance.
[1] E.g. in rust anything that takes https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html
[1] https://www.alilleybrinker.com/mini/rusts-culture-of-semanti...