FE 0.14.0
A C++23 toolkit for writing compiler/interpreter frontends.
Loading...
Searching...
No Matches
patricia.h
Go to the documentation of this file.
1#pragma once
2
3#include <concepts>
4#include <cstddef>
5#include <cstdint>
6
7#include <algorithm>
8#include <array>
9#include <bit>
10#include <functional>
11#include <initializer_list>
12#include <iostream>
13#include <iterator>
14#include <memory>
15#include <ostream>
16#include <print>
17#include <ranges>
18
19#ifdef FE_ABSL
20# include <absl/container/flat_hash_set.h>
21#else
22# include <unordered_set>
23#endif
24
25#include "fe/arena.h"
26#include "fe/assert.h"
27#include "fe/hash.h"
28#include "fe/span.h"
29#include "fe/vector.h"
30
31namespace fe {
32
33/// Hash-consed, immutable sets of `D*`, ordered by an unsigned id the elements carry themselves.
34/// A [Patricia tree](https://dl.acm.org/doi/10.1145/321479.321481) - a prefix trie over the big-endian bits of
35/// the id, as in Okasaki and Gill's [Fast Mergeable Integer Maps](https://ku-fpg.github.io/papers/Okasaki-98-IntMap/)
36/// - in the four flavours below, picked by size alone; equal sets are therefore *pointer-equal*.
37///
38/// | Flavour | Holds |
39/// |---------|-------|
40/// | empty | nothing; a Set that converts to `false` |
41/// | `Uniq` | exactly one `D*` - inline in the Set itself, so a singleton costs no node at all |
42/// | `Arr` | 2 to @p N elements, sorted by id |
43/// | `Br` | more than @p N elements: a prefix, its branching bit, and two non-empty children |
44///
45/// The flavour is picked for *every* subtree, so a branch's children are again single elements, arrays, or
46/// branches - which collapses the bottom @f$\log_2 N@f$ levels of the trie into one array each.
47/// Patricia shape is canonical (the branching bit of an id set is just the highest bit its minimum and maximum
48/// disagree on), so this keeps one element set mapped to exactly one representation.
49///
50/// Ids are ordered as **unsigned**, i.e. iteration yields `0` first and `K(-1)` last.
51/// @p KT is a *key* trait:
52/// ```
53/// struct Key {
54/// static K key(const D*) noexcept; ///< Unique id; orders the elements.
55/// static std::ostream& stream(std::ostream&, const D*); ///< Optional; defaults to Key::key.
56/// };
57/// ```
58/// @attention Key::key *is* the identity: two elements sharing an id are the same element to a Set, and which of
59/// them survives an operation is unspecified.
60/// Every `D` must be at least 4-byte aligned, since a Set tags the two low bits of its word.
61/// @note All operations yield a **new** Set; none of them modify their input.
62/// Nothing is ever freed - the Arena%s release everything at once when the Patricia dies.
63template<class D, class KT, class K = uint32_t, size_t N = 8>
64class Patricia {
65 static_assert(std::unsigned_integral<K>, "Patricia ids are ordered as unsigned");
66 static_assert(N >= 2, "an array node holds 2 to N elements");
67
68private:
69 /// @name Okasaki Bit Twiddling
70 ///@{
71 /// Bits strictly above the branching bit @p m; `0` if @p m is the top bit.
72 static constexpr K above(K m) noexcept { return K(K(0) - K(m << 1)); }
73 /// Is @p k left of the branching bit @p m?
74 static constexpr bool zero_bit(K k, K m) noexcept { return (k & m) == 0; }
75 /// @p k with the branching bit @p m and everything below it cleared; @p k itself for the leaf mask `0`.
76 static constexpr K mask_of(K k, K m) noexcept { return m == 0 ? k : K(k & above(m)); }
77 /// The highest bit @p k1 and @p k2 disagree on; `0` iff they are equal.
78 static constexpr K branching_bit(K k1, K k2) noexcept { return std::bit_floor(K(k1 ^ k2)); }
79 /// Does @p k live under the prefix @p p with branching bit @p m?
80 static constexpr bool match_prefix(K k, K p, K m) noexcept { return mask_of(k, m) == p; }
81 ///@}
82
83 /// An element and its id.
84 /// The id is redundant - but reading it through `KT::key` would chase the pointer on every comparison.
85 struct Entry {
86 K key;
87 D* d;
88 };
89
90 /// 2 to @p N entries, sorted by id.
91 struct Arr {
92 constexpr Arr(size_t hash, uint32_t size) noexcept
93 : hash(hash)
94 , size(size) {}
95
96 size_t hash; ///< Cached: the pool rehashes, so recomputing this over all Arr::entries hurts.
97 uint32_t size;
98 Entry entries[];
99 };
100
101 /// More than @p N elements.
102 struct Br {
103 constexpr Br(K prefix, K mask, size_t size, uintptr_t l, uintptr_t r) noexcept
104 : prefix(prefix)
105 , mask(mask)
106 , size(size)
107 , l(l)
108 , r(r) {}
109
110 K prefix; ///< What all ids below agree on; Br::mask and everything under it cleared.
111 K mask; ///< The branching bit.
112 size_t size; ///< `> N`.
113 uintptr_t l; ///< Tagged Set word of the child with Br::mask clear; never empty.
114 uintptr_t r; ///< Tagged Set word of the child with Br::mask set; never empty.
115 };
116
117 static_assert(alignof(Arr) >= 4 && alignof(Br) >= 4);
118
119 /// How the id spans of two branches relate - the case analysis every binary operation below walks.
120 enum class Rel {
121 Same, ///< The same span: their children pair up.
122 L1, ///< The second one lives under the first one's left child.
123 R1, ///< ... under its right child.
124 L2, ///< The first one lives under the second one's left child.
125 R2, ///< ... under its right child.
126 None, ///< Disjoint spans.
127 };
128
129 static Rel rel(const Br* n1, const Br* n2) noexcept {
130 if (n1->mask == n2->mask && n1->prefix == n2->prefix) return Rel::Same;
131
132 if (n1->mask > n2->mask && match_prefix(n2->prefix, n1->prefix, n1->mask))
133 return zero_bit(n2->prefix, n1->mask) ? Rel::L1 : Rel::R1;
134
135 if (n2->mask > n1->mask && match_prefix(n1->prefix, n2->prefix, n2->mask))
136 return zero_bit(n1->prefix, n2->mask) ? Rel::L2 : Rel::R2;
137
138 return Rel::None;
139 }
140
141public:
142 /// An immutable set - really just a tagged pointer, so copy it around freely.
143 class Set {
144 private:
145 /// An untagged word *is* the `D*` it holds, so a `Uniq` needs no node - and `0` is the empty Set.
146 enum class Tag : uintptr_t { Uniq = 0, Arr = 1, Br = 2 };
147
148 static constexpr uintptr_t Tag_Mask = 0b11;
149
150 constexpr explicit Set(uintptr_t word) noexcept
151 : word_(word) {}
152 constexpr Set(const Arr* n) noexcept
153 : word_(std::bit_cast<uintptr_t>(n) | uintptr_t(Tag::Arr)) {}
154 constexpr Set(const Br* n) noexcept
155 : word_(std::bit_cast<uintptr_t>(n) | uintptr_t(Tag::Br)) {}
156
157 constexpr Tag tag() const noexcept { return Tag(word_ & Tag_Mask); }
158 template<class T>
159 constexpr const T* ptr() const noexcept {
160 return std::bit_cast<const T*>(word_ & ~Tag_Mask);
161 }
162 // clang-format off
163 /// The one element of a `Uniq` - `nullptr` for every other flavour, the empty Set included.
164 constexpr D* isa_uniq() const noexcept { return tag() == Tag::Uniq ? std::bit_cast<D*>(word_) : nullptr; }
165 constexpr const Arr* isa_arr() const noexcept { return tag() == Tag::Arr ? ptr<Arr>() : nullptr; }
166 constexpr const Br* isa_br () const noexcept { return tag() == Tag::Br ? ptr<Br >() : nullptr; }
167 constexpr Set left () const noexcept { return Set(ptr<Br>()->l); }
168 constexpr Set right() const noexcept { return Set(ptr<Br>()->r); }
169 // clang-format on
170
171 /// The entries of an `Arr`; empty for every other flavour - a `Uniq` has no node to point at.
172 constexpr View<Entry> entries() const noexcept {
173 if (auto n = isa_arr()) return View<Entry>(n->entries, size_t(n->size));
174 return {};
175 }
176
177 /// What all ids agree on - the id itself for a `Uniq`.
178 K prefix() const noexcept {
179 if (auto n = isa_br()) return n->prefix;
180 if (auto d = isa_uniq()) return KT::key(d);
181 auto es = entries();
182 return mask_of(es.front().key, branching_bit(es.front().key, es.back().key));
183 }
184
185 public:
186 /// Yields the `D*` in ascending Key::key order.
187 /// @note The trie is at most `8 * sizeof(K)` deep, so the path stack sits inside the iterator.
188 class iterator {
189 public:
190 using iterator_category = std::forward_iterator_tag;
191 using difference_type = std::ptrdiff_t;
192 using value_type = D*;
193 using pointer = D*;
194 using reference = D*;
195
196 iterator() noexcept = default;
197
198 reference operator*() const noexcept { return uniq_ ? uniq_ : i_->d; }
199 pointer operator->() const noexcept { return this->operator*(); }
200
201 iterator& operator++() noexcept {
202 if (uniq_) return uniq_ = nullptr, advance();
203 return ++i_ == e_ ? advance() : *this;
204 }
205
206 iterator operator++(int) noexcept {
207 auto res = *this;
208 this->operator++();
209 return res;
210 }
211
212 bool operator==(const iterator& other) const noexcept { return i_ == other.i_ && uniq_ == other.uniq_; }
213
214 private:
215 explicit iterator(uintptr_t w) noexcept {
216 if (w != 0) descend(w);
217 }
218
219 /// Walks to the leftmost block below @p w, remembering the branches on the way.
220 void descend(uintptr_t w) noexcept {
221 for (; Tag(w & Tag_Mask) == Tag::Br; w = path_[depth_++]->l) {
222 // A branch's mask strictly decreases downwards and the deepest one still spans > N ids.
223 assert(depth_ < path_.size());
224 path_[depth_] = std::bit_cast<const Br*>(w & ~Tag_Mask);
225 left_ |= uint64_t(1) << depth_;
226 }
227
228 if (Tag(w & Tag_Mask) == Tag::Uniq) {
229 uniq_ = std::bit_cast<D*>(w);
230 i_ = e_ = nullptr;
231 } else {
232 auto n = std::bit_cast<const Arr*>(w & ~Tag_Mask);
233 i_ = n->entries;
234 e_ = n->entries + n->size;
235 }
236 }
237
238 /// Climbs to the nearest branch we are still in the left child of and takes its right one.
239 iterator& advance() noexcept {
240 for (; depth_ != 0; --depth_) {
241 auto d = depth_ - 1;
242 if (left_ & (uint64_t(1) << d)) {
243 left_ &= ~(uint64_t(1) << d);
244 descend(path_[d]->r);
245 return *this;
246 }
247 }
248
249 uniq_ = nullptr;
250 i_ = e_ = nullptr;
251 return *this;
252 }
253
254 D* uniq_ = nullptr; ///< The element of a `Uniq`, which has no node to point into.
255 const Entry* i_ = nullptr;
256 const Entry* e_ = nullptr;
257 std::array<const Br*, 8 * sizeof(K)> path_{};
258 uint64_t left_ = 0; ///< One bit per level: are we in `path_[level]`'s left child?
259 size_t depth_ = 0;
260
261 friend class Set;
262 };
263
264 /// @name Construction
265 ///@{
266 constexpr Set() noexcept = default; ///< The empty set.
267
268 /// The singleton @f$\{d\}@f$ - stored inline, so this allocates nothing.
269 constexpr explicit Set(D* d) noexcept
270 : word_(std::bit_cast<uintptr_t>(d)) {
271 assert((word_ & Tag_Mask) == 0 && "a D must be at least 4-byte aligned");
272 }
273 ///@}
274
275 /// @name Getters
276 ///@{
277 size_t size() const noexcept {
278 if (auto n = isa_arr()) return n->size;
279 if (auto n = isa_br()) return n->size;
280 return empty() ? 0 : 1;
281 }
282
283 constexpr bool empty() const noexcept { return word_ == 0; }
284 constexpr explicit operator bool() const noexcept { return !empty(); } ///< Not empty?
285
286 D* min() const noexcept { return edge(false); } ///< Smallest id - or `nullptr`.
287 D* max() const noexcept { return edge(true); } ///< Largest id - or `nullptr`.
288 ///@}
289
290 /// @name Check Membership
291 ///@{
292 bool contains(D* d) const noexcept { return lookup(KT::key(d)) != nullptr; }
293
294 /// Is @f$this \cap other \neq \emptyset@f$?
295 [[nodiscard]] bool has_intersection(Set other) const noexcept {
296 if (this->empty() || other.empty()) return false;
297 if (*this == other) return true;
298
299 auto n1 = this->isa_br();
300 auto n2 = other.isa_br();
301
302 if (!n1) return this->any_in(other);
303 if (!n2) return other.any_in(*this);
304
305 switch (rel(n1, n2)) {
306 case Rel::Same:
307 return this->left().has_intersection(other.left()) || this->right().has_intersection(other.right());
308 case Rel::L1: return this->left().has_intersection(other);
309 case Rel::R1: return this->right().has_intersection(other);
310 case Rel::L2: return this->has_intersection(other.left());
311 case Rel::R2: return this->has_intersection(other.right());
312 case Rel::None: return false;
313 }
314 unreachable();
315 }
316
317 /// Is @f$this \subseteq other@f$?
318 [[nodiscard]] bool subset_of(Set other) const noexcept {
319 if (*this == other || this->empty()) return true;
320 if (this->size() > other.size()) return false;
321
322 auto n1 = this->isa_br();
323 auto n2 = other.isa_br();
324
325 if (!n1) return this->all_in(other);
326 if (!n2) return false; // a Br holds more than N elements, so the size check above already caught this
327
328 switch (rel(n1, n2)) {
329 case Rel::Same: return this->left().subset_of(other.left()) && this->right().subset_of(other.right());
330 case Rel::L2: return this->subset_of(other.left());
331 case Rel::R2: return this->subset_of(other.right());
332 default: return false; // `this` spans ids `other` does not even branch on
333 }
334 }
335 ///@}
336
337 /// @name Iterators
338 /// Ascending by id, compared as **unsigned**.
339 ///@{
340 iterator begin() const noexcept { return iterator(word_); }
341 iterator end() const noexcept { return {}; }
342
343 /// Like iterating, but without the iterator's path stack.
344 template<class F>
345 void for_each(F&& f) const {
346 if (isa_br()) {
347 left().for_each(f);
348 right().for_each(f);
349 } else if (auto d = isa_uniq()) {
350 std::invoke(f, d);
351 } else {
352 for (const auto& e : entries())
353 std::invoke(f, e.d);
354 }
355 }
356 ///@}
357
358 /// @name Comparisons
359 /// Everything is hash-consed and a singleton is always `Uniq`, so this compares contents in `O(1)`.
360 ///@{
361 constexpr bool operator==(Set other) const noexcept { return this->word_ == other.word_; }
362 ///@}
363
364 /// @name Output
365 ///@{
366 std::ostream& stream(std::ostream& os) const {
367 os << '{';
368 auto sep = "";
369 for (auto d : *this) {
370 os << sep;
371 if constexpr (requires { KT::stream(os, d); })
372 KT::stream(os, d);
373 else
374 os << +KT::key(d);
375 sep = ", ";
376 }
377 return os << '}';
378 }
379
380 void dump() const { stream(std::cout) << std::endl; }
381
382 void dot(std::ostream& os) const {
383 std::print(os, "digraph {{\nordering=out;\nnode [shape=box,style=filled];\n");
384 dot(os, *this);
385 std::print(os, "}}\n");
386 }
387 ///@}
388
389 private:
390 /// The element with @p key - or `nullptr`.
391 /// A block holds at most @p N elements, so a scan beats a binary search; the *insertion* point still wants
392 /// `std::lower_bound`, since a miss would scan the whole block.
393 D* lookup(K key) const noexcept {
394 for (auto s = *this;;) {
395 if (auto n = s.isa_br()) {
396 if (!match_prefix(key, n->prefix, n->mask)) return nullptr;
397 s = zero_bit(key, n->mask) ? s.left() : s.right();
398 } else if (auto d = s.isa_uniq()) {
399 return KT::key(d) == key ? d : nullptr;
400 } else {
401 for (const auto& e : s.entries())
402 if (e.key == key) return e.d;
403 return nullptr;
404 }
405 }
406 }
407
408 D* edge(bool last) const noexcept {
409 auto s = *this;
410 while (auto n = s.isa_br())
411 s = last ? s.right() : s.left();
412 if (auto d = s.isa_uniq()) return d;
413 auto es = s.entries();
414 return es.empty() ? nullptr : (last ? es.back().d : es.front().d);
415 }
416
417 /// @name Block Predicates
418 /// `this` is not a `Br` and hence holds at most @p N elements.
419 ///@{
420 bool any_in(Set other) const noexcept {
421 if (auto d = isa_uniq()) return other.contains(d);
422 return std::ranges::any_of(entries(), [other](const Entry& e) { return other.lookup(e.key); });
423 }
424
425 bool all_in(Set other) const noexcept {
426 if (auto d = isa_uniq()) return other.contains(d);
427 return std::ranges::all_of(entries(), [other](const Entry& e) { return other.lookup(e.key); });
428 }
429 ///@}
430
431 /// Appends the entries this very node stores - none for a `Br` - to @p o.
432 template<class O>
433 O copy(O o) const noexcept {
434 if (auto d = isa_uniq()) return *o++ = Entry{KT::key(d), d}, o;
435 return std::ranges::copy(entries(), o).out;
436 }
437
438 static void dot(std::ostream& os, Set s) {
439 if (auto n = s.isa_br()) {
440 std::print(os, "n{} [label=\"{:#x}/{:#x}\"];\n", s.word_, uint64_t(n->prefix), uint64_t(n->mask));
441 for (auto child : {s.left(), s.right()}) {
442 std::print(os, "n{} -> n{};\n", s.word_, child.word_);
443 dot(os, child);
444 }
445 } else {
446 std::print(os, "n{} [label=\"", s.word_);
447 s.stream(os);
448 std::print(os, "\"];\n");
449 }
450 }
451
452 uintptr_t word_ = 0;
453
454 friend class Patricia;
455 friend std::ostream& operator<<(std::ostream& os, Set s) { return s.stream(os); }
456 };
457
458 static_assert(std::forward_iterator<typename Set::iterator>);
459 static_assert(std::ranges::range<Set>);
460
461 /// @name Construction
462 ///@{
463 Patricia& operator=(const Patricia&) = delete;
464
465 explicit Patricia(size_t page_size = Arena::Default_Page_Size)
466 : arr_arena_(page_size)
467 , br_arena_(page_size) {}
468 Patricia(const Patricia&) = delete;
470 : Patricia() {
471 swap(*this, other);
472 }
473 ///@}
474
475 /// @name Set Operations
476 /// @note These operations do **not** modify their input; they yield a **new** Set.
477 ///@{
478
479 /// Creates a Set with all elements in @p r.
480 template<std::ranges::input_range R>
481 requires std::convertible_to<std::ranges::range_reference_t<R>, D*> [[nodiscard]] Set create(R&& r) {
482 auto v = Vector<Entry>();
483 for (D* d : r)
484 v.emplace_back(Entry{KT::key(d), d});
485 return build(v);
486 }
487
488 [[nodiscard]] Set create(std::initializer_list<D*> list) { return create(View<D*>(list.begin(), list.size())); }
489
490 /// Yields @f$s \cup \{d\}@f$.
491 /// @note @p s comes back unchanged if it already holds an element with @p d's id.
492 [[nodiscard]] Set insert(Set s, D* d) {
493 auto key = KT::key(d);
494
495 if (s.empty()) return Set(d);
496
497 if (auto u = s.isa_uniq()) {
498 auto k = KT::key(u);
499 if (k == key) return s;
500 Entry es[2];
501 if (key < k)
502 es[0] = Entry{key, d}, es[1] = Entry{k, u};
503 else
504 es[0] = Entry{k, u}, es[1] = Entry{key, d};
505 return arr(View<Entry>(es, size_t(2)));
506 }
507
508 if (auto n = s.isa_br()) {
509 if (!match_prefix(key, n->prefix, n->mask)) return join(s, Set(d));
510
511 if (zero_bit(key, n->mask)) {
512 auto l = insert(s.left(), d);
513 return l == s.left() ? s : br(n->prefix, n->mask, l, s.right());
514 }
515
516 auto r = insert(s.right(), d);
517 return r == s.right() ? s : br(n->prefix, n->mask, s.left(), r);
518 }
519
520 auto es = s.entries();
521 auto i = std::ranges::lower_bound(es, key, {}, &Entry::key);
522 if (i != es.end() && i->key == key) return s;
523
524 auto pos = size_t(i - es.begin());
525 auto buf = std::array<Entry, N + 1>();
526 auto o = std::ranges::copy(es.first(pos), buf.begin()).out;
527 *o++ = Entry{key, d};
528 o = std::ranges::copy(es.subspan(pos), o).out;
529 return make(View<Entry>(buf.data(), size_t(o - buf.begin())));
530 }
531
532 /// Yields @f$s \setminus \{d\}@f$.
533 [[nodiscard]] Set erase(Set s, D* d) {
534 auto key = KT::key(d);
535
536 if (auto u = s.isa_uniq()) return KT::key(u) == key ? Set() : s;
537
538 if (auto n = s.isa_br()) {
539 if (!match_prefix(key, n->prefix, n->mask)) return s;
540
541 if (zero_bit(key, n->mask)) {
542 auto l = erase(s.left(), d);
543 return l == s.left() ? s : br(n->prefix, n->mask, l, s.right());
544 }
545
546 auto r = erase(s.right(), d);
547 return r == s.right() ? s : br(n->prefix, n->mask, s.left(), r);
548 }
549
550 auto es = s.entries();
551 auto i = std::ranges::find(es, key, &Entry::key);
552 if (i == es.end()) return s;
553
554 auto pos = size_t(i - es.begin());
555 auto buf = std::array<Entry, N>();
556 auto o = std::ranges::copy(es.first(pos), buf.begin()).out;
557 o = std::ranges::copy(es.subspan(pos + 1), o).out;
558 return make(View<Entry>(buf.data(), size_t(o - buf.begin())));
559 }
560
561 /// Yields @f$s_1 \cup s_2@f$.
562 [[nodiscard]] Set merge(Set s1, Set s2) {
563 if (s1 == s2 || s2.empty()) return s1;
564 if (s1.empty()) return s2;
565
566 auto n1 = s1.isa_br();
567 auto n2 = s2.isa_br();
568
569 if (!n1 && !n2) return merge_blocks(s1, s2);
570 if (!n1) return insert_all(s2, s1);
571 if (!n2) return insert_all(s1, s2);
572
573 switch (rel(n1, n2)) {
574 case Rel::Same: return br(n1->prefix, n1->mask, merge(s1.left(), s2.left()), merge(s1.right(), s2.right()));
575 case Rel::L1: return br(n1->prefix, n1->mask, merge(s1.left(), s2), s1.right());
576 case Rel::R1: return br(n1->prefix, n1->mask, s1.left(), merge(s1.right(), s2));
577 case Rel::L2: return br(n2->prefix, n2->mask, merge(s1, s2.left()), s2.right());
578 case Rel::R2: return br(n2->prefix, n2->mask, s2.left(), merge(s1, s2.right()));
579 case Rel::None: return join(s1, s2);
580 }
581 unreachable();
582 }
583
584 /// Yields @f$s_1 \cap s_2@f$.
585 [[nodiscard]] Set intersect(Set s1, Set s2) {
586 if (s1 == s2) return s1;
587 if (s1.empty() || s2.empty()) return {};
588
589 auto n1 = s1.isa_br();
590 auto n2 = s2.isa_br();
591
592 if (!n1) return filter(s1, s2, true);
593 if (!n2) return filter(s2, s1, true);
594
595 switch (rel(n1, n2)) {
596 case Rel::Same:
597 return br(n1->prefix, n1->mask, intersect(s1.left(), s2.left()), intersect(s1.right(), s2.right()));
598 case Rel::L1: return intersect(s1.left(), s2);
599 case Rel::R1: return intersect(s1.right(), s2);
600 case Rel::L2: return intersect(s1, s2.left());
601 case Rel::R2: return intersect(s1, s2.right());
602 case Rel::None: return {};
603 }
604 unreachable();
605 }
606
607 /// Yields @f$s_1 \setminus s_2@f$.
608 [[nodiscard]] Set diff(Set s1, Set s2) {
609 if (s1 == s2) return {};
610 if (s1.empty() || s2.empty()) return s1;
611
612 auto n1 = s1.isa_br();
613 auto n2 = s2.isa_br();
614
615 if (!n1) return filter(s1, s2, false);
616 if (!n2) return erase_all(s1, s2);
617
618 switch (rel(n1, n2)) {
619 case Rel::Same: return br(n1->prefix, n1->mask, diff(s1.left(), s2.left()), diff(s1.right(), s2.right()));
620 case Rel::L1: return br(n1->prefix, n1->mask, diff(s1.left(), s2), s1.right());
621 case Rel::R1: return br(n1->prefix, n1->mask, s1.left(), diff(s1.right(), s2));
622 case Rel::L2: return diff(s1, s2.left());
623 case Rel::R2: return diff(s1, s2.right());
624 case Rel::None: return s1;
625 }
626 unreachable();
627 }
628 ///@}
629
630 friend void swap(Patricia& p1, Patricia& p2) noexcept {
631 using std::swap;
632 // clang-format off
633 swap(p1.arr_arena_, p2.arr_arena_);
634 swap(p1.br_arena_, p2.br_arena_);
635 swap(p1.arrs_, p2.arrs_);
636 swap(p1.brs_, p2.brs_);
637 // clang-format on
638 }
639
640private:
641 /// @name Small Cases
642 /// At least one operand is not a `Br` and hence holds at most @p N elements.
643 ///@{
644
645 /// Both do: one linear pass beats inserting them one by one.
646 Set merge_blocks(Set s1, Set s2) {
647 if (auto d = s1.isa_uniq()) return insert(s2, d);
648 if (auto d = s2.isa_uniq()) return insert(s1, d);
649
650 auto es1 = s1.entries();
651 auto es2 = s2.entries();
652 auto buf = std::array<Entry, 2 * N>();
653 auto i1 = es1.begin(), i2 = es2.begin();
654 auto o = buf.begin();
655
656 while (i1 != es1.end() && i2 != es2.end())
657 if (i1->key < i2->key)
658 *o++ = *i1++;
659 else if (i2->key < i1->key)
660 *o++ = *i2++;
661 else
662 *o++ = *i1++, ++i2;
663
664 o = std::ranges::copy(std::ranges::subrange(i1, es1.end()), o).out;
665 o = std::ranges::copy(std::ranges::subrange(i2, es2.end()), o).out;
666 return make(View<Entry>(buf.data(), size_t(o - buf.begin())));
667 }
668
669 /// Folds @p s into @p t one element at a time.
670 Set insert_all(Set t, Set s) {
671 if (auto d = s.isa_uniq()) return insert(t, d);
672 for (const auto& e : s.entries())
673 t = insert(t, e.d);
674 return t;
675 }
676
677 Set erase_all(Set t, Set s) {
678 if (auto d = s.isa_uniq()) return erase(t, d);
679 for (const auto& e : s.entries())
680 t = erase(t, e.d);
681 return t;
682 }
683
684 /// The elements of @p s that @p other holds as well - or exactly those it does not, for `!in`.
685 Set filter(Set s, Set other, bool in) {
686 if (auto d = s.isa_uniq()) return other.contains(d) == in ? s : Set();
687
688 auto buf = std::array<Entry, N>();
689 auto o = buf.begin();
690 for (const auto& e : s.entries())
691 if (bool(other.lookup(e.key)) == in) *o++ = e;
692 return make(View<Entry>(buf.data(), size_t(o - buf.begin())));
693 }
694 ///@}
695
696 /// @name Node Construction
697 /// This is where the four flavours and the hash-consing live.
698 ///@{
699
700 /// Sorts @p v and drops the duplicate ids, then hands the result to Patricia::make.
701 Set build(Vector<Entry>& v) {
702 std::ranges::stable_sort(v, {}, &Entry::key);
703 auto rest = std::ranges::unique(v, {}, &Entry::key);
704 return make(View<Entry>(v.data(), size_t(rest.begin() - v.begin())));
705 }
706
707 /// The canonical Set for the sorted, duplicate-free @p es.
708 Set make(View<Entry> es) {
709 if (es.empty()) return {};
710 if (es.size() == 1) return Set(es.front().d);
711 if (es.size() <= N) return arr(es);
712
713 auto m = branching_bit(es.front().key, es.back().key);
714 auto p = mask_of(es.front().key, m);
715 auto mid
716 = size_t(std::ranges::partition_point(es, [m](const Entry& e) { return zero_bit(e.key, m); }) - es.begin());
717 return br(p, m, make(es.first(mid)), make(es.subspan(mid)));
718 }
719
720 /// Joins two Set%s whose id ranges are disjoint.
721 Set join(Set s1, Set s2) {
722 auto p1 = s1.prefix();
723 auto p2 = s2.prefix();
724 auto m = branching_bit(p1, p2);
725 auto p = mask_of(p1, m);
726 return zero_bit(p1, m) ? br(p, m, s1, s2) : br(p, m, s2, s1);
727 }
728
729 Set arr(View<Entry> es) {
730 assert(2 <= es.size() && es.size() <= N);
731 auto state = arr_arena_.state();
732 auto buff = arr_arena_.allocate(sizeof(Arr) + es.size() * sizeof(Entry), alignof(Arr));
733 auto node = new (buff) Arr(hash_entries(es), uint32_t(es.size()));
734 std::uninitialized_copy(es.begin(), es.end(), node->entries);
735 auto [i, ins] = arrs_.emplace(node);
736 if (!ins) arr_arena_.deallocate(state);
737 return Set(*i);
738 }
739
740 /// A branch - unless @p l and @p r together still fit into one array node.
741 Set br(K prefix, K mask, Set l, Set r) {
742 if (l.empty()) return r;
743 if (r.empty()) return l;
744
745 auto size = l.size() + r.size();
746 if (size <= N) { // neither child is a Br, and l's ids all precede r's: concatenate
747 auto buf = std::array<Entry, N>();
748 auto o = l.copy(buf.begin());
749 o = r.copy(o);
750 return arr(View<Entry>(buf.data(), size));
751 }
752
753 auto state = br_arena_.state();
754 auto node = new (br_arena_.allocate<Br>(1)) Br(prefix, mask, size, l.word_, r.word_);
755 auto [i, ins] = brs_.emplace(node);
756 if (!ins) br_arena_.deallocate(state);
757 return Set(*i);
758 }
759 ///@}
760
761 /// @name Hash-Consing
762 /// The id follows from the element, so only the pointers take part.
763 ///@{
764 static size_t hash_entries(View<Entry> es) noexcept {
765 auto h = hash_begin();
766 for (const auto& e : es)
767 h = hash_combine(h, std::bit_cast<uintptr_t>(e.d));
768 return h;
769 }
770
771 struct ArrHash {
772 size_t operator()(const Arr* n) const noexcept { return n->hash; }
773 };
774
775 struct ArrEq {
776 bool operator()(const Arr* n1, const Arr* n2) const noexcept {
777 if (n1->size != n2->size) return false;
778 for (uint32_t i = 0; i != n1->size; ++i)
779 if (n1->entries[i].d != n2->entries[i].d) return false;
780 return true;
781 }
782 };
783
784 struct BrHash {
785 size_t operator()(const Br* n) const noexcept {
786 auto h = hash_combine(hash_combine(hash_begin(), n->prefix), n->mask);
787 return hash_combine(hash_combine(h, n->l), n->r);
788 }
789 };
790
791 struct BrEq {
792 bool operator()(const Br* n1, const Br* n2) const noexcept {
793 // The children are canonical already, so comparing their words settles the whole subtree.
794 return n1->prefix == n2->prefix && n1->mask == n2->mask && n1->l == n2->l && n1->r == n2->r;
795 }
796 };
797
798#ifdef FE_ABSL
799 template<class T, class H, class E>
800 using Pool = absl::flat_hash_set<const T*, H, E>;
801#else
802 template<class T, class H, class E>
803 using Pool = std::unordered_set<const T*, H, E>;
804#endif
805 ///@}
806
807 // One Arena per node kind, so that rolling a speculative allocation back on a pool hit is always LIFO.
808 Arena arr_arena_;
809 Arena br_arena_;
810 Pool<Arr, ArrHash, ArrEq> arrs_;
811 Pool<Br, BrHash, BrEq> brs_;
812};
813
814} // namespace fe
static constexpr size_t Default_Page_Size
1MB.
Definition arena.h:27
Yields the D* in ascending Key::key order.
Definition patricia.h:188
iterator() noexcept=default
iterator & operator++() noexcept
Definition patricia.h:201
bool operator==(const iterator &other) const noexcept
Definition patricia.h:212
std::ptrdiff_t difference_type
Definition patricia.h:191
pointer operator->() const noexcept
Definition patricia.h:199
std::forward_iterator_tag iterator_category
Definition patricia.h:190
iterator operator++(int) noexcept
Definition patricia.h:206
reference operator*() const noexcept
Definition patricia.h:198
An immutable set - really just a tagged pointer, so copy it around freely.
Definition patricia.h:143
size_t size() const noexcept
Definition patricia.h:277
constexpr Set() noexcept=default
The empty set.
bool has_intersection(Set other) const noexcept
Is ?
Definition patricia.h:295
D * max() const noexcept
Largest id - or nullptr.
Definition patricia.h:287
D * min() const noexcept
Smallest id - or nullptr.
Definition patricia.h:286
iterator begin() const noexcept
Definition patricia.h:340
void dot(std::ostream &os) const
Definition patricia.h:382
bool subset_of(Set other) const noexcept
Is ?
Definition patricia.h:318
void dump() const
Definition patricia.h:380
friend std::ostream & operator<<(std::ostream &os, Set s)
Definition patricia.h:455
void for_each(F &&f) const
Like iterating, but without the iterator's path stack.
Definition patricia.h:345
constexpr bool empty() const noexcept
Definition patricia.h:283
std::ostream & stream(std::ostream &os) const
Definition patricia.h:366
iterator end() const noexcept
Definition patricia.h:341
bool contains(D *d) const noexcept
Definition patricia.h:292
constexpr bool operator==(Set other) const noexcept
Definition patricia.h:361
friend class Patricia
Definition patricia.h:454
Set diff(Set s1, Set s2)
Yields .
Definition patricia.h:608
Set insert(Set s, D *d)
Yields .
Definition patricia.h:492
friend void swap(Patricia &p1, Patricia &p2) noexcept
Definition patricia.h:630
Patricia(size_t page_size=Arena::Default_Page_Size)
Definition patricia.h:465
Patricia(Patricia &&other)
Definition patricia.h:469
Patricia & operator=(const Patricia &)=delete
Set create(std::initializer_list< D * > list)
Definition patricia.h:488
Set create(R &&r)
Creates a Set with all elements in r.
Definition patricia.h:481
Set intersect(Set s1, Set s2)
Yields .
Definition patricia.h:585
Patricia(const Patricia &)=delete
Set merge(Set s1, Set s2)
Yields .
Definition patricia.h:562
Set erase(Set s, D *d)
Yields .
Definition patricia.h:533
constexpr Vector(size_t size, F &&f)
Definition vector.h:49
Definition algo.h:17
auto lookup(C &container, const K &key)
Yields pointer to element (or the element itself if it is already a pointer), if found and nullptr ot...
Definition container.h:45
constexpr size_t hash(size_t h) noexcept
Mixes h with murmur3 or splitmix64 - whichever matches sizeof(size_t).
Definition hash.h:42
Vector(I, I, A=A()) -> Vector< typename std::iterator_traits< I >::value_type, Default_Inlined_Size< typename std::iterator_traits< I >::value_type >, A >
Span< const T, N > View
Read-only Span; use Span itself, if you want to write through its elements.
Definition span.h:107
constexpr size_t hash_begin() noexcept
Seeds a hash chain with the FNV-1 offset basis.
Definition hash.h:69
constexpr size_t hash_combine(size_t seed, T v) noexcept
Mixes v into seed word-wise, reusing the FNV-1 prime as multiplier.
Definition hash.h:73
void unreachable()
Definition assert.h:31
Definition span.h:150