Skip to content

Instantly share code, notes, and snippets.

@cbeuw
Last active July 13, 2022 22:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cbeuw/e5cb76b1d01b51a56fc35ff40248ab27 to your computer and use it in GitHub Desktop.
Save cbeuw/e5cb76b1d01b51a56fc35ff40248ab27 to your computer and use it in GitHub Desktop.
Read-dont-modify-write miscompilation
// From https://github.com/llvm/llvm-project/issues/56450#issuecomment-1183695905
use std::sync::atomic::fence;
use std::sync::atomic::Ordering::Acquire;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::atomic::Ordering::Release;
use std::{sync::atomic::AtomicI32, sync::atomic::AtomicBool, thread};
#[no_mangle]
pub fn rdmw(storing: &AtomicI32, sync: &AtomicI32, loading: &AtomicI32) -> i32 {
storing.store(1, Relaxed);
fence(Release);
sync.fetch_add(0, Relaxed);
fence(Acquire);
loading.load(Relaxed)
}
pub fn main() {
loop {
let x = Box::leak(Box::new(AtomicI32::new(0)));
let y = Box::leak(Box::new(AtomicI32::new(0)));
let z = Box::leak(Box::new(AtomicI32::new(0)));
// Since each thread is so short, we need to make sure that they truely run at the same time
// Otherwise t1 will finish before t2 even starts
let go = Box::leak(Box::new(AtomicBool::new(false)));
let t1 = thread::spawn(|| {
while !go.load(Relaxed) {}
rdmw(y, x, z)
});
let t2 = thread::spawn(|| {
while !go.load(Relaxed) {}
rdmw(z, x, y)
});
go.store(true, Relaxed);
let a = t1.join().unwrap();
let b = t2.join().unwrap();
assert_ne!((a, b), (0, 0));
}
}
#include <atomic>
#include <cstdint>
#include <cassert>
#include <thread>
void writer(std::atomic_uint64_t& gte, std::atomic_uint64_t& lte) {
for (uint64_t i=0;;i++) {
// Since there is only one writer and the ordering is SeqCst,
// at any point of the program execution, the latest value of lte is either equal or one less than the latest value of gte
gte.store(i, std::memory_order_seq_cst);
lte.store(i, std::memory_order_seq_cst);
}
}
void reader(std::atomic_uint64_t& gte, std::atomic_uint64_t& lte) {
/*
Since the stores to gte and lte are SeqCst, they are both part of
a Single Total Order, looking like this:
gte lte gte lte gte (yet to run)
1 1 2 2 3 ...
*/
while (true) {
/*
gte lte gte lte gte (yet to run)
1 1 2 2 3 ...
rmw on lte has to see this, not any earlier value
*/
uint64_t smaller = lte.fetch_add(0, std::memory_order_relaxed);
// The writer could increment lte and gte by any number of times
// between the two fetch_add(0). This does not matter since smaller is still <= larger
uint64_t larger = gte.fetch_add(0, std::memory_order_relaxed);
assert(larger >= smaller);
}
}
int main() {
std::atomic_uint64_t gte(0);
std::atomic_uint64_t lte(0);
std::thread t_writer(writer, std::ref(gte), std::ref(lte));
std::thread t_reader(reader, std::ref(gte), std::ref(lte));
t_writer.join();
t_reader.join();
return 0;
}
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::atomic::Ordering::SeqCst;
use std::thread;
#[no_mangle]
pub fn writer(gte: &AtomicU64, lte: &AtomicU64) {
for i in 0.. {
// Since there is only one writer and the ordering is SeqCst,
// at any point of the program execution, the latest value of lte is either equal or one less than the latest value of gte
gte.store(i, SeqCst);
lte.store(i, SeqCst);
}
}
#[no_mangle]
pub fn reader(gte: &AtomicU64, lte: &AtomicU64) {
/*
Since the stores to gte and lte are SeqCst, they are both part of
a Single Total Order, looking like this:
gte lte gte lte gte (yet to run)
1 1 2 2 3 ...
*/
loop {
/*
gte lte gte lte gte (yet to run)
1 1 2 2 3 ...
rmw on lte has to see this, not any earlier value
*/
let smaller = lte.fetch_add(0, Relaxed);
// The writer could increment lte and gte by any number of times
// between the two fetch_add(0). This does not matter since smaller is still <= larger
let larger = gte.fetch_add(0, Relaxed);
assert!(larger >= smaller, "the latest gte value {larger} is less than the latest or earlier lte value {smaller}, this is not possible at any point in the program!");
}
}
pub fn main() {
static GTE: AtomicU64 = AtomicU64::new(0);
static LTE: AtomicU64 = AtomicU64::new(0);
let t_writer = thread::spawn(|| {
writer(&GTE, &LTE);
});
let t_reader = thread::spawn(|| {
reader(&GTE, &LTE);
});
t_writer.join().unwrap();
t_reader.join().unwrap();
}
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::atomic::Ordering::SeqCst;
use std::thread;
#[no_mangle]
pub fn writer(gte: &AtomicU64, lte: &AtomicU64) {
for i in 0.. {
// Since there is only one writer and the ordering is SeqCst,
// at any point of the program execution, the latest value of lte is either equal or one less than the latest value of gte
gte.store(i, SeqCst);
lte.store(i, SeqCst);
}
}
#[no_mangle]
pub fn reader(gte: &AtomicU64, lte: &AtomicU64) {
/*
Since the stores to gte and lte are SeqCst, they are both part of
a Single Total Order, looking like this:
gte lte gte lte gte (yet to run)
1 1 2 2 3 ...
*/
loop {
/*
gte lte gte lte gte (yet to run)
1 1 2 2 3 ...
rmw on lte has to see this, not any earlier value
*/
let smaller = lte.fetch_add(0, Relaxed);
// The writer could increment lte and gte by any number of times
// between the two fetch_add(0). This does not matter since smaller is still <= larger
let larger = gte.fetch_add(0, Relaxed);
assert!(larger >= smaller, "the latest gte value {larger} is less than the latest or earlier lte value {smaller}, this is not possible at any point in the program!");
}
}
#[repr(C)]
struct Padder<T> {
_pad: [u8; 111],
val: T,
}
pub fn main() {
static GTE: AtomicU64 = AtomicU64::new(0);
static LTE: Padder<AtomicU64> = Padder {
_pad: [0; 111],
val: AtomicU64::new(0),
};
let t_writer = thread::spawn(|| {
writer(&GTE, &LTE.val);
});
let t_reader = thread::spawn(|| {
reader(&GTE, &LTE.val);
});
t_writer.join().unwrap();
t_reader.join().unwrap();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment