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}