copy-channels 1.0.0

A collection of cross-thread channels for copyable types
Documentation
//! Fast and simple copy channels.
//!
//! These channels work on any [`Copy`] type. When run with `miri`, parallel read/write
//! operations will show undefined behaviour. This currently works fine on all important
//! architectures, under the current compiler, and we expect this to keep working. However
//! it's not guaranteed, hence undefined. Alternatively use [`atomic`](crate::atomic) channels
//! that have no UB but place some restrictions on the contained type.

use crate::loom::sync::atomic::{Ordering, fence};
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;

pub struct Slot<T> {
    value: UnsafeCell<MaybeUninit<T>>,
}

// safety: only a T is carried
unsafe impl<T: Send> Send for Slot<T> {}
// safety: access is synchronised, the unsafety is moved to MaybeUninit::assume_init
unsafe impl<T: Send> Sync for Slot<T> {}

impl<T> Slot<T> {
    fn uninit() -> Self {
        Slot {
            value: UnsafeCell::new(MaybeUninit::uninit()),
        }
    }
}

// Note:
//    There's a data race in the non-atomic read/write. Miri catches it. There is
//    no good solution (yet). The alternative would be to use blocks of AtomicUsize
//    and copy word-for-word, but that runs into the next problem, where we can't
//    read the padding bytes in T so we need to restrict the set of types. It's all
//    a bit crap. Search for "rust seqlock ub" to get an idea. It seems that the
//    general consensus is to use volatile read/write and fences, and cross fingers.
//    (the volatile bit being to further discourage any speculative loads, although
//    I'd think that the UnsafeCell should already accomplish that). This whole thing
//    works currently on all important architectures, with the current version of LLVM,
//    but it's not guaranteed to keep working with future updates (unlikely).

pub struct Slotable;

impl<T: Copy> super::Slotable<T> for Slotable {
    type Slot = Slot<T>;
    type SlotArrayItem = Slot<T>;

    fn boxed_uninit_single() -> Box<Self::Slot> {
        Box::new(Slot::uninit())
    }

    fn create_boxed(value: T) -> Box<Self::Slot> {
        Box::new(Slot {
            value: UnsafeCell::new(MaybeUninit::new(value)),
        })
    }

    fn read(slot: &Self::Slot, ordering: Ordering) -> MaybeUninit<T> {
        // Safety: reading MaybeUninit so any bit pattern is valid, essentially.
        let value = unsafe { slot.value.get().read_volatile() };
        if ordering != Ordering::Relaxed {
            // According to spec, an atomic fence only synchronises *atomic* read/writes. But
            // this seems to do the right thing on all important architectures.
            fence(ordering);
        }
        value
    }

    fn write(slot: &Self::Slot, value: T, ordering: Ordering) {
        if ordering != Ordering::Relaxed {
            // According to spec, an atomic fence only synchronises *atomic* read/writes. But
            // this seems to do the right thing on all important architectures.
            fence(ordering);
        }

        // Safety: well this is actually where we officially break things. We can not guarantee
        // that nobody else is reading from or writing to the slot. The only consolation is
        // that we're writing a MaybeUninit which itself comes with no guarantees.
        unsafe {
            slot.value.get().write_volatile(MaybeUninit::new(value));
        }
    }

    fn boxed_uninit_multiple(n: usize) -> Box<[Self::SlotArrayItem]> {
        std::iter::repeat_with(Slot::uninit)
            .take(n)
            .collect::<Vec<_>>()
            .into_boxed_slice()
    }

    fn index_in_array(items: &[Self::SlotArrayItem], index: usize) -> &Self::Slot {
        &items[index]
    }
}

crate::impl_channels!(crate::fast::Slotable, Copy);