once/grove/
block.rs

1use std::{mem::ManuallyDrop, num::NonZero, sync::atomic::Ordering};
2
3use crate::{
4    std_or_loom::{
5        GetMut,
6        sync::atomic::{AtomicPtr, AtomicUsize},
7    },
8    write_once::WriteOnce,
9};
10
11/// An allocation that can store a fixed number of elements.
12#[derive(Debug)]
13pub struct Block<T> {
14    /// The number of elements in the block.
15    len: AtomicUsize,
16
17    /// A pointer to the data buffer.
18    ///
19    /// If `size` is nonzero, this points to a slice of `WriteOnce<T>` of that size. If `size` is
20    /// zero, this is a null pointer.
21    ///
22    /// It would be more convenient to use `AtomicPtr<[WriteOnce<T>]` here. However, `AtomicPtr<T>`
23    /// requires `T: Sized`. This is because pointers to DSTs are fat, and take two words in memory.
24    /// Most architectures can operate atomically on at most one word.
25    data: AtomicPtr<WriteOnce<T>>,
26}
27
28impl<T> Block<T> {
29    /// Create a new block.
30    pub(super) fn new() -> Self {
31        Self {
32            len: AtomicUsize::new(0),
33            data: AtomicPtr::new(std::ptr::null_mut()),
34        }
35    }
36
37    pub(super) fn is_init(&self) -> bool {
38        !self.data.load(Ordering::Acquire).is_null()
39    }
40
41    pub(super) fn data(&self) -> &AtomicPtr<WriteOnce<T>> {
42        &self.data
43    }
44
45    /// Initialize the block with a given size.
46    ///
47    /// # Safety
48    ///
49    /// For any given block, this method must always be called with the same size.
50    pub(super) unsafe fn init(&self, size: NonZero<usize>) {
51        if self.data.load(Ordering::Relaxed).is_null() {
52            // We need to initialize the block
53
54            // NB: Benchmarking shows that using `alloc_zeroed` is somehow significantly slower than
55            // just pushing repeatedly to a Vec.
56            let mut data_buffer = ManuallyDrop::new(Vec::with_capacity(size.get()));
57            for _ in 0..size.get() {
58                data_buffer.push(WriteOnce::none());
59            }
60            let data_ptr = data_buffer.as_mut_ptr();
61
62            // We can use `Relaxed` here because we will release-store the data pointer, and so any
63            // aquire-load of the data pointer will also see the instructions before it, in
64            // particular this store.
65            //
66            // Note that potentially many threads could be trying to initialize the block at the
67            // same time, and execute this store. This is not a problem, since by assumption all
68            // those threads will attempt to store the same value.
69            self.len.store(size.get(), Ordering::Relaxed);
70
71            // `Release` means that any thread that sees the data pointer will also see the size. We
72            // can use `Relaxed` for the failure case because we don't need to synchronize with any
73            // other atomic operation.
74            if self
75                .data
76                .compare_exchange(
77                    std::ptr::null_mut(),
78                    data_ptr,
79                    Ordering::Release,
80                    Ordering::Relaxed,
81                )
82                .is_err()
83            {
84                // Another thread initialized the block before us
85                // Safety: this is the last use of `data_buffer`
86                unsafe { ManuallyDrop::drop(&mut data_buffer) };
87            }
88        }
89    }
90
91    // **NOTE**: The following methods load the data pointer with `Acquire` ordering. If we
92    // strengthen the safety contract to require that the initialized state was *observed* by the
93    // caller, we could use `Relaxed` ordering here. In practice, this struct is only used by
94    // `Grove`, which does always call `init` first, so this is entirely reasonable. However, it
95    // seems to hit this bug in loom: https://github.com/tokio-rs/loom/issues/260
96    //
97    // The performance hit seems to be negligible, and the correctness checks that loom offer are
98    // more important, so we stick with `Acquire` for now.
99
100    /// Insert a value at the given index.
101    ///
102    /// # Safety
103    ///
104    /// The block must be initialized.
105    pub(super) unsafe fn insert(&self, index: usize, value: T) {
106        let data_ptr = self.data.load(Ordering::Acquire);
107        let len = self.len.load(Ordering::Relaxed);
108        // Safety: the block has been initialized
109        let data = unsafe { std::slice::from_raw_parts(data_ptr, len) };
110        data[index].set(value);
111    }
112
113    /// Attempt to insert a value at the given index.
114    ///
115    /// # Safety
116    ///
117    /// The block must be initialized.
118    pub(super) unsafe fn try_insert(&self, index: usize, value: T) -> Result<(), T> {
119        // We can use Relaxed operations here because the initialization of the block has a
120        // happens-before relationship with this operation, by assumption.
121        let data_ptr = self.data.load(Ordering::Acquire);
122        let len = self.len.load(Ordering::Relaxed);
123        // Safety: the block has been initialized
124        let data = unsafe { std::slice::from_raw_parts(data_ptr, len) };
125        data[index].try_set(value)
126    }
127
128    /// Return the value at the given index.
129    ///
130    /// # Safety
131    ///
132    /// The block must be initialized.
133    pub(super) unsafe fn get(&self, index: usize) -> Option<&T> {
134        // We can use Relaxed operations here because the initialization of the block has a
135        // happens-before relationship with this operation, by assumption.
136        let data_ptr = self.data.load(Ordering::Acquire);
137        let len = self.len.load(Ordering::Relaxed);
138        // Safety: the block has been initialized
139        let data = unsafe { std::slice::from_raw_parts(data_ptr, len) };
140        data.get(index).and_then(|w| w.get())
141    }
142
143    /// Return a mutable reference to the value at the given index if it exists.
144    ///
145    /// This method is safe to call even if the block is uninitialized.
146    pub(super) fn get_mut(&mut self, index: usize) -> Option<&mut T> {
147        let len = self.len.get_by_mut();
148        if index >= len {
149            return None;
150        }
151        let data_ptr = self.data.get_by_mut();
152        // Safety: index < len, so the pointer is in bounds. We reference a single element
153        // rather than creating a slice over the entire block, so that the resulting `&mut`
154        // does not alias other elements in the same allocation.
155        let elem = unsafe { &mut *data_ptr.add(index) };
156        elem.get_mut()
157    }
158
159    /// Return the value at the given index.
160    ///
161    /// # Safety
162    ///
163    /// The block must be initialized.
164    pub(super) unsafe fn is_set(&self, index: usize) -> bool {
165        // We can use Relaxed operations here because the initialization of the block has a
166        // happens-before relationship with this operation, by assumption.
167        let data_ptr = self.data.load(Ordering::Acquire);
168        let len = self.len.load(Ordering::Relaxed);
169        // Safety: the block has been initialized
170        let data = unsafe { std::slice::from_raw_parts(data_ptr, len) };
171        data.get(index).is_some_and(|w| w.is_set())
172    }
173}
174
175impl<T> Drop for Block<T> {
176    fn drop(&mut self) {
177        let len = self.len.get_by_mut();
178        let data_ptr = self.data.get_by_mut();
179        if !data_ptr.is_null() {
180            // Safety: initialization stores a pointer that came from exactly such a vector
181            unsafe { Vec::from_raw_parts(data_ptr, len, len) };
182            // vector is dropped here
183        }
184    }
185}