Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can we allow read-read races between atomic and non-atomic accesses? #483

Closed
RalfJung opened this issue Dec 11, 2023 · 1 comment · Fixed by rust-lang/rust#128778
Closed

Comments

@RalfJung
Copy link
Member

Currently, we document read-read races where one access is atomic and one is not to be UB. This is born out of the fact that one cannot write code in C++ that has such a race: if one creates an atomic_ref so perform atomic accesses to an object previously used non-atomically, then for as long as the atomic_ref exists, one may not use non-atomic accesses at all.

However, this is a very unfortunate limitation. First of all it means replacing a non-atomic access by an atomic one can actually introduce UB (even if there are no alignment issues), which is silly. Secondly it defies intuition. I do think we should allow such code. However, then we have to change the way we document our atomics; specifically, this part:

Rust atomics currently follow the same rules as C++20 atomics, specifically atomic_ref. Basically, creating a shared reference to one of the Rust atomic types corresponds to creating an atomic_ref in C++; the atomic_ref is destroyed when the lifetime of the shared reference ends.

My hope is that we can still use the C++ memory model, but instead of documenting which C++ library functions our Rust operations correspond to, we "link directly with the memory model" in the sense of documenting that certain Rust operations correspond to certain operations directly in the C++ model (reads/writes/RMWs). I'll have to look more closely at how the C++ standard documents the memory model to understand how this could be done; so far I've only read the CS papers that formalize this model. ;)

@gonzalobg
Copy link

The PTX memory model allows these races. The fact that these races are not allowed in C++, is not due to the C++ memory model, but due to pre-conditions in the C++ atomic and atomic_ref abstractions. These preconditions are there to enable the implementation to fake atomic memory operations using locks.

A lower-level software-exposure for these operations that guarantees "either lock-freedom or fails (to compile or at runtime)" would not need this limitation.

rust-timer added a commit to rust-lang-ci/rust that referenced this issue Sep 28, 2024
Rollup merge of rust-lang#128778 - RalfJung:atomic-read-read-races, r=Mark-Simulacrum

atomics: allow atomic and non-atomic reads to race

We currently define our atomics in terms of C++ `atomic_ref`. That has the unfortunate side-effect of making it UB for an atomic and a non-atomic read to race (concretely, [this code](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d1a743774e60923db33def7fe314d754) has UB). There's really no good reason for this, all the academic models of the C++ memory model I am aware of allow this -- C++ just disallows this because of their insistence on an "object model" with typed memory, where `atomic_ref` temporarily creates an "atomic object" that may not be accesses via regular non-atomic operations.

So instead of tying our operations to `atomic_ref`, let us tie them directly to the underlying C++ memory model. I am not sure what is the best way to phrase this, so here's a first attempt.

We also carve out an exception from the "no mixed-size atomic accesses" rule to permit mixed-size atomic reads -- given that we permit mixed-size non-atomic reads, it seems odd that this would be disallowed for atomic reads. However, when an atomic write races with any other atomic operation, they must use the same size.

With this change, it is finally the case that every non-atomic access can be replaced by an atomic access without introducing UB.

Cc `@rust-lang/opsem` `@chorman0773` `@m-ou-se` `@WaffleLapkin` `@Amanieu`

Fixes rust-lang/unsafe-code-guidelines#483
RalfJung pushed a commit to RalfJung/miri that referenced this issue Oct 3, 2024
…ulacrum

atomics: allow atomic and non-atomic reads to race

We currently define our atomics in terms of C++ `atomic_ref`. That has the unfortunate side-effect of making it UB for an atomic and a non-atomic read to race (concretely, [this code](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d1a743774e60923db33def7fe314d754) has UB). There's really no good reason for this, all the academic models of the C++ memory model I am aware of allow this -- C++ just disallows this because of their insistence on an "object model" with typed memory, where `atomic_ref` temporarily creates an "atomic object" that may not be accesses via regular non-atomic operations.

So instead of tying our operations to `atomic_ref`, let us tie them directly to the underlying C++ memory model. I am not sure what is the best way to phrase this, so here's a first attempt.

We also carve out an exception from the "no mixed-size atomic accesses" rule to permit mixed-size atomic reads -- given that we permit mixed-size non-atomic reads, it seems odd that this would be disallowed for atomic reads. However, when an atomic write races with any other atomic operation, they must use the same size.

With this change, it is finally the case that every non-atomic access can be replaced by an atomic access without introducing UB.

Cc `@rust-lang/opsem` `@chorman0773` `@m-ou-se` `@WaffleLapkin` `@Amanieu`

Fixes rust-lang/unsafe-code-guidelines#483
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants