summaryrefslogtreecommitdiff
path: root/crates/equix/src/bucket_array/hash.rs
blob: 1d4b2167eb452773de5bad9a5e4c5210e003910d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
//! Hash table layer
//!
//! This uses the minimal utilities in [`crate::bucket_array::mem`] to build
//! a slightly higher level data structure that's meant to be used as a hash
//! table for uniformly distributed keys. The single and paired bucket arrays
//! from [`crate::bucket_array::mem`] are wrapped into concrete value-only
//! and key-value hash table stores, and unified again via common traits.
//!
//! This introduces new data types and conversions that let us represent one
//! item key in multiple formats: the full size key, its implied bucket number,
//! the remaining unused key bits, and a packed version of that remainder.

use crate::bucket_array::mem::{self, BucketArrayMemory};
use num_traits::{One, WrappingAdd, WrappingNeg, Zero};
use std::marker::PhantomData;
use std::ops::{BitAnd, Div, Mul, Not, Range, Rem, Shl, Shr, Sub};

/// Trait for accessing the overall shape of a bucket array
pub(crate) trait Shape<K: Key> {
    /// The number of buckets in this array
    const NUM_BUCKETS: usize;

    /// The item capacity of each bucket
    const BUCKET_CAPACITY: usize;

    /// Get the range of items within a single bucket.
    fn item_range(&self, bucket: usize) -> Range<usize>;

    /// Get the key divisor: the number of buckets but as a [`Key`] instance.
    #[inline(always)]
    fn divisor(&self) -> K {
        K::from_bucket_index(Self::NUM_BUCKETS)
    }

    /// Split a wide key into the bucket index and the remaining bits.
    #[inline(always)]
    fn split_wide_key(&self, key: K) -> (usize, K) {
        let divisor = self.divisor();
        ((key % divisor).into_bucket_index(), (key / divisor))
    }

    /// Rebuild a wide key from its split components.
    #[inline(always)]
    fn join_wide_key(&self, bucket: usize, remainder: K) -> K {
        let divisor = self.divisor();
        remainder * divisor + K::from_bucket_index(bucket)
    }
}

/// Trait for writing new key/value pairs to a bucket array
pub(crate) trait Insert<K: Key, V: Copy> {
    /// Append a new item to its sorting bucket, or return Err(()) if it's full
    fn insert(&mut self, key: K, value: V) -> Result<(), ()>;
}

/// Trait for bucket arrays that include storage for keys
///
/// Keys are always used to index into the bucket array, but an array may
/// also optionally include storage for the remaining portion.
pub(crate) trait KeyLookup<S: KeyStorage<K>, K: Key> {
    /// Retrieve the stored key remainder bits for this item
    fn item_stored_key(&self, bucket: usize, item: usize) -> S;

    /// Retrieve the key for a particular item, as a full width key
    fn item_full_key(&self, bucket: usize, item: usize) -> K;
}

/// Trait for bucket arrays that include storage for values
///
/// Values are opaque data, any [`Copy`] type may be used.
pub(crate) trait ValueLookup<V: Copy> {
    /// Retrieve the Value for a particular item
    fn item_value(&self, bucket: usize, item: usize) -> V;
}

/// Concrete bucket array with parallel [`BucketArrayMemory`] for key and value
/// storage
///
/// This is the basic data type used for one layer of sorting buckets.
///
/// Takes several type parameters so the narrowest possible types can be
/// chosen for counters, keys, and values. Keys take two types: the 'wide'
/// version that appears in the API and a 'storage' version that's been
/// stripped of the data redundant with its bucket position.
///
/// The validity of [`BucketArrayMemory`] entries is ensured by the combination
/// of our mutable ref to the `BucketArrayMemory` itself and our tracking of
/// bucket counts within the lifetime of that reference.
pub(crate) struct KeyValueBucketArray<
    // Lifetime for mutable reference to key storage memory
    'k,
    // Lifetime for mutable reference to value storage memory
    'v,
    // Number of buckets
    const N: usize,
    // Maximum number of items per bucket
    const CAP: usize,
    // Type for bucket item counts
    C: Count,
    // Full size key type, used in the API and for calculating bucket indices
    K: Key,
    // Storage type for keys, potentially smaller than `K`
    KS: KeyStorage<K>,
    // Value type
    V: Copy,
>(
    mem::BucketArrayPair<'k, 'v, N, CAP, C, KS, V>,
    PhantomData<K>,
);

impl<'k, 'v, const N: usize, const CAP: usize, C: Count, K: Key, KS: KeyStorage<K>, V: Copy>
    KeyValueBucketArray<'k, 'v, N, CAP, C, K, KS, V>
{
    /// A new [`KeyValueBucketArray`] wraps two mutable [`BucketArrayMemory`]
    /// references and adds state to track which items are valid.
    pub(crate) fn new(
        key_mem: &'k mut BucketArrayMemory<N, CAP, KS>,
        value_mem: &'v mut BucketArrayMemory<N, CAP, V>,
    ) -> Self {
        Self(mem::BucketArrayPair::new(key_mem, value_mem), PhantomData)
    }

    /// Keep the counts and the value memory but drop the key memory.
    ///
    /// Returns a new [`ValueBucketArray`].
    pub(crate) fn drop_key_storage(self) -> ValueBucketArray<'v, N, CAP, C, K, V> {
        ValueBucketArray(self.0.drop_first(), self.1)
    }
}

/// Concrete bucket array with a single [`BucketArrayMemory`] for value storage
///
/// Keys are used for bucket indexing but the remainder bits are not stored.
pub(crate) struct ValueBucketArray<
    // Lifetime for mutable reference to value storage memory
    'v,
    // Number of buckets
    const N: usize,
    // Maximum number of items per bucket
    const CAP: usize,
    // Type for bucket item counts
    C: Count,
    // Full size key type, used in the API and for calculating bucket indices
    K: Key,
    // Value type
    V: Copy,
>(mem::BucketArray<'v, N, CAP, C, V>, PhantomData<K>);

impl<'v, const N: usize, const CAP: usize, C: Count, K: Key, V: Copy>
    ValueBucketArray<'v, N, CAP, C, K, V>
{
    /// A new [`ValueBucketArray`] wraps one mutable [`BucketArrayMemory`]
    /// reference and adds a counts array to track which items are valid.
    pub(crate) fn new(value_mem: &'v mut BucketArrayMemory<N, CAP, V>) -> Self {
        Self(mem::BucketArray::new(value_mem), PhantomData)
    }
}

impl<'k, 'v, const N: usize, const CAP: usize, C: Count, K: Key, KS: KeyStorage<K>, V: Copy>
    Shape<K> for KeyValueBucketArray<'k, 'v, N, CAP, C, K, KS, V>
{
    /// Number of buckets in the array
    const NUM_BUCKETS: usize = N;
    /// Capacity of each bucket in the array
    const BUCKET_CAPACITY: usize = CAP;

    #[inline(always)]
    fn item_range(&self, bucket: usize) -> Range<usize> {
        self.0.item_range(bucket)
    }
}

impl<'v, const N: usize, const CAP: usize, C: Count, K: Key, V: Copy> Shape<K>
    for ValueBucketArray<'v, N, CAP, C, K, V>
{
    /// Number of buckets in the array
    const NUM_BUCKETS: usize = N;
    /// Capacity of each bucket in the array
    const BUCKET_CAPACITY: usize = CAP;

    #[inline(always)]
    fn item_range(&self, bucket: usize) -> Range<usize> {
        self.0.item_range(bucket)
    }
}

impl<'k, 'v, const N: usize, const CAP: usize, C: Count, K: Key, KS: KeyStorage<K>, V: Copy>
    Insert<K, V> for KeyValueBucketArray<'k, 'v, N, CAP, C, K, KS, V>
{
    #[inline(always)]
    fn insert(&mut self, key: K, value: V) -> Result<(), ()> {
        let (bucket, key_remainder) = self.split_wide_key(key);
        let key_storage = KS::from_key(key_remainder);
        self.0.insert(bucket, key_storage, value)
    }
}

impl<'v, const N: usize, const CAP: usize, C: Count, K: Key, V: Copy> Insert<K, V>
    for ValueBucketArray<'v, N, CAP, C, K, V>
{
    #[inline(always)]
    fn insert(&mut self, key: K, value: V) -> Result<(), ()> {
        let (bucket, _) = self.split_wide_key(key);
        self.0.insert(bucket, value)
    }
}

impl<'k, 'v, const N: usize, const CAP: usize, C: Count, K: Key, KS: KeyStorage<K>, V: Copy>
    KeyLookup<KS, K> for KeyValueBucketArray<'k, 'v, N, CAP, C, K, KS, V>
{
    #[inline(always)]
    fn item_stored_key(&self, bucket: usize, item: usize) -> KS {
        self.0.item_value_first(bucket, item)
    }

    #[inline(always)]
    fn item_full_key(&self, bucket: usize, item: usize) -> K {
        self.join_wide_key(bucket, self.item_stored_key(bucket, item).into_key())
    }
}

impl<'k, 'v, const N: usize, const CAP: usize, C: Count, K: Key, KS: KeyStorage<K>, V: Copy>
    ValueLookup<V> for KeyValueBucketArray<'k, 'v, N, CAP, C, K, KS, V>
{
    #[inline(always)]
    fn item_value(&self, bucket: usize, item: usize) -> V {
        self.0.item_value_second(bucket, item)
    }
}

impl<'v, const N: usize, const CAP: usize, C: Count, K: Key, V: Copy> ValueLookup<V>
    for ValueBucketArray<'v, N, CAP, C, K, V>
{
    #[inline(always)]
    fn item_value(&self, bucket: usize, item: usize) -> V {
        self.0.item_value(bucket, item)
    }
}

/// Trait for types that can be used as a count of items in a bucket
///
/// Whereas [`mem::Count`] is meant to be the minimum for that module's
/// purposes, this is an extended trait with features needed by the rest of
/// the crate.
pub(crate) trait Count: mem::Count + TryFrom<usize> {
    /// Convert from a usize item index, panic on overflow
    #[inline(always)]
    fn from_item_index(i: usize) -> Self {
        // Omit the original error type, to avoid propagating Debug bounds
        // for this trait. We might be able to stop doing this once the
        // associated_type_bounds Rust feature stabilizes.
        i.try_into()
            .map_err(|_| ())
            .expect("Bucket count type is always wide enough for item index")
    }
}

impl<T: mem::Count + TryFrom<usize>> Count for T {}

/// Types we can use as full width keys
pub(crate) trait Key:
    Copy
    + Zero
    + One
    + PartialEq<Self>
    + TryFrom<usize>
    + TryInto<usize>
    + Shl<usize, Output = Self>
    + Shr<usize, Output = Self>
    + Div<Self, Output = Self>
    + Rem<Self, Output = Self>
    + Mul<Self, Output = Self>
    + Not
    + Sub<Self, Output = Self>
    + BitAnd<Self, Output = Self>
    + WrappingAdd
    + WrappingNeg
{
    /// Build a Key from a bucket index.
    ///
    /// Panics if the index is out of range.
    #[inline(always)]
    fn from_bucket_index(i: usize) -> Self {
        i.try_into()
            .map_err(|_| ())
            .expect("Key type is always wide enough for a bucket index")
    }

    /// Convert this Key back into a bucket index.
    ///
    /// Panics if the value would not fit in a `usize`. No other range
    /// checking on the bucket index is enforced here.
    #[inline(always)]
    fn into_bucket_index(self) -> usize {
        self.try_into()
            .map_err(|_| ())
            .expect("Key is a bucket index which always fits in a usize")
    }

    /// Check if the N low bits of the key are zero.
    #[inline(always)]
    fn low_bits_are_zero(self, num_bits: usize) -> bool {
        (self & ((Self::one() << num_bits) - Self::one())) == Self::zero()
    }
}

impl<
        T: Copy
            + Zero
            + One
            + PartialEq<Self>
            + TryFrom<usize>
            + TryInto<usize>
            + Shl<usize, Output = Self>
            + Shr<usize, Output = Self>
            + Div<Self, Output = Self>
            + Rem<Self, Output = Self>
            + Mul<Self, Output = Self>
            + Not
            + Sub<Self, Output = Self>
            + BitAnd<Self, Output = Self>
            + WrappingAdd
            + WrappingNeg,
    > Key for T
{
}

/// Backing storage for a specific key type
///
/// Intended to be smaller than or equal in size to the full Key type.
pub(crate) trait KeyStorage<K>:
    Copy + Zero + Not<Output = Self> + TryFrom<K> + TryInto<K>
where
    K: Key,
{
    /// Fit the indicated key into a [`KeyStorage`], wrapping if necessary.
    ///
    /// It is normal for keys to accumulate additional insignificant bits on
    /// the left side as we compute sums.
    #[inline(always)]
    fn from_key(k: K) -> Self {
        let key_mask = (!Self::zero()).into_key();
        <K as TryInto<Self>>::try_into(k & key_mask)
            .map_err(|_| ())
            .expect("masked Key type always fits in KeyStorage")
    }

    /// Unpack this [`KeyStorage`] back into a Key type, without
    /// changing its value.
    #[inline(always)]
    fn into_key(self) -> K {
        self.try_into()
            .map_err(|_| ())
            .expect("Key type is always wider than KeyStorage")
    }
}

impl<T: Copy + Zero + Not<Output = Self> + TryFrom<K> + TryInto<K>, K: Key> KeyStorage<K> for T {}