use {
core::ptr,
std::{sync::Arc, thread},
};
const ONE: u128 = 1;
const MAX: u128 = u128::MAX;
fn main() {
let a = Arc::new(0u128);
for _ in 0..32 {
let a = a.clone();
thread::spawn(move || {
let p = ptr::addr_of!(*a) as *mut u128;
loop {
unsafe { *p = ONE }
}
});
}
for _ in 0..32 {
let a = a.clone();
thread::spawn(move || {
let p = ptr::addr_of!(*a) as *mut u128;
loop {
unsafe { *p = MAX }
}
});
}
thread::sleep(std::time::Duration::from_secs(3));
loop {
if !(*a == ONE || *a == MAX) {
panic!("wrong number {a}")
}
}
}
Used target-cpu=native cargo run --release --offline and didn't panic, which means no dirty data. In what situations it's safe without Mutex?
It's not safe - you're depending on a data race being defined the way you want it to.
I put the code into the Rust Playground and used Miri (under the Tools menu on the right) to examine it.
Miri says:
error: Undefined Behavior: Data race detected between (1) retag read on thread `main` and (2) non-atomic write on thread `unnamed-1` at alloc1334+0x10. (2) just happened here
--> src/main.rs:14:26
|
14 | unsafe { *p = ONE }
| ^^^^^^^^ Data race detected between (1) retag read on thread `main` and (2) non-atomic write on thread `unnamed-1` at alloc1334+0x10. (2) just happened here
|
help: and (1) occurred earlier here
--> src/main.rs:10:17
|
10 | let a = a.clone();
| ^^^^^^^^^
= help: retags occur on all (re)borrows and as well as when references are copied or moved
= help: retags permit optimizations that insert speculative reads or writes
= help: therefore from the perspective of data races, a retag has the same implications as a read or write
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE (of the first span) on thread `unnamed-1`:
= note: inside closure at src/main.rs:14:26: 14:34
And the link about Undefined Behavior says that data races are unconditionally undefined, hence the program's behaviour is undefined and you can say nothing about its behaviour.
No; it's about how the Rust language is defined. If you use unsafe, and you do anything that is Undefined Behaviour inside an unsafe block, then your program can take on any meaning as far as the compiler is concerned; it could do what you expect, but there is no guaranteed meaning to your program, and the compiler is perfectly correct if it decides to (for example) set *a to 42 on days when the Pope is outside Vatican City.
Once you have UB in your program, it's impossible to reason about, because the definition of UB is "when UB is executed, the entire program execution, including all steps before UB, is undefined and can be anything".
Rust uses LLVM to generate code. LLVM optimizer does not compile code for your target CPU, even with the target-cpu=native. It compiles code for an abstract low-level virtual machine that has its own quirky semantics, which will be simulated on your target CPU if LLVM feels like it. It means that if your program violates rules of LLVM, it may be miscompiled and behave in bizarre ways that are not the same as what you would imagine from some direct translation of the program to your CPU.
You’re not allowed to cause language’s Undefined Behavior even if your CPU defines it.