Live data from Hacker News

Read_once(), Write_once(), but Not for Rust

lwn.net

21–30 of 41 posts

Re: Read_once(), Write_once(), but Not for Rust

#21
post #5

Earlier quoted context omitted.

Not sure if it introduces a tiered experience or not. But reading the article it appears that the Rust devs advocated for an api that is clearer in it's semantics with the tradeoff that now understanding how it interacts with C code requires understanding two APIs. How this shakes out in practice remains to be seen.

Advocating for an API with clearer semantics has, afaict, been most of the actual work of integrating Rust into the kernel.

That is my understanding from the outside as well. The core question here should, I think, be whether the adoption and spread of clearer semantics via Rust is worth the potential for confusion and misunderstandings at the boundaries between C and Rust. From the article it appears that this specific instance actually resulted in identifying issues in the usage of the C api's here that are geting scrutiny and fixes as a result. That would indicate the introduction of Rust is causing the trend line to go in the correct direction in at least this instance.

Re: Read_once(), Write_once(), but Not for Rust

#22
post #21

Earlier quoted context omitted.

Advocating for an API with clearer semantics has, afaict, been most of the actual work of integrating Rust into the kernel.

That is my understanding from the outside as well. The core question here should, I think, be whether the adoption and spread of clearer semantics via Rust is worth the potential for confusion and misunderstandings at the boundaries between C and Rust. From the article it appears that this specific instance actually resulted in identifying issues in the usage of the C api's here that are geting scrutiny and fixes as…

That's been largely my experience of RIIR over years of work in numerous contexts: attempting to encode invariants in the type system results in identifying semantic issues. over and over.

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.

Re: Read_once(), Write_once(), but Not for Rust

#23

> 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…

> 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.

Re: Read_once(), Write_once(), but Not for Rust

#24
post #8

Earlier quoted context omitted.

Is that any better than just using an acquire load?

It is cheaper on ARM and POWER. But I'm not sure it is always safe. The standard has very complex rules for consume to make sure that the compiler didn't break the dependencies. edit: and those rules where so complex that compilers decided where not implementable or not worth it.

The rules were there to explain what optimizations remained possible. Here no optimization is possible at the compiler level, and only the processor retains freedom because we know it won't use it.

It is nasty, but it's very similar to how Linux does it (volatile read + __asm__("") compiler barrier).

Re: Read_once(), Write_once(), but Not for Rust

#25

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.

More chuckles from the source: /* * 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. */

[flagged]

Re: Read_once(), Write_once(), but Not for Rust

#26

Very interesting. AFAIK the kernel explicitly gives consume semantics to read_once (and in fact it is not just a compiler barrier on alpha), so technically lowering it to a relaxed operation is wrong. Does rust have or need the equivalent of std::memory_order_consume? Famously this was deemed unimplementable in C++.

It wasn’t implemented for the same reason. Rust uses C++20 ordering.

C++20 actually [changed the semantics of consume](https://devblogs.microsoft.com/oldnewthing/20230427-00/?p=10...), but Rust doesn't include it. And last I remember compilers still treat it as acquire, so it's not worth the bytes it's stored in.

Re: Read_once(), Write_once(), but Not for Rust

#27

> 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…

> 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?

Re: Read_once(), Write_once(), but Not for Rust

#28
post #24

Earlier quoted context omitted.

It is cheaper on ARM and POWER. But I'm not sure it is always safe. The standard has very complex rules for consume to make sure that the compiler didn't break the dependencies. edit: and those rules where so complex that compilers decided where not implementable or not worth it.

The rules were there to explain what optimizations remained possible. Here no optimization is possible at the compiler level, and only the processor retains freedom because we know it won't use it. It is nasty, but it's very similar to how Linux does it (volatile read + __asm__("") compiler barrier).

This is still unsound (in both C and Rust), because the compiler can break data dependencies by e.g. replacing a value with a different value known to be equal to it. A compiler barrier doesn't prevent this. (Neither would a hardware barrier, but with a hardware barrier it doesn't matter if data dependencies are broken.) The difficulty of ensuring the compiler will never break data dependencies is why compilers never properly implemented consume. Yet at the same time, this kind of optimization is actually very rare in non-pathological code, which is why Linux has been able to get away with assuming it won't happen.

Re: Read_once(), Write_once(), but Not for Rust

#30

> 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…

In my experience, in practice, it usually isn't that hard to figure out what people meant by a READ/WRITE_ONCE().

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.

Post reply on HN