1
use std::hash::BuildHasher;
2
use std::hash::Hash;
3
use std::mem;
4

            
5
use merc_unsafety::ConcurrentAppendVec;
6
use merc_unsafety::ShardedHashMap;
7
use rustc_hash::FxBuildHasher;
8

            
9
/// The default branching factor, matches a standard cache line of words.
10
const DEFAULT_BRANCHING: usize = 8;
11

            
12
/// Shards in the per-height interning tables.
13
const FOREST_SHARDS: usize = 16;
14

            
15
/// A value that can be stored in a [`SequenceForest`] node slot.
16
///
17
/// Nodes are untyped `[V; N]` arrays with neither a length nor a leaf/inner
18
/// discriminant, so the slot type itself must represent three things: a real
19
/// leaf value, an interior child reference, and an unused (empty) slot.
20
///
21
/// Implementations must keep these disjoint: [`Slot::EMPTY`] must never equal a
22
/// real value, and a slot produced by [`Slot::from_child`] is only ever decoded
23
/// with [`Slot::as_child`] when the traversal already knows (from the node's
24
/// height) that the slot holds a child reference.
25
pub trait Slot: Copy + Eq + Hash {
26
    /// Sentinel stored in unused trailing slots, in place of a per-node length.
27
    /// It must be a value the producer of real values never emits.
28
    const EMPTY: Self;
29

            
30
    /// Encodes a child node index into a slot value.
31
    fn from_child(index: usize) -> Self;
32

            
33
    /// Decodes a slot that the caller already knows (from the node's height)
34
    /// holds a child index.
35
    fn as_child(self) -> usize;
36
}
37

            
38
impl Slot for usize {
39
    const EMPTY: usize = usize::MAX;
40

            
41
582133
    fn from_child(index: usize) -> usize {
42
582133
        index
43
582133
    }
44

            
45
387245
    fn as_child(self) -> usize {
46
387245
        self
47
387245
    }
48
}
49

            
50
impl Slot for u32 {
51
    const EMPTY: u32 = u32::MAX;
52

            
53
    fn from_child(index: usize) -> u32 {
54
        // A `u32` slot caps the node pool at `u32::MAX` exclusive, since that
55
        // value is reserved for `EMPTY`. Encode defensively so exhausting that
56
        // cap is caught in debug builds instead of silently truncating a child
57
        // reference (or colliding with the empty-slot sentinel).
58
        debug_assert!(index < u32::MAX as usize, "node pool exceeded u32 slot capacity");
59
        index as u32
60
    }
61

            
62
    fn as_child(self) -> usize {
63
        self as usize
64
    }
65
}
66

            
67
/// Number of bits reserved for the height in the packed `Tree` field. Six bits
68
/// suffice because the tallest possible tree (branching factor two) has height
69
/// `max_height(2)`, which is representable in six bits (checked below).
70
const MAX_HEIGHT_BITS: u32 = 6;
71
const HEIGHT_MASK: u64 = (1 << MAX_HEIGHT_BITS) - 1;
72

            
73
/// The packed height field must be wide enough for the tallest tree any
74
/// supported branching factor can produce; two is the worst case.
75
const _: () = assert!(
76
    max_height(2) < (1 << MAX_HEIGHT_BITS),
77
    "MAX_HEIGHT_BITS too small for the tallest representable tree"
78
);
79

            
80
/// All-ones in the 58-bit root field; used as the empty-tree sentinel.
81
const ROOT_EMPTY: u64 = (1u64 << (64 - MAX_HEIGHT_BITS)) - 1;
82

            
83
/// A handle to a single tree stored in a [`SequenceForest`].
84
///
85
/// Handles are only meaningful for the forest that produced them and are
86
/// invalidated by [`SequenceForest::clear`].
87
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
88
pub struct Tree {
89
    /// Bits [63:6] hold the root node index; bits [5:0] hold the height.
90
    /// When bits [63:6] equal `ROOT_EMPTY` the tree is empty.
91
    packed: u64,
92
}
93

            
94
impl Tree {
95
    /// The empty tree, holding no entries.
96
    pub const EMPTY: Tree = Tree {
97
        packed: ROOT_EMPTY << MAX_HEIGHT_BITS,
98
    };
99

            
100
    /// Creates a new tree with the given root and height.
101
81847
    fn new(root: usize, height: u8) -> Tree {
102
81847
        debug_assert!((root as u64) < ROOT_EMPTY, "node pool exhausted");
103
81847
        Tree {
104
81847
            packed: ((root as u64) << MAX_HEIGHT_BITS) | (height as u64),
105
81847
        }
106
81847
    }
107

            
108
    /// Returns the root node index and height of this tree.
109
50750
    fn root(self) -> usize {
110
50750
        (self.packed >> MAX_HEIGHT_BITS) as usize
111
50750
    }
112

            
113
    /// Returns the height of this tree.
114
50750
    fn height(self) -> u8 {
115
50750
        (self.packed & HEIGHT_MASK) as u8
116
50750
    }
117

            
118
    /// Returns true if this tree has no entries.
119
73010
    pub fn is_empty(self) -> bool {
120
73010
        self.packed >> MAX_HEIGHT_BITS == ROOT_EMPTY
121
73010
    }
122
}
123

            
124
impl Default for Tree {
125
    fn default() -> Tree {
126
        Tree::EMPTY
127
    }
128
}
129

            
130
/// A thread-safe forest of hash-consed sequences of `V` with branching factor `N`.
131
///
132
/// # Details
133
///
134
/// Inspired by the `cranelift_bforest` crate. The crucial difference is that
135
/// this implementation uses hash consing to maximally share (immutable) nodes
136
/// across trees. This makes it suitable to compactly represent large sets of
137
/// similar sequences.
138
///
139
/// Values are expected to be small `Copy` types, matching the
140
/// `cranelift_bforest` design tradeoffs. The branching factor `N` can be
141
/// overridden to experiment with node sizes.
142
///
143
/// Because a value and a child index could share a bit pattern, interning is
144
/// partitioned by height: nodes are only ever deduplicated against other nodes
145
/// at the same height, so `[5, 7]` interpreted as values never collides with
146
/// `[5, 7]` interpreted as child references.
147
///
148
/// Nodes are never freed individually; the forest is append-only and reclaimed
149
/// all at once with [`SequenceForest::clear`].
150
pub struct SequenceForest<V, const N: usize = DEFAULT_BRANCHING, S = FxBuildHasher>
151
where
152
    V: Copy,
153
{
154
    /// The shared node pool. Each push hands out the index a node is stored at,
155
    /// which serves as its interned identity. Indices are unique but not dense.
156
    nodes: ConcurrentAppendVec<[V; N]>,
157
    /// One interning index per height, mapping a node's content to its index in
158
    /// `nodes`. Avoids duplicating the `nodes` information by passing the hash
159
    /// and equality functions as closures.
160
    tables: Vec<ShardedHashMap<usize, S>>,
161
}
162

            
163
/// Reusable scratch buffers so repeated inserts on a hot path do not
164
/// reallocate.
165
///
166
/// The buffers only hold node indices, so a single context works for any
167
/// [`SequenceForest`] regardless of its value type or branching factor.
168
#[derive(Debug, Default, Clone)]
169
pub struct SequenceForestContext {
170
    /// Child indices of the level just built.
171
    src: Vec<usize>,
172
    /// Child indices of the level currently being built.
173
    dst: Vec<usize>,
174
}
175

            
176
impl SequenceForestContext {
177
    /// Creates an empty context.
178
46422
    pub fn new() -> SequenceForestContext {
179
46422
        SequenceForestContext::default()
180
46422
    }
181
}
182

            
183
impl<V, const N: usize, S> SequenceForest<V, N, S>
184
where
185
    V: Slot,
186
    S: Default,
187
{
188
    /// Creates a new empty forest.
189
314
    pub fn new() -> SequenceForest<V, N, S> {
190
        const { assert!(N >= 2, "branching factor must be at least two") };
191
        // One interning table per representable height. The height field is
192
        // `MAX_HEIGHT_BITS` wide, so heights range over `0..(1 << MAX_HEIGHT_BITS)`.
193
314
        let tables = (0..(1usize << MAX_HEIGHT_BITS))
194
20096
            .map(|_| ShardedHashMap::with_shards_and_hasher(FOREST_SHARDS, S::default()))
195
314
            .collect();
196
314
        SequenceForest {
197
314
            nodes: ConcurrentAppendVec::new(),
198
314
            tables,
199
314
        }
200
314
    }
201
}
202

            
203
impl<V, const N: usize, S> SequenceForest<V, N, S>
204
where
205
    V: Slot,
206
    S: BuildHasher,
207
{
208
    /// Interns the sequence `values` and returns a handle to its tree, using a
209
    /// throwaway [`SequenceForestContext`]. Prefer [`SequenceForest::insert_with`] on
210
    /// hot paths to reuse the scratch buffers.
211
22912
    pub fn insert(&self, values: &[V]) -> Tree {
212
22912
        self.insert_with(values, &mut SequenceForestContext::new())
213
22912
    }
214

            
215
    /// Interns the sequence `values` and returns a handle to its tree, reusing
216
    /// any already-interned nodes. Equal sequences always yield the same
217
    /// handle.
218
    ///
219
    /// # Details
220
    ///
221
    /// The tree is assembled bottom-up: contiguous chunks of `values` become
222
    /// leaf-level nodes, then those are grouped into interior nodes, and so on
223
    /// until a single root remains.
224
68821
    pub fn insert_with(&self, values: &[V], context: &mut SequenceForestContext) -> Tree {
225
68821
        if values.is_empty() {
226
4210
            return Tree::EMPTY;
227
64611
        }
228

            
229
        // `src` is the level just built, `dst` the one being built. The buffers
230
        // live in `context` so repeated inserts do not reallocate, but they are
231
        // not shared between threads.
232
64611
        let SequenceForestContext { src, dst } = context;
233

            
234
        // Each chunk of `values` is copied straight into a node, padded with
235
        // `EMPTY`.
236
64611
        src.clear();
237
319169
        for chunk in values.chunks(N) {
238
319169
            let mut slots = [V::EMPTY; N];
239
610483
            for (offset, &value) in chunk.iter().enumerate() {
240
610483
                debug_assert!(value != V::EMPTY, "value collides with the empty-slot sentinel");
241
610483
                slots[offset] = value;
242
            }
243
319169
            src.push(self.intern(0, slots));
244
        }
245

            
246
        // Group the previous level into nodes of child indices until one node
247
        // remains.
248
64611
        let mut height = 0u8;
249
210159
        while src.len() > 1 {
250
145548
            height += 1;
251
145548
            dst.clear();
252
308855
            for chunk in src.chunks(N) {
253
308855
                let mut slots = [V::EMPTY; N];
254
563413
                for (offset, &child) in chunk.iter().enumerate() {
255
563413
                    slots[offset] = V::from_child(child);
256
563413
                }
257
308855
                dst.push(self.intern(height, slots));
258
            }
259
145548
            mem::swap(src, dst);
260
        }
261

            
262
64611
        Tree::new(src[0], height)
263
68821
    }
264

            
265
    /// Interns `data` at `height`, returning the index of the existing identical
266
    /// node if one is present, or of a freshly allocated node otherwise.
267
628024
    fn intern(&self, height: u8, data: [V; N]) -> usize {
268
628024
        debug_assert!(
269
628024
            (height as usize) <= max_height(N),
270
            "tree height exceeds max_height for this branching factor"
271
        );
272
        // The table is specific to this height, so entries are compared and
273
        // hashed purely by their node content.
274
628024
        let table = &self.tables[height as usize];
275
628024
        let hash = table.hash(&data);
276
628024
        let eq = |&index: &usize| self.node(index) == data;
277

            
278
        // Fast path: an existing node is found while only read-locking the shard.
279
628024
        if let Some(index) = table.find(hash, eq) {
280
405265
            return index;
281
222759
        }
282

            
283
        // `push` appends the node and returns the index it landed at. The slot is
284
        // published before `push` returns, so any thread that later reads the
285
        // index from `tables` can resolve it in `nodes`. `make` runs only on a
286
        // miss and while the shard is write-locked, so the index is unique.
287
222759
        table
288
222759
            .find_or_insert_with(
289
222759
                hash,
290
222759
                eq,
291
262779
                |&index| table.hash(&self.node(index)),
292
222758
                || {
293
222758
                    let index = self.nodes.push(data);
294
222758
                    debug_assert!((index as u64) < ROOT_EMPTY, "node pool exhausted");
295
222758
                    index
296
222758
                },
297
            )
298
            .0
299
628024
    }
300

            
301
    /// Returns a copy of the interned node at `index`, which must exist.
302
694352
    fn node(&self, index: usize) -> [V; N] {
303
        // SAFETY: `index` came from `tables`, which only holds indices returned
304
        // by `push`; the interning shard lock published that write before this
305
        // read, so `get_unchecked` is sound.
306
694352
        *unsafe { self.nodes.get_unchecked(index) }
307
694352
    }
308
}
309

            
310
/// Returns the maximum useful tree depth for a branching factor of `n`.
311
///
312
/// A tree of height `h` can hold at most `n^(h+1)` leaf values, so once
313
/// `h+1 > usize::BITS / floor(log2(n))` the tree would need more nodes than
314
/// a `usize` can index. This ceiling is therefore a tight upper bound on any
315
/// reachable depth.
316
1051225
pub const fn max_depth(n: usize) -> usize {
317
    // floor(log2(n))
318
1051225
    let log2_n = (usize::BITS - n.leading_zeros() - 1) as usize;
319
    // ceil(usize::BITS / log2_n)
320
1051225
    (usize::BITS as usize).div_ceil(log2_n)
321
1051225
}
322

            
323
/// Returns the maximum height any tree with branching factor `n` can reach.
324
///
325
/// Leaves sit at height zero, so a tree spanning `max_depth(n)` levels tops out
326
/// at one less than that. This is the largest value the packed height field of
327
/// a [`Tree`] ever has to hold.
328
663980
pub const fn max_height(n: usize) -> usize {
329
663980
    max_depth(n) - 1
330
663980
}
331

            
332
/// Upper bound on the height of any tree.
333
const MAX_DEPTH: usize = max_depth(2);
334

            
335
impl<V, const N: usize, S> SequenceForest<V, N, S>
336
where
337
    V: Slot,
338
{
339
    /// Returns an iterator over the values of `tree` in sequence order.
340
45162
    pub fn iter(&self, tree: Tree) -> Iter<'_, V, N> {
341
45162
        let mut stack = [([V::EMPTY; N], 0u32, 0u8); MAX_DEPTH];
342
45162
        let mut depth = 0;
343
45162
        if !tree.is_empty() {
344
42902
            // SAFETY: a `Tree` is only produced by interning its nodes, so the
345
42902
            // root index was returned by `push` and is published to any thread
346
42902
            // that holds the handle.
347
42902
            let root = *unsafe { self.nodes.get_unchecked(tree.root()) };
348
42902
            stack[0] = (root, 0, tree.height());
349
42902
            depth = 1;
350
42902
        }
351
45162
        Iter {
352
45162
            nodes: &self.nodes,
353
45162
            stack,
354
45162
            depth,
355
45162
        }
356
45162
    }
357
}
358

            
359
impl<V, const N: usize, S> SequenceForest<V, N, S>
360
where
361
    V: Slot,
362
{
363
    /// Returns the number of distinct nodes currently held by the forest. This
364
    /// is the deduplicated node count, not the total number of entries across
365
    /// all trees.
366
2624
    pub fn node_count(&self) -> usize {
367
2624
        self.nodes.len()
368
2624
    }
369

            
370
    /// Removes every tree from the forest, invalidating all outstanding
371
    /// [`Tree`] handles.
372
    pub fn clear(&mut self) {
373
        self.nodes.clear();
374
        for table in &self.tables {
375
            table.clear();
376
        }
377
    }
378
}
379

            
380
impl<V, const N: usize, S> Default for SequenceForest<V, N, S>
381
where
382
    V: Slot,
383
    S: Default,
384
{
385
    fn default() -> SequenceForest<V, N, S> {
386
        SequenceForest::new()
387
    }
388
}
389

            
390
/// In-order iterator over the values of a single tree.
391
///
392
/// The descent stack is a fixed-size array, so no heap allocation is performed,
393
/// which keeps reconstruction cheap on hot paths. Each frame caches a copy of
394
/// its node's slot array, so a node is fetched from the shared pool at most once
395
/// per descent rather than on every value.
396
pub struct Iter<'a, V, const N: usize>
397
where
398
    V: Copy,
399
{
400
    nodes: &'a ConcurrentAppendVec<[V; N]>,
401
    /// Frames on the path from the root, each holding the node's slot array, the
402
    /// index of the next slot to visit in it, and the node's height.
403
    stack: [([V; N], u32, u8); MAX_DEPTH],
404
    depth: usize,
405
}
406

            
407
impl<V, const N: usize> Iterator for Iter<'_, V, N>
408
where
409
    V: Slot,
410
{
411
    type Item = V;
412

            
413
458638
    fn next(&mut self) -> Option<V> {
414
1261525
        while self.depth > 0 {
415
1216948
            let top = self.depth - 1;
416
1216948
            let slot = self.stack[top].1 as usize;
417
1216948
            let height = self.stack[top].2;
418

            
419
            // A slot past the end or holding the empty sentinel exhausts this
420
            // node; pop back to its parent.
421
1216948
            if slot >= N || self.stack[top].0[slot] == V::EMPTY {
422
422602
                self.depth -= 1;
423
422602
                continue;
424
794346
            }
425

            
426
794346
            let value = self.stack[top].0[slot];
427
794346
            self.stack[top].1 += 1;
428

            
429
794346
            if height == 0 {
430
                // Height zero means the slot holds a leaf value.
431
414061
                return Some(value);
432
380285
            }
433

            
434
            // Otherwise it is a child reference; descend into it.
435
380285
            assert!(self.depth < max_depth(N), "tree deeper than max_depth({N})");
436
            // SAFETY: child indices are stored in interned nodes, so they were
437
            // returned by `push` and published when the tree was built.
438
380285
            let child = *unsafe { self.nodes.get_unchecked(value.as_child()) };
439
380285
            self.stack[self.depth] = (child, 0, height - 1);
440
380285
            self.depth += 1;
441
        }
442
44577
        None
443
458638
    }
444
}
445

            
446
#[cfg(test)]
447
mod tests {
448
    use std::collections::HashMap;
449
    use std::sync::Arc;
450
    use std::sync::Mutex;
451

            
452
    use rand::RngExt;
453

            
454
    use merc_utilities::random_test;
455
    use merc_utilities::random_test_threads;
456

            
457
    use super::SequenceForest;
458
    use super::SequenceForestContext;
459
    use super::Tree;
460

            
461
    #[test]
462
    #[cfg_attr(miri, ignore)]
463
1
    fn random_insert_iter_dedup() {
464
100
        random_test(100, |rng| {
465
            // Interns random sequences and checks every forest invariant against a
466
            // HashMap implementation.
467
100
            let forest: SequenceForest<usize, 2> = SequenceForest::new();
468
100
            let mut context = SequenceForestContext::new();
469
100
            let mut seen: HashMap<Vec<usize>, Tree> = HashMap::new();
470

            
471
100
            for _ in 0..200 {
472
20000
                let len = rng.random_range(0..20);
473
190093
                let seq: Vec<usize> = (0..len).map(|_| rng.random_range(0..10)).collect();
474

            
475
20000
                let tree = forest.insert_with(&seq, &mut context);
476
20000
                assert_eq!(forest.insert(&seq), tree, "context path matches the allocating path");
477

            
478
20000
                let got: Vec<usize> = forest.iter(tree).collect();
479
20000
                assert_eq!(got, seq);
480
20000
                assert_eq!(
481
20000
                    seq.is_empty(),
482
20000
                    tree.is_empty(),
483
                    "only the empty sequence is the empty tree"
484
                );
485

            
486
20000
                match seen.get(&seq) {
487
1312
                    Some(&prev) => {
488
1312
                        let before = forest.node_count();
489
1312
                        assert_eq!(forest.insert(&seq), prev, "equal sequences share a handle");
490
1312
                        assert_eq!(forest.node_count(), before, "re-interning must not allocate nodes");
491
                    }
492
18688
                    None => {
493
18688
                        seen.insert(seq, tree);
494
18688
                    }
495
                }
496
            }
497
100
        });
498
1
    }
499

            
500
    #[test]
501
    #[cfg_attr(miri, ignore)]
502
1
    fn random_concurrent_intern() {
503
        // Interns random sequences concurrently.
504
1
        let forest: Arc<SequenceForest<usize, 4>> = Arc::new(SequenceForest::new());
505
1
        let seen: Arc<Mutex<HashMap<Vec<usize>, Tree>>> = Arc::new(Mutex::new(HashMap::new()));
506

            
507
1
        random_test_threads(
508
            200,
509
            8,
510
8
            || (Arc::clone(&forest), Arc::clone(&seen)),
511
1600
            move |rng, (forest, seen)| {
512
1600
                let len = rng.random_range(0..20);
513
15323
                let seq: Vec<usize> = (0..len).map(|_| rng.random_range(0..30)).collect();
514

            
515
1600
                let tree = forest.insert(&seq);
516
1600
                let got: Vec<usize> = forest.iter(tree).collect();
517
1600
                assert_eq!(got, seq);
518

            
519
1600
                let mut seen = seen.lock().unwrap();
520
1600
                match seen.get(&seq) {
521
134
                    Some(&prev) => assert_eq!(prev, tree, "equal sequences interned to different trees"),
522
1466
                    None => {
523
1466
                        seen.insert(seq, tree);
524
1466
                    }
525
                }
526
1600
            },
527
        );
528
1
    }
529
}