1
//! Memory management internals for the bucket array
2
//!
3
//! The solver needs sparse arrays of sorting buckets as temporary space. To
4
//! minimize peak memory usage, we need to dynamically reallocate portions
5
//! of that space between usages. A naive solution could use a separate Vec for
6
//! each array of bucket keys/values. A quick benchmark confirms that this is
7
//! between 2% and 10% worse than if the solver could use a single block of
8
//! memory with predetermined packed layout.
9
//!
10
//! Using a static layout presents some challenges in initialization, since the
11
//! entire struct is now too large to fit on the stack reliably. Creating an
12
//! instance using Box::new() would cause a stack overflow.
13
//!
14
//! We can solve several of these problems by orienting the data structure
15
//! around MaybeUninit memory: No initialization needed, we can easily switch
16
//! between space layouts using a union without risk of transmuting live values,
17
//! and we avoid an additional redundant zeroing of memory.
18
//!
19
//! This module provides a trait, [`Uninit`], for memory that we can safely
20
//! treat as uninitialized when it isn't being used. We track that use via a
21
//! mutable reference, so that gives us mutually exclusive access enforced at
22
//! compile-time.
23
//!
24
//! That uninitialized memory can be used to build backing layouts that are
25
//! made from instances of [`BucketArrayMemory`]. The whole backing layout is
26
//! then instantiated using freshly allocated uninitialized memory.
27
//!
28
//! A [`BucketArrayMemory`] can be temporarily, using a mutable reference,
29
//! wrapped into a [`BucketArray`] or half of a [`BucketArrayPair`].
30
//! Internally, a [`BucketState`] tracks how many items are in each bucket.
31
//! The only supported write operation is appending to an underfull bucket.
32
//! Once initialized by such a write, bucket items may be randomly accessed
33
//! via the methods on the [`BucketArray`] or [`BucketArrayPair`].
34
//!
35
//! It's critical for memory safety that we only read from a [`MaybeUninit`]
36
//! that has definitely been initialized. The static lifetimes of the mutable
37
//! reference and our `counts` array ensure we always start out with zeroed
38
//! counts and fully assumed-uninitialized memory. Our write method must be
39
//! certain to never increment the counter without actually writing to the
40
//! [`MaybeUninit`]. See [`BucketState::insert`].
41

            
42
// We need to allow this warning because we conditionally make some private
43
// functions public, but their documentation links to private types.
44
#![allow(rustdoc::private_intra_doc_links)]
45

            
46
use num_traits::{One, Zero};
47
use std::alloc;
48
use std::mem::MaybeUninit;
49
use std::ops::{Add, Range};
50

            
51
/// Marker trait for types that are normally assumed uninitialized
52
///
53
/// # Safety
54
///
55
/// By implementing this trait, it's a guarantee that uninitialized memory
56
/// may be transmuted into this type safely. It implies that the type is `Copy`.
57
/// It should contain no bits except for [`MaybeUninit`] fields. Structs and
58
/// arrays made entirely of [`MaybeUninit`] are fine.
59
///
60
/// This memory is always assumed to be uninitialized unless we hold a mutable
61
/// reference that's associated with information about specific fields that
62
/// were initialized during the reference's lifetime.
63
#[cfg_attr(feature = "bucket-array", visibility::make(pub))]
64
pub(crate) unsafe trait Uninit: Copy {
65
    /// Allocate new uninitialized memory, returning a new Box.
66
3224
    fn alloc() -> Box<Self> {
67
3224
        // SAFETY: Any type implementing Uninit guarantees that creating an
68
3224
        //         instance from uninitialized memory is sound. We pass this
69
3224
        //         pointer's ownership immediately to Box.
70
3224
        unsafe {
71
3224
            let layout = alloc::Layout::new::<Self>();
72
3224
            let ptr: *mut Self = std::mem::transmute(alloc::alloc(layout));
73
3224
            Box::from_raw(ptr)
74
3224
        }
75
3224
    }
76
}
77

            
78
/// Backing memory for a single key or value bucket array
79
///
80
/// Describes `N` buckets which each hold at most `M` items of type `T`.
81
///
82
/// Implements [`Uninit`]. Structs and unions made from `BucketArrayMemory`
83
/// can be soundly marked as [`Uninit`].
84
#[cfg_attr(feature = "bucket-array", visibility::make(pub))]
85
#[derive(Copy, Clone)]
86
pub(crate) struct BucketArrayMemory<
87
    // Number of buckets
88
    const N: usize,
89
    // Number of item slots in each bucket
90
    const M: usize,
91
    // Item type
92
    T: Copy,
93
>([[MaybeUninit<T>; M]; N]);
94

            
95
/// Implements the [`Uninit`] trait. This memory is always assumed to be
96
/// uninitialized unless we hold a mutable reference paired with bucket
97
/// usage information.
98
///
99
/// SAFETY: We can implement [`Uninit`] for [`BucketArrayMemory`]
100
///         because it's `Copy` and it contains only `MaybeUninit` bits.
101
unsafe impl<const N: usize, const M: usize, T: Copy> Uninit for BucketArrayMemory<N, M, T> {}
102

            
103
/// Types that can be used as a count of items in a bucket
104
#[cfg_attr(feature = "bucket-array", visibility::make(pub))]
105
pub(crate) trait Count: Copy + Zero + One + Into<usize> + Add<Self, Output = Self> {}
106

            
107
impl<T: Copy + Zero + One + Into<usize> + Add<Self, Output = Self>> Count for T {}
108

            
109
/// Common implementation for key/value and value-only bucket arrays
110
///
111
/// Tracks the number of items in each bucket. This is used by [`BucketArray`]
112
/// and [`BucketArrayPair`] implementations to safely access only initialized
113
/// items in the [`BucketArrayMemory`].
114
struct BucketState<
115
    // Number of buckets
116
    const N: usize,
117
    // Maximum number of items per bucket
118
    const CAP: usize,
119
    // Type for bucket item counts
120
    C: Count,
121
> {
122
    /// Number of initialized items in each bucket
123
    ///
124
    /// Each bucket B's valid item range would be `(0 .. counts[B])`
125
    counts: [C; N],
126
}
127

            
128
impl<const N: usize, const CAP: usize, C: Count> BucketState<N, CAP, C> {
129
    /// Create a new counter store.
130
    ///
131
    /// This will happen inside the lifetime of our mutable reference
132
    /// to the backing store memory.
133
1842750
    fn new() -> Self {
134
1842750
        Self {
135
1842750
            counts: [C::zero(); N],
136
1842750
        }
137
1842750
    }
138

            
139
    /// Append a new item to a specific bucket using a writer callback.
140
    ///
141
    /// The writer is invoked with an item index, after checking
142
    /// bucket capacity but before marking the new item as written.
143
    ///
144
    /// The writer must unconditionally write to the index it's given, in
145
    /// each of the backing memories covered by this state tracker.
146
    #[inline(always)]
147
929814582
    fn insert<F: FnMut(usize)>(&mut self, bucket: usize, mut writer: F) -> Result<(), ()> {
148
929814582
        let item_count = self.counts[bucket];
149
929814582
        let count_usize: usize = item_count.into();
150
929814582
        if count_usize < CAP {
151
929814557
            writer(count_usize);
152
929814557
            self.counts[bucket] = item_count + C::one();
153
929814557
            Ok(())
154
        } else {
155
25
            Err(())
156
        }
157
929814582
    }
158

            
159
    /// Look up the valid item range for a particular bucket.
160
    ///
161
    /// Panics if the bucket index is out of range. Item indices inside the
162
    /// returned range are initialized, and any outside may be uninitialized.
163
    #[inline(always)]
164
3358855923
    fn item_range(&self, bucket: usize) -> Range<usize> {
165
3358855923
        0..self.counts[bucket].into()
166
3358855923
    }
167
}
168

            
169
/// Concrete binding between one [`BucketState`] and one [`BucketArrayMemory`]
170
#[cfg_attr(feature = "bucket-array", visibility::make(pub))]
171
pub(crate) struct BucketArray<
172
    // Lifetime for mutable reference to the backing memory
173
    'a,
174
    // Number of buckets
175
    const N: usize,
176
    // Maximum number of items per bucket
177
    const CAP: usize,
178
    // Type for bucket item counts
179
    C: Count,
180
    // Item type
181
    A: Copy,
182
> {
183
    /// Reference to external backing memory for type A
184
    mem: &'a mut BucketArrayMemory<N, CAP, A>,
185
    /// Tracking for which items are in use within each bucket
186
    state: BucketState<N, CAP, C>,
187
}
188

            
189
impl<'a, const N: usize, const CAP: usize, C: Count, A: Copy> BucketArray<'a, N, CAP, C, A> {
190
    /// A new [`BucketArray`] wraps a new [`BucketState`] and some possibly-recycled [`BucketArrayMemory`]
191
1828575
    #[cfg_attr(feature = "bucket-array", visibility::make(pub))]
192
1828575
    pub(crate) fn new(mem: &'a mut BucketArrayMemory<N, CAP, A>) -> Self {
193
1828575
        Self {
194
1828575
            mem,
195
1828575
            state: BucketState::new(),
196
1828575
        }
197
1828575
    }
198

            
199
    /// Look up the valid item range for a particular bucket.
200
    ///
201
    /// Panics if the bucket index is out of range.
202
187697075
    #[cfg_attr(feature = "bucket-array", visibility::make(pub))]
203
187697075
    #[inline(always)]
204
187697075
    pub(crate) fn item_range(&self, bucket: usize) -> Range<usize> {
205
187697075
        self.state.item_range(bucket)
206
187697075
    }
207

            
208
    /// Look up the value of one item in one bucket.
209
    ///
210
    /// Panics if the indices are out of range.
211
956690754
    #[cfg_attr(feature = "bucket-array", visibility::make(pub))]
212
956690754
    #[inline(always)]
213
956690754
    pub(crate) fn item_value(&self, bucket: usize, item: usize) -> A {
214
956690754
        assert!(self.state.item_range(bucket).contains(&item));
215
        // SAFETY: This requires that our [`BucketState`] instance accurately
216
        //         represents which fields in [`mem`] have been initialized.
217
956690754
        unsafe { self.mem.0[bucket][item].assume_init() }
218
956690754
    }
219

            
220
    /// Append a new item to a bucket.
221
    ///
222
    /// If the bucket is full, returns `Err(())` and makes no changes.
223
    #[cfg_attr(
224
        feature = "bucket-array",
225
187758500
        visibility::make(pub),
226
187758500
        allow(clippy::result_unit_err)
227
187758500
    )]
228
187758500
    #[inline(always)]
229
187758500
    pub(crate) fn insert(&mut self, bucket: usize, value: A) -> Result<(), ()> {
230
480661721
        self.state.insert(bucket, |item| {
231
473151357
            self.mem.0[bucket][item].write(value);
232
480661721
        })
233
187758500
    }
234
}
235

            
236
/// Concrete binding between one [`BucketState`] and a pair of [`BucketArrayMemory`]
237
#[cfg_attr(feature = "bucket-array", visibility::make(pub))]
238
pub(crate) struct BucketArrayPair<
239
    // Lifetime for mutable reference to the first backing memory
240
    'a,
241
    // Lifetime for mutable reference to the second backing memory
242
    'b,
243
    // Number of buckets
244
    const N: usize,
245
    // Maximum number of items per bucket
246
    const CAP: usize,
247
    // Type for bucket item counts
248
    C: Count,
249
    // Type for items in the first backing memory
250
    A: Copy,
251
    // Type for items in the second backing memory
252
    B: Copy,
253
> {
254
    /// Reference to external backing memory for type `A`
255
    mem_a: &'a mut BucketArrayMemory<N, CAP, A>,
256
    /// Reference to external backing memory for type `B`
257
    mem_b: &'b mut BucketArrayMemory<N, CAP, B>,
258
    /// Tracking for which items are in use within each bucket
259
    state: BucketState<N, CAP, C>,
260
}
261

            
262
impl<'a, 'b, const N: usize, const CAP: usize, C: Count, A: Copy, B: Copy>
263
    BucketArrayPair<'a, 'b, N, CAP, C, A, B>
264
{
265
    /// A new [`BucketArray`] wraps a new [`BucketState`] and two [`BucketArrayMemory`]
266
14175
    #[cfg_attr(feature = "bucket-array", visibility::make(pub))]
267
14175
    pub(crate) fn new(
268
14175
        mem_a: &'a mut BucketArrayMemory<N, CAP, A>,
269
14175
        mem_b: &'b mut BucketArrayMemory<N, CAP, B>,
270
14175
    ) -> Self {
271
14175
        Self {
272
14175
            mem_a,
273
14175
            mem_b,
274
14175
            state: BucketState::new(),
275
14175
        }
276
14175
    }
277

            
278
    /// Look up the valid item range for a particular bucket.
279
    ///
280
    /// Panics if the bucket index is out of range.
281
1451250
    #[cfg_attr(feature = "bucket-array", visibility::make(pub))]
282
1451250
    #[inline(always)]
283
1451250
    pub(crate) fn item_range(&self, bucket: usize) -> Range<usize> {
284
1451250
        self.state.item_range(bucket)
285
1451250
    }
286

            
287
    /// Look up the first value for one item in one bucket.
288
    ///
289
    /// Panics if the indices are out of range.
290
755059925
    #[cfg_attr(feature = "bucket-array", visibility::make(pub))]
291
755059925
    #[inline(always)]
292
755059925
    pub(crate) fn item_value_first(&self, bucket: usize, item: usize) -> A {
293
755059925
        assert!(self.state.item_range(bucket).contains(&item));
294
        // SAFETY: This requires that our [`BucketState`] instance accurately
295
        //         represents which fields in [`mem`] have been initialized.
296
755059925
        unsafe { self.mem_a.0[bucket][item].assume_init() }
297
755059925
    }
298

            
299
    /// Look up the second value for one item in one bucket.
300
    ///
301
    /// Panics if the indices are out of range.
302
65844
    #[cfg_attr(feature = "bucket-array", visibility::make(pub))]
303
65844
    #[inline(always)]
304
65844
    pub(crate) fn item_value_second(&self, bucket: usize, item: usize) -> B {
305
65844
        assert!(self.state.item_range(bucket).contains(&item));
306
        // SAFETY: This requires that our [`BucketState`] instance accurately
307
        //         represents which fields in [`mem`] have been initialized.
308
65844
        unsafe { self.mem_b.0[bucket][item].assume_init() }
309
65844
    }
310

            
311
    /// Append a new item pair to a bucket.
312
    ///
313
    /// If the bucket is full, returns Err(()) and makes no changes.
314
    #[cfg_attr(
315
        feature = "bucket-array",
316
742056082
        visibility::make(pub),
317
742056082
        allow(clippy::result_unit_err)
318
742056082
    )]
319
742056082
    #[inline(always)]
320
742056082
    pub(crate) fn insert(&mut self, bucket: usize, first: A, second: B) -> Result<(), ()> {
321
953722304
        self.state.insert(bucket, |item| {
322
938820393
            self.mem_a.0[bucket][item].write(first);
323
938820393
            self.mem_b.0[bucket][item].write(second);
324
953722304
        })
325
742056082
    }
326

            
327
    /// Transfer the [`BucketState`] to a new single [`BucketArray`],
328
    /// keeping the second half and dropping the first.
329
4725
    #[cfg_attr(feature = "bucket-array", visibility::make(pub))]
330
4725
    pub(crate) fn drop_first(self) -> BucketArray<'b, N, CAP, C, B> {
331
4725
        BucketArray {
332
4725
            mem: self.mem_b,
333
4725
            state: self.state,
334
4725
        }
335
4725
    }
336
}