Put the initialisation value into the store buffer
This commit is contained in:
parent
577054c6de
commit
92145373c3
@ -533,7 +533,11 @@ fn write_scalar_atomic(
|
|||||||
let this = self.eval_context_mut();
|
let this = self.eval_context_mut();
|
||||||
this.allow_data_races_mut(move |this| this.write_scalar(val, &(*dest).into()))?;
|
this.allow_data_races_mut(move |this| this.write_scalar(val, &(*dest).into()))?;
|
||||||
this.validate_atomic_store(dest, atomic)?;
|
this.validate_atomic_store(dest, atomic)?;
|
||||||
this.buffered_atomic_write(val, dest, atomic)
|
// FIXME: it's not possible to get the value before write_scalar. A read_scalar will cause
|
||||||
|
// side effects from a read the program did not perform. So we have to initialise
|
||||||
|
// the store buffer with the value currently being written
|
||||||
|
// ONCE this is fixed please remove the hack in buffered_atomic_write() in weak_memory.rs
|
||||||
|
this.buffered_atomic_write(val, dest, atomic, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform an atomic operation on a memory location.
|
/// Perform an atomic operation on a memory location.
|
||||||
@ -556,7 +560,12 @@ fn atomic_op_immediate(
|
|||||||
|
|
||||||
this.validate_atomic_rmw(place, atomic)?;
|
this.validate_atomic_rmw(place, atomic)?;
|
||||||
|
|
||||||
this.buffered_atomic_rmw(val.to_scalar_or_uninit(), place, atomic)?;
|
this.buffered_atomic_rmw(
|
||||||
|
val.to_scalar_or_uninit(),
|
||||||
|
place,
|
||||||
|
atomic,
|
||||||
|
old.to_scalar_or_uninit(),
|
||||||
|
)?;
|
||||||
Ok(old)
|
Ok(old)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -575,7 +584,7 @@ fn atomic_exchange_scalar(
|
|||||||
|
|
||||||
this.validate_atomic_rmw(place, atomic)?;
|
this.validate_atomic_rmw(place, atomic)?;
|
||||||
|
|
||||||
this.buffered_atomic_rmw(new, place, atomic)?;
|
this.buffered_atomic_rmw(new, place, atomic, old)?;
|
||||||
Ok(old)
|
Ok(old)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -603,7 +612,12 @@ fn atomic_min_max_scalar(
|
|||||||
|
|
||||||
this.validate_atomic_rmw(place, atomic)?;
|
this.validate_atomic_rmw(place, atomic)?;
|
||||||
|
|
||||||
this.buffered_atomic_rmw(new_val.to_scalar_or_uninit(), place, atomic)?;
|
this.buffered_atomic_rmw(
|
||||||
|
new_val.to_scalar_or_uninit(),
|
||||||
|
place,
|
||||||
|
atomic,
|
||||||
|
old.to_scalar_or_uninit(),
|
||||||
|
)?;
|
||||||
|
|
||||||
// Return the old value.
|
// Return the old value.
|
||||||
Ok(old)
|
Ok(old)
|
||||||
@ -654,14 +668,14 @@ fn atomic_compare_exchange_scalar(
|
|||||||
if cmpxchg_success {
|
if cmpxchg_success {
|
||||||
this.allow_data_races_mut(|this| this.write_scalar(new, &(*place).into()))?;
|
this.allow_data_races_mut(|this| this.write_scalar(new, &(*place).into()))?;
|
||||||
this.validate_atomic_rmw(place, success)?;
|
this.validate_atomic_rmw(place, success)?;
|
||||||
this.buffered_atomic_rmw(new, place, success)?;
|
this.buffered_atomic_rmw(new, place, success, old.to_scalar_or_uninit())?;
|
||||||
} else {
|
} else {
|
||||||
this.validate_atomic_load(place, fail)?;
|
this.validate_atomic_load(place, fail)?;
|
||||||
// A failed compare exchange is equivalent to a load, reading from the latest store
|
// A failed compare exchange is equivalent to a load, reading from the latest store
|
||||||
// in the modification order.
|
// in the modification order.
|
||||||
// Since `old` is only a value and not the store element, we need to separately
|
// Since `old` is only a value and not the store element, we need to separately
|
||||||
// find it in our store buffer and perform load_impl on it.
|
// find it in our store buffer and perform load_impl on it.
|
||||||
this.perform_read_on_buffered_latest(place, fail)?;
|
this.perform_read_on_buffered_latest(place, fail, old.to_scalar_or_uninit())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the old value.
|
// Return the old value.
|
||||||
|
@ -81,11 +81,11 @@
|
|||||||
pub struct StoreBufferAlloc {
|
pub struct StoreBufferAlloc {
|
||||||
/// Store buffer of each atomic object in this allocation
|
/// Store buffer of each atomic object in this allocation
|
||||||
// Behind a RefCell because we need to allocate/remove on read access
|
// Behind a RefCell because we need to allocate/remove on read access
|
||||||
store_buffer: RefCell<AllocationMap<StoreBuffer>>,
|
store_buffers: RefCell<AllocationMap<StoreBuffer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
struct StoreBuffer {
|
pub(super) struct StoreBuffer {
|
||||||
// Stores to this location in modification order
|
// Stores to this location in modification order
|
||||||
buffer: VecDeque<StoreElement>,
|
buffer: VecDeque<StoreElement>,
|
||||||
}
|
}
|
||||||
@ -111,21 +111,23 @@ struct StoreElement {
|
|||||||
|
|
||||||
impl StoreBufferAlloc {
|
impl StoreBufferAlloc {
|
||||||
pub fn new_allocation() -> Self {
|
pub fn new_allocation() -> Self {
|
||||||
Self { store_buffer: RefCell::new(AllocationMap::new()) }
|
Self { store_buffers: RefCell::new(AllocationMap::new()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a store buffer associated with an atomic object in this allocation
|
/// Gets a store buffer associated with an atomic object in this allocation
|
||||||
fn get_store_buffer<'tcx>(
|
/// Or creates one with the specified initial value
|
||||||
|
fn get_or_create_store_buffer<'tcx>(
|
||||||
&self,
|
&self,
|
||||||
range: AllocRange,
|
range: AllocRange,
|
||||||
|
init: ScalarMaybeUninit<Tag>,
|
||||||
) -> InterpResult<'tcx, Ref<'_, StoreBuffer>> {
|
) -> InterpResult<'tcx, Ref<'_, StoreBuffer>> {
|
||||||
let access_type = self.store_buffer.borrow().access_type(range);
|
let access_type = self.store_buffers.borrow().access_type(range);
|
||||||
let pos = match access_type {
|
let pos = match access_type {
|
||||||
AccessType::PerfectlyOverlapping(pos) => pos,
|
AccessType::PerfectlyOverlapping(pos) => pos,
|
||||||
AccessType::Empty(pos) => {
|
AccessType::Empty(pos) => {
|
||||||
// First atomic access on this range, allocate a new StoreBuffer
|
let new_buffer = StoreBuffer::new(init);
|
||||||
let mut buffer = self.store_buffer.borrow_mut();
|
let mut buffers = self.store_buffers.borrow_mut();
|
||||||
buffer.insert_at_pos(pos, range, StoreBuffer::default());
|
buffers.insert_at_pos(pos, range, new_buffer);
|
||||||
pos
|
pos
|
||||||
}
|
}
|
||||||
AccessType::ImperfectlyOverlapping(pos_range) => {
|
AccessType::ImperfectlyOverlapping(pos_range) => {
|
||||||
@ -140,20 +142,22 @@ fn get_store_buffer<'tcx>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Ref::map(self.store_buffer.borrow(), |buffer| &buffer[pos]))
|
Ok(Ref::map(self.store_buffers.borrow(), |buffer| &buffer[pos]))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a mutable store buffer associated with an atomic object in this allocation
|
/// Gets a mutable store buffer associated with an atomic object in this allocation
|
||||||
fn get_store_buffer_mut<'tcx>(
|
fn get_or_create_store_buffer_mut<'tcx>(
|
||||||
&mut self,
|
&mut self,
|
||||||
range: AllocRange,
|
range: AllocRange,
|
||||||
|
init: ScalarMaybeUninit<Tag>,
|
||||||
) -> InterpResult<'tcx, &mut StoreBuffer> {
|
) -> InterpResult<'tcx, &mut StoreBuffer> {
|
||||||
let buffer = self.store_buffer.get_mut();
|
let buffers = self.store_buffers.get_mut();
|
||||||
let access_type = buffer.access_type(range);
|
let access_type = buffers.access_type(range);
|
||||||
let pos = match access_type {
|
let pos = match access_type {
|
||||||
AccessType::PerfectlyOverlapping(pos) => pos,
|
AccessType::PerfectlyOverlapping(pos) => pos,
|
||||||
AccessType::Empty(pos) => {
|
AccessType::Empty(pos) => {
|
||||||
buffer.insert_at_pos(pos, range, StoreBuffer::default());
|
let new_buffer = StoreBuffer::new(init);
|
||||||
|
buffers.insert_at_pos(pos, range, new_buffer);
|
||||||
pos
|
pos
|
||||||
}
|
}
|
||||||
AccessType::ImperfectlyOverlapping(pos_range) => {
|
AccessType::ImperfectlyOverlapping(pos_range) => {
|
||||||
@ -164,19 +168,28 @@ fn get_store_buffer_mut<'tcx>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(&mut buffer[pos])
|
Ok(&mut buffers[pos])
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for StoreBuffer {
|
|
||||||
fn default() -> Self {
|
|
||||||
let mut buffer = VecDeque::new();
|
|
||||||
buffer.reserve(STORE_BUFFER_LIMIT);
|
|
||||||
Self { buffer }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'mir, 'tcx: 'mir> StoreBuffer {
|
impl<'mir, 'tcx: 'mir> StoreBuffer {
|
||||||
|
fn new(init: ScalarMaybeUninit<Tag>) -> Self {
|
||||||
|
let mut buffer = VecDeque::new();
|
||||||
|
buffer.reserve(STORE_BUFFER_LIMIT);
|
||||||
|
let mut ret = Self { buffer };
|
||||||
|
let store_elem = StoreElement {
|
||||||
|
// The thread index and timestamp of the initialisation write
|
||||||
|
// are never meaningfully used, so it's fine to leave them as 0
|
||||||
|
store_index: VectorIdx::from(0),
|
||||||
|
timestamp: 0,
|
||||||
|
val: init,
|
||||||
|
is_seqcst: false,
|
||||||
|
loads: RefCell::new(FxHashMap::default()),
|
||||||
|
};
|
||||||
|
ret.buffer.push_back(store_elem);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads from the last store in modification order
|
/// Reads from the last store in modification order
|
||||||
fn read_from_last_store(&self, global: &GlobalState) {
|
fn read_from_last_store(&self, global: &GlobalState) {
|
||||||
let store_elem = self.buffer.back();
|
let store_elem = self.buffer.back();
|
||||||
@ -192,7 +205,7 @@ fn buffered_read(
|
|||||||
is_seqcst: bool,
|
is_seqcst: bool,
|
||||||
rng: &mut (impl rand::Rng + ?Sized),
|
rng: &mut (impl rand::Rng + ?Sized),
|
||||||
validate: impl FnOnce() -> InterpResult<'tcx>,
|
validate: impl FnOnce() -> InterpResult<'tcx>,
|
||||||
) -> InterpResult<'tcx, Option<ScalarMaybeUninit<Tag>>> {
|
) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
|
||||||
// Having a live borrow to store_buffer while calling validate_atomic_load is fine
|
// Having a live borrow to store_buffer while calling validate_atomic_load is fine
|
||||||
// because the race detector doesn't touch store_buffer
|
// because the race detector doesn't touch store_buffer
|
||||||
|
|
||||||
@ -210,10 +223,8 @@ fn buffered_read(
|
|||||||
// requires access to ThreadClockSet.clock, which is updated by the race detector
|
// requires access to ThreadClockSet.clock, which is updated by the race detector
|
||||||
validate()?;
|
validate()?;
|
||||||
|
|
||||||
let loaded = store_elem.map(|store_elem| {
|
|
||||||
let (index, clocks) = global.current_thread_state();
|
let (index, clocks) = global.current_thread_state();
|
||||||
store_elem.load_impl(index, &clocks)
|
let loaded = store_elem.load_impl(index, &clocks);
|
||||||
});
|
|
||||||
Ok(loaded)
|
Ok(loaded)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,23 +241,18 @@ fn buffered_write(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Selects a valid store element in the buffer.
|
/// Selects a valid store element in the buffer.
|
||||||
/// The buffer does not contain the value used to initialise the atomic object
|
|
||||||
/// so a fresh atomic object has an empty store buffer and this function
|
|
||||||
/// will return `None`. In this case, the caller should ensure that the non-buffered
|
|
||||||
/// value from `MiriEvalContext::read_scalar()` is observed by the program, which is
|
|
||||||
/// the initial value of the atomic object. `MiriEvalContext::read_scalar()` is always
|
|
||||||
/// the latest value in modification order so it is always correct to be observed by any thread.
|
|
||||||
fn fetch_store<R: rand::Rng + ?Sized>(
|
fn fetch_store<R: rand::Rng + ?Sized>(
|
||||||
&self,
|
&self,
|
||||||
is_seqcst: bool,
|
is_seqcst: bool,
|
||||||
clocks: &ThreadClockSet,
|
clocks: &ThreadClockSet,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
) -> Option<&StoreElement> {
|
) -> &StoreElement {
|
||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
let mut found_sc = false;
|
let mut found_sc = false;
|
||||||
// FIXME: this should be an inclusive take_while (stops after a false predicate, but
|
// FIXME: we want an inclusive take_while (stops after a false predicate, but
|
||||||
// includes the element that gave the false), but such function doesn't yet
|
// includes the element that gave the false), but such function doesn't yet
|
||||||
// exist in the standard libary https://github.com/rust-lang/rust/issues/62208
|
// exist in the standard libary https://github.com/rust-lang/rust/issues/62208
|
||||||
|
// so we have to hack around it with keep_searching
|
||||||
let mut keep_searching = true;
|
let mut keep_searching = true;
|
||||||
let candidates = self
|
let candidates = self
|
||||||
.buffer
|
.buffer
|
||||||
@ -303,7 +309,9 @@ fn fetch_store<R: rand::Rng + ?Sized>(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
candidates.choose(rng)
|
candidates
|
||||||
|
.choose(rng)
|
||||||
|
.expect("store buffer cannot be empty, an element is populated on construction")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ATOMIC STORE IMPL in the paper (except we don't need the location's vector clock)
|
/// ATOMIC STORE IMPL in the paper (except we don't need the location's vector clock)
|
||||||
@ -366,6 +374,7 @@ fn buffered_atomic_rmw(
|
|||||||
new_val: ScalarMaybeUninit<Tag>,
|
new_val: ScalarMaybeUninit<Tag>,
|
||||||
place: &MPlaceTy<'tcx, Tag>,
|
place: &MPlaceTy<'tcx, Tag>,
|
||||||
atomic: AtomicRwOp,
|
atomic: AtomicRwOp,
|
||||||
|
init: ScalarMaybeUninit<Tag>,
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
let this = self.eval_context_mut();
|
let this = self.eval_context_mut();
|
||||||
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?;
|
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?;
|
||||||
@ -379,7 +388,7 @@ fn buffered_atomic_rmw(
|
|||||||
global.sc_write();
|
global.sc_write();
|
||||||
}
|
}
|
||||||
let range = alloc_range(base_offset, place.layout.size);
|
let range = alloc_range(base_offset, place.layout.size);
|
||||||
let buffer = alloc_buffers.get_store_buffer_mut(range)?;
|
let buffer = alloc_buffers.get_or_create_store_buffer_mut(range, init)?;
|
||||||
buffer.read_from_last_store(global);
|
buffer.read_from_last_store(global);
|
||||||
buffer.buffered_write(new_val, global, atomic == AtomicRwOp::SeqCst)?;
|
buffer.buffered_write(new_val, global, atomic == AtomicRwOp::SeqCst)?;
|
||||||
}
|
}
|
||||||
@ -401,8 +410,10 @@ fn buffered_atomic_read(
|
|||||||
global.sc_read();
|
global.sc_read();
|
||||||
}
|
}
|
||||||
let mut rng = this.machine.rng.borrow_mut();
|
let mut rng = this.machine.rng.borrow_mut();
|
||||||
let buffer =
|
let buffer = alloc_buffers.get_or_create_store_buffer(
|
||||||
alloc_buffers.get_store_buffer(alloc_range(base_offset, place.layout.size))?;
|
alloc_range(base_offset, place.layout.size),
|
||||||
|
latest_in_mo,
|
||||||
|
)?;
|
||||||
let loaded = buffer.buffered_read(
|
let loaded = buffer.buffered_read(
|
||||||
global,
|
global,
|
||||||
atomic == AtomicReadOp::SeqCst,
|
atomic == AtomicReadOp::SeqCst,
|
||||||
@ -410,7 +421,7 @@ fn buffered_atomic_read(
|
|||||||
validate,
|
validate,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
return Ok(loaded.unwrap_or(latest_in_mo));
|
return Ok(loaded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,6 +435,7 @@ fn buffered_atomic_write(
|
|||||||
val: ScalarMaybeUninit<Tag>,
|
val: ScalarMaybeUninit<Tag>,
|
||||||
dest: &MPlaceTy<'tcx, Tag>,
|
dest: &MPlaceTy<'tcx, Tag>,
|
||||||
atomic: AtomicWriteOp,
|
atomic: AtomicWriteOp,
|
||||||
|
init: ScalarMaybeUninit<Tag>,
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
let this = self.eval_context_mut();
|
let this = self.eval_context_mut();
|
||||||
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(dest.ptr)?;
|
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(dest.ptr)?;
|
||||||
@ -435,8 +447,23 @@ fn buffered_atomic_write(
|
|||||||
if atomic == AtomicWriteOp::SeqCst {
|
if atomic == AtomicWriteOp::SeqCst {
|
||||||
global.sc_write();
|
global.sc_write();
|
||||||
}
|
}
|
||||||
let buffer =
|
|
||||||
alloc_buffers.get_store_buffer_mut(alloc_range(base_offset, dest.layout.size))?;
|
// UGLY HACK: in write_scalar_atomic() we don't know the value before our write,
|
||||||
|
// so init == val always. If the buffer is fresh then we would've duplicated an entry,
|
||||||
|
// so we need to remove it.
|
||||||
|
let was_empty = matches!(
|
||||||
|
alloc_buffers
|
||||||
|
.store_buffers
|
||||||
|
.borrow()
|
||||||
|
.access_type(alloc_range(base_offset, dest.layout.size)),
|
||||||
|
AccessType::Empty(_)
|
||||||
|
);
|
||||||
|
let buffer = alloc_buffers
|
||||||
|
.get_or_create_store_buffer_mut(alloc_range(base_offset, dest.layout.size), init)?;
|
||||||
|
if was_empty {
|
||||||
|
buffer.buffer.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
buffer.buffered_write(val, global, atomic == AtomicWriteOp::SeqCst)?;
|
buffer.buffered_write(val, global, atomic == AtomicWriteOp::SeqCst)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,6 +478,7 @@ fn perform_read_on_buffered_latest(
|
|||||||
&self,
|
&self,
|
||||||
place: &MPlaceTy<'tcx, Tag>,
|
place: &MPlaceTy<'tcx, Tag>,
|
||||||
atomic: AtomicReadOp,
|
atomic: AtomicReadOp,
|
||||||
|
init: ScalarMaybeUninit<Tag>,
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
let this = self.eval_context_ref();
|
let this = self.eval_context_ref();
|
||||||
|
|
||||||
@ -461,7 +489,8 @@ fn perform_read_on_buffered_latest(
|
|||||||
let size = place.layout.size;
|
let size = place.layout.size;
|
||||||
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?;
|
let (alloc_id, base_offset, ..) = this.ptr_get_alloc_id(place.ptr)?;
|
||||||
if let Some(alloc_buffers) = this.get_alloc_extra(alloc_id)?.weak_memory.as_ref() {
|
if let Some(alloc_buffers) = this.get_alloc_extra(alloc_id)?.weak_memory.as_ref() {
|
||||||
let buffer = alloc_buffers.get_store_buffer(alloc_range(base_offset, size))?;
|
let buffer = alloc_buffers
|
||||||
|
.get_or_create_store_buffer(alloc_range(base_offset, size), init)?;
|
||||||
buffer.read_from_last_store(global);
|
buffer.read_from_last_store(global);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,6 @@ unsafe impl<T> Sync for EvilSend<T> {}
|
|||||||
// multiple times
|
// multiple times
|
||||||
fn static_atomic(val: usize) -> &'static AtomicUsize {
|
fn static_atomic(val: usize) -> &'static AtomicUsize {
|
||||||
let ret = Box::leak(Box::new(AtomicUsize::new(val)));
|
let ret = Box::leak(Box::new(AtomicUsize::new(val)));
|
||||||
// A workaround to put the initialisation value in the store buffer
|
|
||||||
ret.store(val, Relaxed);
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,8 +203,19 @@ fn test_sc_store_buffering() {
|
|||||||
assert_ne!((a, b), (0, 0));
|
assert_ne!((a, b), (0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_single_thread() {
|
||||||
|
let x = AtomicUsize::new(42);
|
||||||
|
|
||||||
|
assert_eq!(x.load(Relaxed), 42);
|
||||||
|
|
||||||
|
x.store(43, Relaxed);
|
||||||
|
|
||||||
|
assert_eq!(x.load(Relaxed), 43);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
|
test_single_thread();
|
||||||
test_mixed_access();
|
test_mixed_access();
|
||||||
test_load_buffering_acq_rel();
|
test_load_buffering_acq_rel();
|
||||||
test_message_passing();
|
test_message_passing();
|
||||||
|
@ -22,11 +22,17 @@ unsafe impl<T> Sync for EvilSend<T> {}
|
|||||||
// multiple times
|
// multiple times
|
||||||
fn static_atomic(val: usize) -> &'static AtomicUsize {
|
fn static_atomic(val: usize) -> &'static AtomicUsize {
|
||||||
let ret = Box::leak(Box::new(AtomicUsize::new(val)));
|
let ret = Box::leak(Box::new(AtomicUsize::new(val)));
|
||||||
// A workaround to put the initialisation value in the store buffer
|
|
||||||
ret.store(val, Relaxed);
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spins until it reads the given value
|
||||||
|
fn reads_value(loc: &AtomicUsize, val: usize) -> usize {
|
||||||
|
while loc.load(Relaxed) != val {
|
||||||
|
std::hint::spin_loop();
|
||||||
|
}
|
||||||
|
val
|
||||||
|
}
|
||||||
|
|
||||||
fn relaxed() -> bool {
|
fn relaxed() -> bool {
|
||||||
let x = static_atomic(0);
|
let x = static_atomic(0);
|
||||||
let j1 = spawn(move || {
|
let j1 = spawn(move || {
|
||||||
@ -64,6 +70,31 @@ fn seq_cst() -> bool {
|
|||||||
r3 == 1
|
r3 == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn initialization_write() -> bool {
|
||||||
|
let x = static_atomic(11);
|
||||||
|
assert_eq!(x.load(Relaxed), 11);
|
||||||
|
|
||||||
|
let wait = static_atomic(0);
|
||||||
|
|
||||||
|
let j1 = spawn(move || {
|
||||||
|
x.store(22, Relaxed);
|
||||||
|
// Relaxed is intentional. We want to test if the thread 2 reads the initialisation write
|
||||||
|
// after a relaxed write
|
||||||
|
wait.store(1, Relaxed);
|
||||||
|
});
|
||||||
|
|
||||||
|
let j2 = spawn(move || {
|
||||||
|
reads_value(wait, 1);
|
||||||
|
x.load(Relaxed)
|
||||||
|
});
|
||||||
|
|
||||||
|
j1.join().unwrap();
|
||||||
|
let r2 = j2.join().unwrap();
|
||||||
|
|
||||||
|
r2 == 11
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Asserts that the function returns true at least once in 100 runs
|
// Asserts that the function returns true at least once in 100 runs
|
||||||
macro_rules! assert_once {
|
macro_rules! assert_once {
|
||||||
($f:ident) => {
|
($f:ident) => {
|
||||||
@ -74,4 +105,5 @@ macro_rules! assert_once {
|
|||||||
pub fn main() {
|
pub fn main() {
|
||||||
assert_once!(relaxed);
|
assert_once!(relaxed);
|
||||||
assert_once!(seq_cst);
|
assert_once!(seq_cst);
|
||||||
|
assert_once!(initialization_write);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user