Skip to main content

oxidd_manager_index/
manager.rs

1//! Index-Based Manager Implementation
2//!
3//! Abbreviations of generics:
4//!
5//! | Abbreviation | Meaning                           |
6//! | ------------ | --------------------------------- |
7//! | `N`          | Inner Node                        |
8//! | `ET`         | Edge Tag                          |
9//! | `TM`         | Terminal Manager                  |
10//! | `R`          | Diagram Rules                     |
11//! | `MD`         | Manager Data                      |
12//! | `NC`         | Inner Node Type Constructor       |
13//! | `TMC`        | Terminal Manager Type Constructor |
14//! | `RC`         | Diagram Rules Type Constructor    |
15//! | `OP`         | Operation                         |
16
17use std::cell::{Cell, UnsafeCell};
18use std::cmp::Ordering;
19use std::hash::{Hash, Hasher};
20use std::iter::FusedIterator;
21use std::marker::PhantomData;
22use std::mem::ManuallyDrop;
23use std::ops::Range;
24use std::sync::Arc;
25use std::sync::atomic::AtomicU64;
26use std::sync::atomic::Ordering::{Acquire, Relaxed};
27
28use crossbeam_utils::CachePadded;
29use derive_where::derive_where;
30use fixedbitset::FixedBitSet;
31use parking_lot::{Condvar, Mutex, MutexGuard};
32use rustc_hash::FxHasher;
33
34use oxidd_core::error::{DuplicateVarName, OutOfMemory};
35use oxidd_core::function::EdgeOfFunc;
36use oxidd_core::util::{AbortOnDrop, AllocResult, Borrowed, DropWith, VarNameMap};
37use oxidd_core::{DiagramRules, InnerNode, LevelNo, ManagerEventSubscriber, Tag, VarNo};
38
39use crate::node::NodeBase;
40use crate::terminal_manager::TerminalManager;
41use crate::util::{Invariant, TryLock, VarLevelMap, rwlock::RwLock};
42
43#[cfg(not(feature = "hugealloc"))]
44mod huge {
45    pub type Box<T> = std::boxed::Box<T>;
46    pub type Vec<T> = std::vec::Vec<T>;
47
48    pub type RawTable<T> = linear_hashtbl::raw::RawTable<T, u32>;
49    pub type RawTableIntoIter<T> = linear_hashtbl::raw::IntoIter<T, u32>;
50}
51#[cfg(feature = "hugealloc")]
52mod huge {
53    use hugealloc::HugeAlloc;
54    pub type Box<T> = allocator_api2::boxed::Box<T, HugeAlloc>;
55    pub type Vec<T> = allocator_api2::vec::Vec<T, HugeAlloc>;
56
57    pub type RawTable<T> = linear_hashtbl::raw::RawTable<T, u32, HugeAlloc>;
58    pub type RawTableIntoIter<T> = linear_hashtbl::raw::IntoIter<T, u32, HugeAlloc>;
59}
60
61// === Type Constructors =======================================================
62
63/// Inner node type constructor
64pub trait InnerNodeCons<ET: Tag> {
65    type T<'id>: NodeBase + InnerNode<Edge<'id, Self::T<'id>, ET>> + Send + Sync;
66}
67
68/// Terminal manager type constructor
69pub trait TerminalManagerCons<NC: InnerNodeCons<ET>, ET: Tag, const TERMINALS: usize>:
70    Sized
71{
72    type TerminalNode: Send + Sync;
73    type T<'id>: Send
74        + Sync
75        + TerminalManager<'id, NC::T<'id>, ET, TERMINALS, TerminalNode = Self::TerminalNode>;
76}
77
78/// Diagram rules type constructor
79pub trait DiagramRulesCons<
80    NC: InnerNodeCons<ET>,
81    ET: Tag,
82    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
83    MDC: ManagerDataCons<NC, ET, TMC, Self, TERMINALS>,
84    const TERMINALS: usize,
85>: Sized
86{
87    type T<'id>: Send
88        + Sync
89        + DiagramRules<
90            Edge<'id, NC::T<'id>, ET>,
91            NC::T<'id>,
92            <TMC::T<'id> as TerminalManager<'id, NC::T<'id>, ET, TERMINALS>>::TerminalNode,
93        >;
94}
95
96/// Manager data type constructor
97pub trait ManagerDataCons<
98    NC: InnerNodeCons<ET>,
99    ET: Tag,
100    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
101    RC: DiagramRulesCons<NC, ET, TMC, Self, TERMINALS>,
102    const TERMINALS: usize,
103>: Sized
104{
105    type T<'id>: Send
106        + Sync
107        + DropWith<Edge<'id, NC::T<'id>, ET>>
108        + ManagerEventSubscriber<
109            Manager<'id, NC::T<'id>, ET, TMC::T<'id>, RC::T<'id>, Self::T<'id>, TERMINALS>,
110        >;
111}
112
113// === Manager & Edges =========================================================
114
115/// "Signals" used to communicate with the garbage collection thread
116#[derive(Clone, Copy, PartialEq, Eq, Debug)]
117enum GCSignal {
118    RunGc,
119    Quit,
120}
121
122pub struct Store<'id, N, ET, TM, R, MD, const TERMINALS: usize>
123where
124    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
125    ET: Tag,
126    TM: TerminalManager<'id, N, ET, TERMINALS>,
127    MD: DropWith<Edge<'id, N, ET>>,
128{
129    inner_nodes: huge::Box<SlotSlice<'id, N, TERMINALS>>,
130    manager: RwLock<Manager<'id, N, ET, TM, R, MD, TERMINALS>>,
131    terminal_manager: TM,
132    state: CachePadded<Mutex<SharedStoreState>>,
133    gc_signal: (Mutex<GCSignal>, Condvar),
134    workers: crate::workers::Workers,
135}
136
137unsafe impl<'id, N, ET, TM, R, MD, const TERMINALS: usize> Sync
138    for Store<'id, N, ET, TM, R, MD, TERMINALS>
139where
140    N: NodeBase + InnerNode<Edge<'id, N, ET>> + Send + Sync,
141    ET: Tag + Send + Sync,
142    TM: TerminalManager<'id, N, ET, TERMINALS> + Send + Sync,
143    MD: DropWith<Edge<'id, N, ET>> + Send + Sync,
144{
145}
146
147/// Size of a pre-allocation (number of nodes)
148const CHUNK_SIZE: u32 = 64 * 1024;
149
150/// State of automatic background garbage collection
151#[derive(Clone, Copy, PartialEq, Eq, Debug)]
152enum GCState {
153    /// Automatic garbage collection is disabled entirely
154    Disabled,
155    /// Initial state
156    Init,
157    /// Garbage collection has been triggered because the state was `Init` and
158    /// the node count exceeded the high water mark
159    ///
160    /// The collecting thread sets this state back to `Init` if the node count
161    /// reaches a value less than the low water mark.
162    Triggered,
163}
164
165struct SharedStoreState {
166    /// IDs of the next free slots
167    ///
168    /// Each element of the vector corresponds to a linked list of free slots.
169    /// If a worker runs out of locally allocated free slots, it will first try
170    /// to take a list of free slots from here. If this is not possible, a new
171    /// chunk of uninitialized slots will be allocated. New entries to this
172    /// vector are added during garbage collection. Each list should not be
173    /// longer than `CHUNK_SIZE` (this is not a strict requirement, though).
174    next_free: Vec<u32>,
175
176    /// All slots in range `..allocated` are either initialized (a `node` or a
177    /// `next_free` ID), or `uninit` and pre-allocated by a worker.
178    ///
179    /// Note that this is an index into the slice, so no `- TERMINALS` is needed
180    /// to access the slot.
181    allocated: u32,
182
183    /// Eventually consistent count of inner nodes
184    ///
185    /// This is an `i64` and not an `u32` because of the following scenario:
186    /// Worker A creates n nodes such that its `node_count_delta` becomes n - 1
187    /// and the shared `node_count` is 1. At least two of the newly created
188    /// nodes are solely referenced by a [`Function`]. An application thread
189    /// drops these two functions. This will directly decrement the shared
190    /// `node_count` by 1 twice. If we used a `u32`, this would lead to an
191    /// overflow.
192    node_count: i64,
193
194    /// Background garbage collection state (see [`GCState`])
195    gc_state: GCState,
196    /// Low water mark for background garbage collection (see [`GCState`])
197    gc_lwm: u32,
198    /// High water mark for background garbage collection (see [`GCState`])
199    gc_hwm: u32,
200}
201
202#[repr(align(64))] // all fields on a single cache line
203struct LocalStoreState {
204    /// ID of the next free slot
205    ///
206    /// `0` means that there is no such slot so far. Either `initialized` can be
207    /// incremented or we are out of memory. Since `0` is always a terminal (we
208    /// require `TERMINALS >= 1`), there is no ambiguity.
209    next_free: Cell<u32>,
210
211    /// All slots in range `..initialized` are not `uninit`, i.e., either a
212    /// `node` or a `next_free` ID. All slots until the next multiple of
213    /// `CHUNK_SIZE` can be used by the local thread. So if
214    /// `initialized % CHUNK_SIZE == 0` the worker needs to allocate a new chunk
215    /// from the shared store state.
216    ///
217    /// Note that this is an index into the slice, so no `- TERMINALS` is needed
218    /// to access the slot.
219    initialized: Cell<u32>,
220
221    /// Address of the associated `Store`
222    ///
223    /// Before using slots from this `LocalStoreState`, the implementation needs
224    /// to ensure that the slots come from the respective `Store`.
225    current_store: Cell<usize>,
226
227    /// Local changes to the shared node counter
228    ///
229    /// In general, we synchronize with the shared counter if request slots from
230    /// the shared manager or we return the `next_free` list.  The latter
231    /// happens if this counter reaches `-CHUNK_SIZE`.
232    node_count_delta: Cell<i32>,
233}
234
235thread_local! {
236    static LOCAL_STORE_STATE: LocalStoreState = const {
237        LocalStoreState {
238            next_free: Cell::new(0),
239            initialized: Cell::new(0),
240            current_store: Cell::new(0),
241            node_count_delta: Cell::new(0),
242        }
243    };
244}
245
246struct LocalStoreStateGuard<'a, 'id, N, ET, TM, R, MD, const TERMINALS: usize>(
247    &'a Store<'id, N, ET, TM, R, MD, TERMINALS>,
248)
249where
250    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
251    ET: Tag,
252    TM: TerminalManager<'id, N, ET, TERMINALS>,
253    MD: DropWith<Edge<'id, N, ET>>;
254
255/// Node slot. We distinguish three states, corresponding to the variants:
256/// "containing a node," "free," and "uninitialized."
257union Slot<N> {
258    node: ManuallyDrop<N>,
259    /// ID of the next free slot or `0` if there is none. Subtract `TERMINALS`
260    /// to get the `SlotSlice` index.
261    next_free: u32,
262    #[allow(unused)]
263    uninit: (),
264}
265
266#[repr(transparent)]
267struct SlotSlice<'id, N, const TERMINALS: usize> {
268    phantom: PhantomData<Invariant<'id>>,
269    slots: [UnsafeCell<Slot<N>>],
270}
271
272/// Edge type, see [`oxidd_core::Edge`]
273///
274/// Internally, this is represented as a `u32` index.
275#[repr(transparent)]
276#[must_use]
277#[derive_where(PartialEq, Eq, PartialOrd, Ord, Hash)]
278pub struct Edge<'id, N, ET>(
279    /// `node_index | edge_tag << (u32::BITS - Self::TAG_BITS)`
280    u32,
281    #[derive_where(skip)] PhantomData<(Invariant<'id>, N, ET)>,
282);
283
284#[repr(C)]
285pub struct Manager<'id, N, ET, TM, R, MD, const TERMINALS: usize>
286where
287    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
288    ET: Tag,
289    TM: TerminalManager<'id, N, ET, TERMINALS>,
290    MD: DropWith<Edge<'id, N, ET>>,
291{
292    unique_table: Vec<Mutex<LevelViewSet<'id, N, ET, TM, R, MD, TERMINALS>>>,
293    var_level_map: VarLevelMap,
294    var_name_map: VarNameMap,
295    data: ManuallyDrop<MD>,
296    /// Pointer back to the store, obtained via [`Arc::as_ptr()`].
297    ///
298    /// Theoretically, we should be able to get the pointer from a `&Manager`
299    /// reference, but this leads to provenance issues.
300    store: *const Store<'id, N, ET, TM, R, MD, TERMINALS>,
301    gc_count: AtomicU64,
302    reorder_count: u64,
303    gc_ongoing: TryLock,
304    reorder_gc_prepared: bool,
305}
306
307/// Type "constructor" for the manager from `InnerNodeCons` etc.
308type M<'id, NC, ET, TMC, RC, MDC, const TERMINALS: usize> = Manager<
309    'id,
310    <NC as InnerNodeCons<ET>>::T<'id>,
311    ET,
312    <TMC as TerminalManagerCons<NC, ET, TERMINALS>>::T<'id>,
313    <RC as DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>>::T<'id>,
314    <MDC as ManagerDataCons<NC, ET, TMC, RC, TERMINALS>>::T<'id>,
315    TERMINALS,
316>;
317
318unsafe impl<
319    'id,
320    N: NodeBase + InnerNode<Edge<'id, N, ET>> + Send + Sync,
321    ET: Tag + Send + Sync,
322    TM: TerminalManager<'id, N, ET, TERMINALS> + Send + Sync,
323    R,
324    MD: DropWith<Edge<'id, N, ET>> + Send + Sync,
325    const TERMINALS: usize,
326> Send for Manager<'id, N, ET, TM, R, MD, TERMINALS>
327{
328}
329unsafe impl<
330    'id,
331    N: NodeBase + InnerNode<Edge<'id, N, ET>> + Send + Sync,
332    ET: Tag + Send + Sync,
333    TM: TerminalManager<'id, N, ET, TERMINALS> + Send + Sync,
334    R,
335    MD: DropWith<Edge<'id, N, ET>> + Send + Sync,
336    const TERMINALS: usize,
337> Sync for Manager<'id, N, ET, TM, R, MD, TERMINALS>
338{
339}
340
341// --- Edge Impls --------------------------------------------------------------
342
343impl<N: NodeBase, ET: Tag> Edge<'_, N, ET> {
344    const TAG_BITS: u32 = {
345        let bits = usize::BITS - ET::MAX_VALUE.leading_zeros();
346        assert!(bits <= 16, "Maximum value of edge tag is too large");
347        bits
348    };
349    /// Shift amount to store/retrieve the tag from the most significant bits
350    const TAG_SHIFT: u32 = (u32::BITS - Self::TAG_BITS) % 32;
351
352    /// Mask corresponding to [`Self::TAG_BITS`]
353    const TAG_MASK: u32 = ((1 << Self::TAG_BITS) - 1) << Self::TAG_SHIFT;
354
355    /// SAFETY: The edge must be untagged.
356    #[inline(always)]
357    unsafe fn node_id_unchecked(&self) -> u32 {
358        debug_assert_eq!(self.0 & Self::TAG_MASK, 0);
359        self.0
360    }
361
362    /// Get the node ID, i.e., the edge value without any tags
363    #[inline(always)]
364    fn node_id(&self) -> usize {
365        (self.0 & !Self::TAG_MASK) as usize
366    }
367
368    /// Returns `true` iff the edge's tag != 0
369    #[inline(always)]
370    fn is_tagged(&self) -> bool {
371        self.0 & Self::TAG_MASK != 0
372    }
373
374    /// Get the raw representation of the edge (also called "edge value")
375    #[inline(always)]
376    pub fn raw(&self) -> u32 {
377        self.0
378    }
379
380    /// Get an edge from a terminal ID
381    ///
382    /// # Safety
383    ///
384    /// `id` must be a terminal ID, i.e., `id < TERMINALS`, and the caller must
385    /// update the reference count for the terminal accordingly.
386    #[inline(always)]
387    pub unsafe fn from_terminal_id(id: u32) -> Self {
388        Self(id, PhantomData)
389    }
390}
391
392impl<N: NodeBase, ET: Tag> oxidd_core::Edge for Edge<'_, N, ET> {
393    type Tag = ET;
394
395    #[inline]
396    fn borrowed(&self) -> Borrowed<'_, Self> {
397        Borrowed::new(Self(self.0, PhantomData))
398    }
399
400    #[inline]
401    fn with_tag(&self, tag: Self::Tag) -> Borrowed<'_, Self> {
402        Borrowed::new(Self(
403            (self.0 & !Self::TAG_MASK) | ((tag.as_usize() as u32) << Self::TAG_SHIFT),
404            PhantomData,
405        ))
406    }
407
408    #[inline]
409    fn with_tag_owned(mut self, tag: Self::Tag) -> Self {
410        self.0 = (self.0 & !Self::TAG_MASK) | ((tag.as_usize() as u32) << Self::TAG_SHIFT);
411        self
412    }
413
414    #[inline]
415    fn tag(&self) -> Self::Tag {
416        ET::from_usize(((self.0 & Self::TAG_MASK) >> Self::TAG_SHIFT) as usize)
417    }
418
419    #[inline]
420    fn node_id(&self) -> oxidd_core::NodeID {
421        (self.0 & !Self::TAG_MASK) as oxidd_core::NodeID
422    }
423}
424
425impl<N, ET> Drop for Edge<'_, N, ET> {
426    #[inline(never)]
427    #[cold]
428    fn drop(&mut self) {
429        eprintln!(
430            "`Edge`s must not be dropped. Use `Manager::drop_edge()`. Backtrace:\n{}",
431            std::backtrace::Backtrace::capture()
432        );
433
434        #[cfg(feature = "static_leak_check")]
435        {
436            extern "C" {
437                #[link_name = "\n\n`Edge`s must not be dropped. Use `Manager::drop_edge()`.\n"]
438                fn trigger() -> !;
439            }
440            unsafe { trigger() }
441        }
442    }
443}
444
445// --- SlotSlice Impl ----------------------------------------------------------
446
447impl<'id, N: NodeBase, const TERMINALS: usize> SlotSlice<'id, N, TERMINALS> {
448    // Create a new slot slice for up to `capacity` nodes
449    fn new_boxed(capacity: u32) -> huge::Box<Self> {
450        #[cfg(not(feature = "hugealloc"))]
451        let mut vec: huge::Vec<UnsafeCell<Slot<N>>> = huge::Vec::with_capacity(capacity as usize);
452        #[cfg(feature = "hugealloc")]
453        let mut vec: huge::Vec<UnsafeCell<Slot<N>>> =
454            huge::Vec::with_capacity_in(capacity as usize, hugealloc::HugeAlloc);
455
456        // SAFETY: The new length is equal to the capacity. All elements are
457        // "initialized" as `Slot::uninit`.
458        //
459        // Clippy's `uninit_vec` lint is a bit too strict here. `Slot`s are
460        // somewhat like `MaybeUninit`, but Clippy wants `MaybeUninit`.
461        #[allow(clippy::uninit_vec)]
462        unsafe {
463            vec.set_len(capacity as usize)
464        };
465
466        let boxed = vec.into_boxed_slice();
467        // SAFETY: `SlotSlice` has `repr(transparent)` and thus the same
468        // representation as `[UnsafeCell<Slot<N>>]`.
469        unsafe { std::mem::transmute(boxed) }
470    }
471
472    /// SAFETY: `edge` must be untagged and reference an inner node
473    #[inline]
474    unsafe fn slot_pointer_unchecked<ET: Tag>(&self, edge: &Edge<'id, N, ET>) -> *mut Slot<N> {
475        // SAFETY: `edge` is untagged
476        let id = unsafe { edge.node_id_unchecked() } as usize;
477        debug_assert!(id >= TERMINALS, "`edge` must reference an inner node");
478        debug_assert!(id - TERMINALS < self.slots.len());
479        // SAFETY: Indices derived from edges pointing to inner nodes are always
480        // in bounds.
481        unsafe { self.slots.get_unchecked(id - TERMINALS).get() }
482    }
483
484    /// Panics if `edge` references a terminal node
485    #[inline]
486    fn inner_node<ET: Tag>(&self, edge: &Edge<'id, N, ET>) -> &N {
487        let id = edge.node_id();
488        assert!(id >= TERMINALS, "`edge` must reference an inner node");
489        debug_assert!(id - TERMINALS < self.slots.len());
490        // SAFETY:
491        // - Indices derived from edges pointing to inner nodes are in bounds
492        // - If an edge points to an inner node, the node's `Slot` is immutable and a
493        //   properly initialized node
494        unsafe { &(*self.slots.get_unchecked(id - TERMINALS).get()).node }
495    }
496
497    /// SAFETY: `edge` must be untagged and reference an inner node
498    #[inline(always)]
499    unsafe fn inner_node_unchecked<ET: Tag>(&self, edge: &Edge<'id, N, ET>) -> &N {
500        // SAFETY: If an edge points to an inner node, the node's `Slot` is
501        // immutable and a properly initialized node.
502        unsafe { &(*self.slot_pointer_unchecked(edge)).node }
503    }
504
505    /// SAFETY: `edge` must be untagged and reference an inner node
506    #[inline(always)]
507    unsafe fn clone_edge_unchecked<ET: Tag>(&self, edge: &Edge<'id, N, ET>) -> Edge<'id, N, ET> {
508        unsafe { self.inner_node_unchecked(edge) }.retain();
509        Edge(edge.0, PhantomData)
510    }
511}
512
513// --- Store Impls -------------------------------------------------------------
514
515/// Shorthand for `std::ptr::from_ref(reference).addr()`
516#[inline(always)]
517fn addr<T>(ptr: *const T) -> usize {
518    ptr.addr()
519}
520
521impl<'id, N, ET, TM, R, MD, const TERMINALS: usize> Store<'id, N, ET, TM, R, MD, TERMINALS>
522where
523    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
524    ET: Tag,
525    TM: TerminalManager<'id, N, ET, TERMINALS>,
526    MD: DropWith<Edge<'id, N, ET>>,
527{
528    const CHECK_TERMINALS: () = {
529        assert!(TERMINALS >= 1, "TERMINALS must be >= 1");
530        assert!(
531            TERMINALS <= u32::MAX as usize,
532            "TERMINALS must fit into an u32"
533        );
534    };
535
536    #[inline]
537    fn prepare_local_state(
538        &self,
539    ) -> Option<LocalStoreStateGuard<'_, 'id, N, ET, TM, R, MD, TERMINALS>> {
540        LOCAL_STORE_STATE.with(|state| {
541            if state.current_store.get() == 0 {
542                state.next_free.set(0);
543                state.initialized.set(0);
544                state.current_store.set(addr(self));
545                debug_assert_eq!(state.node_count_delta.get(), 0);
546                Some(LocalStoreStateGuard(self))
547            } else {
548                None
549            }
550        })
551    }
552
553    /// Add a node to the store. Does not perform any duplicate checks.
554    ///
555    /// Panics if the store is full.
556    #[inline]
557    fn add_node(&self, node: N) -> AllocResult<[Edge<'id, N, ET>; 2]> {
558        debug_assert_eq!(node.load_rc(Relaxed), 2);
559        let res = LOCAL_STORE_STATE.with(|state| {
560            let node_count_delta = if state.current_store.get() == addr(self) {
561                let delta = state.node_count_delta.get() + 1;
562                let id = state.next_free.get();
563                if id != 0 {
564                    // SAFETY: `id` is the ID of a free slot, we have exclusive access
565                    let (next_free, slot) = unsafe { self.use_free_slot(id) };
566                    state.next_free.set(next_free);
567                    state.node_count_delta.set(delta);
568                    return Ok((id, slot));
569                }
570
571                let index = state.initialized.get();
572                if index % CHUNK_SIZE != 0 {
573                    let slots = &self.inner_nodes.slots;
574                    debug_assert!((index as usize) < slots.len());
575                    // SAFETY: All slots in range
576                    // `index..index.next_multiple_of(CHUNK_SIZE)` are
577                    // uninitialized and pre-allocated, so we have exclusive
578                    // access to them.
579                    let slot = unsafe { &mut *slots.get_unchecked(index as usize).get() };
580                    state.initialized.set(index + 1);
581                    state.node_count_delta.set(delta);
582                    return Ok((index + TERMINALS as u32, slot));
583                }
584
585                state.node_count_delta.set(0);
586                delta
587            } else {
588                1
589            };
590
591            self.get_slot_from_shared(state, node_count_delta)
592        });
593        match res {
594            Ok((id, slot)) => {
595                slot.node = ManuallyDrop::new(node);
596                Ok([Edge(id, PhantomData), Edge(id, PhantomData)])
597            }
598            Err(OutOfMemory) => {
599                node.drop_with(|e| self.drop_edge(e));
600                Err(OutOfMemory)
601            }
602        }
603    }
604
605    /// Get the free slot with ID `id` and load the `next_free` ID
606    ///
607    /// SAFETY: `id` must be the ID of a free slot and the current thread must
608    /// have exclusive access to it.
609    #[inline(always)]
610    #[allow(clippy::mut_from_ref)]
611    unsafe fn use_free_slot(&self, id: u32) -> (u32, &mut Slot<N>) {
612        debug_assert!(id as usize >= TERMINALS);
613        let index = id as usize - TERMINALS;
614        debug_assert!(index < self.inner_nodes.slots.len());
615        // SAFETY: All next-free ids (>= TERMINALS) are valid, we have
616        // exclusive access to the node.
617        let slot = unsafe { &mut *self.inner_nodes.slots.get_unchecked(index).get() };
618        // SAFETY: The slot is free.
619        let next_free = unsafe { slot.next_free };
620        (next_free, slot)
621    }
622
623    /// Get a slot from the shared state
624    ///
625    /// `delta` is added to the shared node count.
626    ///
627    /// Unless out of memory, this method returns the ID of a free or
628    /// uninitialized slot as well as a reference pointing to that slot.
629    #[cold]
630    #[allow(clippy::mut_from_ref)]
631    fn get_slot_from_shared(
632        &self,
633        local: &LocalStoreState,
634        delta: i32,
635    ) -> AllocResult<(u32, &mut Slot<N>)> {
636        let mut shared = self.state.lock();
637
638        shared.node_count += delta as i64;
639        if shared.gc_state == GCState::Init && shared.node_count >= shared.gc_hwm as i64 {
640            shared.gc_state = GCState::Triggered;
641            self.gc_signal.1.notify_one();
642        }
643
644        if local.current_store.get() == addr(self) {
645            debug_assert_eq!(local.next_free.get(), 0);
646            debug_assert_eq!(local.initialized.get() % CHUNK_SIZE, 0);
647
648            if let Some(id) = shared.next_free.pop() {
649                // SAFETY: `id` is the ID of a free slot, we have exclusive access
650                let (next_free, slot) = unsafe { self.use_free_slot(id) };
651                local.next_free.set(next_free);
652                return Ok((id, slot));
653            }
654
655            let index = shared.allocated;
656            let slots = &self.inner_nodes.slots;
657            if (index as usize + CHUNK_SIZE as usize) < slots.len() {
658                shared.allocated = (index / CHUNK_SIZE + 1) * CHUNK_SIZE;
659                local.initialized.set(index + 1);
660            } else if (index as usize) < slots.len() {
661                shared.allocated += 1;
662            } else {
663                return Err(OutOfMemory);
664            }
665            // SAFETY: `index` is in bounds, the slot is uninitialized
666            let slot = unsafe { &mut *slots.get_unchecked(index as usize).get() };
667            Ok((index + TERMINALS as u32, slot))
668        } else {
669            if let Some(id) = shared.next_free.pop() {
670                // SAFETY: `id` is the ID of a free slot, we have exclusive access
671                let (next_free, slot) = unsafe { self.use_free_slot(id) };
672                if next_free != 0 {
673                    shared.next_free.push(next_free);
674                }
675                return Ok((id, slot));
676            }
677
678            let index = shared.allocated;
679            let slots = &self.inner_nodes.slots;
680            if (index as usize) >= slots.len() {
681                return Err(OutOfMemory);
682            }
683            shared.allocated += 1;
684            // SAFETY: `index` is in bounds, the slot is uninitialized
685            let slot = unsafe { &mut *slots.get_unchecked(index as usize).get() };
686            Ok((index + TERMINALS as u32, slot))
687        }
688    }
689
690    /// Drop an edge that originates from the unique table.
691    ///
692    /// Edges from the unique table are untagged and point to inner nodes, so
693    /// we need less case distinctions. Furthermore, we assume that the node's
694    /// children are still present in the unique table (waiting for their
695    /// revival or to be garbage collected), so we just decrement the children's
696    /// reference counters. There is a debug assertion that checks this
697    /// assumption.
698    ///
699    /// SAFETY: `edge` must be untagged and point to an inner node
700    unsafe fn drop_unique_table_edge(&self, edge: Edge<'id, N, ET>) {
701        // SAFETY (next 2): `edge` is untagged and points to an inner node
702        let slot_ptr = unsafe { self.inner_nodes.slot_pointer_unchecked(&edge) };
703        let id = unsafe { edge.node_id_unchecked() };
704        std::mem::forget(edge);
705
706        // SAFETY:
707        // - `edge` points to an inner node
708        // - We have shared access to nodes
709        // - `edge` is forgotten.
710        if unsafe { (*slot_ptr).node.release() } != 1 {
711            return;
712        }
713
714        // We only have exclusive access to the other fields of node after the
715        // fence. It synchronizes with the `NodeBase::release()` above (which is
716        // guaranteed to have `Release` order).
717        std::sync::atomic::fence(Acquire);
718
719        // SAFETY: Now, we have exclusive access to the slot. It contains a
720        // node. `id` is the ID of the slot.
721        unsafe { self.free_slot(&mut *slot_ptr, id) };
722    }
723
724    /// Free `slot`, i.e., drop the node and add its ID (`id`) to the free list
725    ///
726    /// SAFETY: `slot` must contain a node. `id` is the ID of `slot`.
727    #[inline]
728    unsafe fn free_slot(&self, slot: &mut Slot<N>, id: u32) {
729        debug_assert!(id as usize >= TERMINALS);
730        // SAFETY: We don't use the node in `slot` again.
731        unsafe { ManuallyDrop::take(&mut slot.node) }.drop_with(|edge| self.drop_edge(edge));
732
733        LOCAL_STORE_STATE.with(|state| {
734            if state.current_store.get() == addr(self) {
735                slot.next_free = state.next_free.get();
736                state.next_free.set(id);
737
738                let delta = state.node_count_delta.get() - 1;
739                if delta > -(CHUNK_SIZE as i32) {
740                    state.node_count_delta.set(delta);
741                } else {
742                    let mut shared = self.state.lock();
743                    shared.next_free.push(state.next_free.replace(0));
744                    shared.node_count += state.node_count_delta.replace(0) as i64;
745                }
746            } else {
747                #[cold]
748                fn return_slot<N>(shared: &Mutex<SharedStoreState>, slot: &mut Slot<N>, id: u32) {
749                    let mut shared = shared.lock();
750                    slot.next_free = shared.next_free.pop().unwrap_or(0);
751                    shared.next_free.push(id);
752                    shared.node_count -= 1;
753                }
754                return_slot(&self.state, slot, id);
755            }
756        });
757    }
758
759    /// Drop an edge, assuming that it isn't the last one pointing to the
760    /// referenced node
761    ///
762    /// This assumption is fulfilled in case the node is still stored in the
763    /// unique table.
764    ///
765    /// There is a debug assertion that checks the aforementioned assumption. In
766    /// release builds, this function will simply leak the node.
767    #[inline]
768    fn drop_edge(&self, edge: Edge<'id, N, ET>) {
769        let id = edge.node_id();
770        if id >= TERMINALS {
771            // inner node
772            let node = self.inner_nodes.inner_node(&edge);
773            std::mem::forget(edge);
774            // SAFETY: `edge` is forgotten
775            let _old_rc = unsafe { node.release() };
776            debug_assert!(_old_rc > 1);
777        } else {
778            std::mem::forget(edge);
779            // SAFETY: `id` is a valid terminal ID and `edge` is forgotten
780            unsafe { self.terminal_manager.release(id) };
781        }
782    }
783
784    /// Clone `edge`
785    ///
786    /// `edge` may be tagged and point to an inner or a terminal node.
787    #[inline]
788    fn clone_edge(&self, edge: &Edge<'id, N, ET>) -> Edge<'id, N, ET> {
789        let id = edge.node_id();
790        if id >= TERMINALS {
791            // inner node
792            self.inner_nodes.inner_node(edge).retain();
793        } else {
794            // SAFETY: `id` is a valid terminal ID
795            unsafe { self.terminal_manager.retain(id) };
796        }
797        Edge(edge.0, PhantomData)
798    }
799}
800
801impl<'id, N, ET, TM, R, MD, const TERMINALS: usize> Drop for Store<'id, N, ET, TM, R, MD, TERMINALS>
802where
803    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
804    ET: Tag,
805    TM: TerminalManager<'id, N, ET, TERMINALS>,
806    MD: DropWith<Edge<'id, N, ET>>,
807{
808    fn drop(&mut self) {
809        let manager = self.manager.get_mut();
810        // We don't care about reference counters from here on.
811        // SAFETY: We don't use `manager.data` again.
812        unsafe { ManuallyDrop::take(&mut manager.data) }.drop_with(std::mem::forget);
813
814        if N::needs_drop() {
815            let unique_table = std::mem::take(&mut manager.unique_table);
816            for level in unique_table {
817                for edge in level.into_inner() {
818                    // SAFETY (next 2): `edge` is untagged and points to an
819                    // inner node
820                    let slot_ptr = unsafe { self.inner_nodes.slot_pointer_unchecked(&edge) };
821                    std::mem::forget(edge);
822                    // SAFETY: We have exclusive access to all nodes in the
823                    // store. `edge` points to an inner node.
824                    let node = unsafe { ManuallyDrop::take(&mut (*slot_ptr).node) };
825
826                    // We don't care about reference counts anymore.
827                    node.drop_with(std::mem::forget);
828                }
829            }
830        }
831    }
832}
833
834// --- LocalStoreStateGuard impl -----------------------------------------------
835
836impl<'id, N, ET, TM, R, MD, const TERMINALS: usize> Drop
837    for LocalStoreStateGuard<'_, 'id, N, ET, TM, R, MD, TERMINALS>
838where
839    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
840    ET: Tag,
841    TM: TerminalManager<'id, N, ET, TERMINALS>,
842    MD: DropWith<Edge<'id, N, ET>>,
843{
844    #[inline]
845    fn drop(&mut self) {
846        #[cold]
847        fn drop_slow<N>(
848            slots: &[UnsafeCell<Slot<N>>],
849            shared_state: &Mutex<SharedStoreState>,
850            terminals: u32,
851        ) {
852            LOCAL_STORE_STATE.with(|local| {
853                local.current_store.set(0);
854                let start = local.initialized.get();
855                let next_free = if start % CHUNK_SIZE != 0 {
856                    // We cannot simply give an uninitialized chunk back. Hence,
857                    // we prepend the slots to the free list.
858                    debug_assert!(start <= u32::MAX - CHUNK_SIZE);
859                    let end = (start / CHUNK_SIZE + 1) * CHUNK_SIZE;
860                    let last_slot = &slots[(end - 1) as usize];
861                    // SAFETY (next 2): We have exclusive access to the (uninitialized) slots in the
862                    // range from `local.initialized` up to the next multiple of `CHUNK_SIZE`.
863                    unsafe { &mut *last_slot.get() }.next_free = local.next_free.get();
864                    for (slot, next_id) in slots[start as usize..(end - 1) as usize]
865                        .iter()
866                        .zip(start + terminals + 1..)
867                    {
868                        unsafe { &mut *slot.get() }.next_free = next_id;
869                    }
870                    start + terminals
871                } else {
872                    local.next_free.get()
873                };
874
875                let mut shared = shared_state.lock();
876                if next_free != 0 {
877                    shared.next_free.push(next_free);
878                }
879                shared.node_count += local.node_count_delta.replace(0) as i64;
880            });
881        }
882
883        LOCAL_STORE_STATE.with(|local| {
884            if addr(self.0) == local.current_store.get()
885                && (local.next_free.get() != 0
886                    || local.initialized.get() % CHUNK_SIZE != 0
887                    || local.node_count_delta.get() != 0)
888            {
889                drop_slow(&self.0.inner_nodes.slots, &self.0.state, TERMINALS as u32);
890            }
891        });
892    }
893}
894
895// --- Manager Impls -----------------------------------------------------------
896
897impl<'id, N, ET, TM, R, MD, const TERMINALS: usize> Manager<'id, N, ET, TM, R, MD, TERMINALS>
898where
899    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
900    ET: Tag,
901    TM: TerminalManager<'id, N, ET, TERMINALS>,
902    R: oxidd_core::DiagramRules<Edge<'id, N, ET>, N, TM::TerminalNode>,
903    MD: DropWith<Edge<'id, N, ET>> + ManagerEventSubscriber<Self>,
904{
905    /// Get a reference to the store
906    #[inline]
907    fn store(&self) -> &Store<'id, N, ET, TM, R, MD, TERMINALS> {
908        // We can simply get the store pointer by subtracting the offset of
909        // `Manager` in `Store`. The only issue is that this violates Rust's
910        // (proposed) aliasing rules. Hence, we only provide a hint that the
911        // store's address can be computed without loading the value.
912        let offset = const {
913            std::mem::offset_of!(Store<'static, N, ET, TM, R, MD, TERMINALS>, manager)
914                + RwLock::<Self>::DATA_OFFSET
915        };
916        // SAFETY:
917        // - The pointer resulting from the subtraction is in bounds of the `Store`
918        //   allocation.
919        // - The pointers are equal after initialization of `self.store`.
920        unsafe {
921            std::hint::assert_unchecked(std::ptr::eq(
922                std::ptr::from_ref(self).cast::<u8>().sub(offset).cast(),
923                self.store,
924            ))
925        };
926
927        // SAFETY: After initialization, `self.store` always points to the
928        // containing `Store`.
929        unsafe { &*self.store }
930    }
931
932    /// Initialize the manager data
933    fn init(&mut self) {
934        self.data.init(self);
935        MD::init_mut(self);
936    }
937}
938
939unsafe impl<'id, N, ET, TM, R, MD, const TERMINALS: usize> oxidd_core::Manager
940    for Manager<'id, N, ET, TM, R, MD, TERMINALS>
941where
942    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
943    ET: Tag,
944    TM: TerminalManager<'id, N, ET, TERMINALS>,
945    R: oxidd_core::DiagramRules<Edge<'id, N, ET>, N, TM::TerminalNode>,
946    MD: DropWith<Edge<'id, N, ET>> + ManagerEventSubscriber<Self>,
947{
948    type Edge = Edge<'id, N, ET>;
949    type EdgeTag = ET;
950    type InnerNode = N;
951    type Terminal = TM::TerminalNode;
952    type TerminalRef<'a>
953        = TM::TerminalNodeRef<'a>
954    where
955        Self: 'a;
956    type Rules = R;
957
958    type TerminalIterator<'a>
959        = TM::Iterator<'a>
960    where
961        Self: 'a;
962    type NodeSet = NodeSet;
963    type LevelView<'a>
964        = LevelView<'a, 'id, N, ET, TM, R, MD, TERMINALS>
965    where
966        Self: 'a;
967    type LevelIterator<'a>
968        = LevelIter<'a, 'id, N, ET, TM, R, MD, TERMINALS>
969    where
970        Self: 'a;
971
972    #[inline]
973    fn get_node(&self, edge: &Self::Edge) -> oxidd_core::Node<'_, Self> {
974        let store = self.store();
975        let id = edge.node_id();
976        if id >= TERMINALS {
977            oxidd_core::Node::Inner(store.inner_nodes.inner_node(edge))
978        } else {
979            // SAFETY: `id` is a valid terminal ID
980            oxidd_core::Node::Terminal(unsafe { store.terminal_manager.get_terminal(id) })
981        }
982    }
983
984    #[inline]
985    fn clone_edge(&self, edge: &Self::Edge) -> Self::Edge {
986        self.store().clone_edge(edge)
987    }
988
989    #[inline]
990    fn drop_edge(&self, edge: Self::Edge) {
991        self.store().drop_edge(edge)
992    }
993
994    #[track_caller]
995    fn try_remove_node(&self, edge: Edge<'id, N, ET>, level: LevelNo) -> bool {
996        let id = edge.node_id();
997        let store = self.store();
998        if id < TERMINALS {
999            std::mem::forget(edge);
1000            // SAFETY: `id` is a valid terminal ID and `edge` is forgotten
1001            unsafe { store.terminal_manager.release(id) };
1002            debug_assert_eq!(level, LevelNo::MAX, "`level` does not match");
1003            return false;
1004        }
1005
1006        // inner node
1007        let node = store.inner_nodes.inner_node(&edge);
1008        std::mem::forget(edge);
1009        // SAFETY: `edge` is forgotten
1010        let old_rc = unsafe { node.release() };
1011
1012        debug_assert_ne!(level, LevelNo::MAX, "`level` does not match");
1013        debug_assert!(
1014            (level as usize) < self.unique_table.len(),
1015            "`level` out of range"
1016        );
1017        debug_assert!(node.check_level(|l| l == level), "`level` does not match");
1018        debug_assert!(old_rc > 1);
1019
1020        if old_rc != 2 || !self.reorder_gc_prepared {
1021            return false;
1022        }
1023
1024        let Some(set) = self.unique_table.get(level as usize) else {
1025            return false;
1026        };
1027        let mut set = set.lock();
1028
1029        // Read the reference count again: Another thread may have created an
1030        // edge between our `node.release()` call and `set.lock()`.
1031        let rc = node.load_rc(Acquire);
1032        debug_assert_ne!(rc, 0);
1033        if rc != 1 {
1034            return false;
1035        }
1036
1037        // SAFETY: we checked that `reorder_gc_prepared` is true above
1038        let Some(edge) = (unsafe { set.remove(&store.inner_nodes, node) }) else {
1039            return false;
1040        };
1041
1042        // SAFETY (next 2): `edge` is untagged and points to an inner node
1043        let slot_ptr = unsafe { store.inner_nodes.slot_pointer_unchecked(&edge) };
1044        let id = unsafe { edge.node_id_unchecked() };
1045        std::mem::forget(edge);
1046
1047        // SAFETY: Since `rc` is 1, this is the last reference. We use `Acquire`
1048        // order above and `Release` order when decrementing reference counters,
1049        // so we have exclusive node access now. The slot contains a node. `id`
1050        // is the ID of the slot.
1051        unsafe { store.free_slot(&mut *slot_ptr, id) };
1052
1053        true
1054    }
1055
1056    #[inline]
1057    fn num_inner_nodes(&self) -> usize {
1058        self.unique_table
1059            .iter()
1060            .map(|level| level.lock().len())
1061            .sum()
1062    }
1063
1064    #[inline]
1065    fn approx_num_inner_nodes(&self) -> usize {
1066        let count = self.store().state.lock().node_count;
1067        if count < 0 { 0 } else { count as u64 as usize }
1068    }
1069
1070    #[inline(always)]
1071    fn num_levels(&self) -> LevelNo {
1072        self.unique_table.len() as LevelNo
1073    }
1074
1075    #[inline(always)]
1076    fn num_named_vars(&self) -> VarNo {
1077        self.var_name_map.named_count() as VarNo
1078    }
1079
1080    #[track_caller]
1081    fn add_vars(&mut self, additional: VarNo) -> Range<VarNo> {
1082        let len = self.unique_table.len() as VarNo;
1083        let new_len = len.checked_add(additional).expect("too many variables");
1084        let range = len..new_len;
1085
1086        self.data.pre_reorder(self);
1087        MD::pre_reorder_mut(self);
1088
1089        self.unique_table
1090            .resize_with(new_len as usize, || Mutex::new(LevelViewSet::default()));
1091        self.var_level_map.extend(additional);
1092        self.var_name_map.add_unnamed(additional);
1093
1094        debug_assert_eq!(new_len as usize, self.unique_table.len());
1095        debug_assert_eq!(new_len as usize, self.var_level_map.len());
1096        debug_assert_eq!(new_len, self.var_name_map.len());
1097
1098        self.data.post_reorder(self);
1099        MD::post_reorder_mut(self);
1100
1101        range
1102    }
1103
1104    #[track_caller]
1105    fn add_named_vars<S: Into<String>>(
1106        &mut self,
1107        names: impl IntoIterator<Item = S>,
1108    ) -> Result<Range<VarNo>, DuplicateVarName> {
1109        self.data.pre_reorder(self);
1110        MD::pre_reorder_mut(self);
1111
1112        let len = self.var_name_map.len();
1113        let mut guard = scopeguard::guard(self, |this| {
1114            // This block is executed whenever `guard` gets dropped, i.e., even
1115            // if iterating over `names` or converting a value of type `S` into
1116            // `String` panics. This way, we ensure that the manager's state
1117            // remains consistent.
1118            let new_len = this.var_name_map.len();
1119            this.unique_table
1120                .resize_with(new_len as usize, || Mutex::new(LevelViewSet::default()));
1121            this.var_level_map.extend((new_len - len) as VarNo);
1122
1123            debug_assert_eq!(new_len as usize, this.unique_table.len());
1124            debug_assert_eq!(new_len as usize, this.var_level_map.len());
1125            debug_assert_eq!(new_len, this.var_name_map.len());
1126
1127            this.data.post_reorder(this);
1128            MD::post_reorder_mut(this);
1129        });
1130
1131        let mut names = names.into_iter();
1132        let range = guard.var_name_map.add_named(names.by_ref())?;
1133        drop(guard);
1134
1135        if names.next().is_some() {
1136            // important: panic only after dropping `guard`
1137            panic!("too many variables");
1138        }
1139
1140        Ok(range)
1141    }
1142
1143    #[track_caller]
1144    fn add_named_vars_from_map(
1145        &mut self,
1146        map: VarNameMap,
1147    ) -> Result<Range<VarNo>, DuplicateVarName> {
1148        if !self.var_name_map.is_empty() {
1149            return self.add_named_vars(map.into_names_iter());
1150        }
1151
1152        self.data.pre_reorder(self);
1153        MD::pre_reorder_mut(self);
1154
1155        let n = map.len();
1156        self.unique_table
1157            .resize_with(n as usize, || Mutex::new(LevelViewSet::default()));
1158        self.var_level_map.extend(n);
1159        self.var_name_map = map;
1160
1161        debug_assert_eq!(n as usize, self.unique_table.len());
1162        debug_assert_eq!(n as usize, self.var_level_map.len());
1163        debug_assert_eq!(n, self.var_name_map.len());
1164
1165        self.data.post_reorder(self);
1166        MD::post_reorder_mut(self);
1167
1168        Ok(0..n)
1169    }
1170
1171    #[track_caller]
1172    #[inline(always)]
1173    fn var_name(&self, var: VarNo) -> &str {
1174        self.var_name_map.var_name(var)
1175    }
1176
1177    #[track_caller]
1178    #[inline(always)]
1179    fn set_var_name(
1180        &mut self,
1181        var: VarNo,
1182        name: impl Into<String>,
1183    ) -> Result<(), DuplicateVarName> {
1184        self.var_name_map.set_var_name(var, name)
1185    }
1186
1187    #[inline(always)]
1188    fn name_to_var(&self, name: impl AsRef<str>) -> Option<VarNo> {
1189        self.var_name_map.name_to_var(name)
1190    }
1191
1192    #[track_caller]
1193    #[inline(always)]
1194    fn var_to_level(&self, var: VarNo) -> LevelNo {
1195        self.var_level_map.var_to_level(var)
1196    }
1197
1198    #[track_caller]
1199    #[inline(always)]
1200    fn level_to_var(&self, level: LevelNo) -> VarNo {
1201        self.var_level_map.level_to_var(level)
1202    }
1203
1204    #[track_caller]
1205    #[inline(always)]
1206    fn level(&self, no: LevelNo) -> Self::LevelView<'_> {
1207        LevelView {
1208            store: self.store(),
1209            var_level_map: &self.var_level_map,
1210            allow_node_removal: self.reorder_gc_prepared,
1211            level: no,
1212            set: self.unique_table[no as usize].lock(),
1213        }
1214    }
1215
1216    #[inline(always)]
1217    unsafe fn level_unchecked(&self, no: LevelNo) -> Self::LevelView<'_> {
1218        LevelView {
1219            store: self.store(),
1220            var_level_map: &self.var_level_map,
1221            allow_node_removal: self.reorder_gc_prepared,
1222            level: no,
1223            // SAFETY: ensured by caller
1224            set: unsafe { self.unique_table.get_unchecked(no as usize) }.lock(),
1225        }
1226    }
1227
1228    #[inline]
1229    fn levels(&self) -> Self::LevelIterator<'_> {
1230        LevelIter {
1231            store: self.store(),
1232            var_level_map: &self.var_level_map,
1233            allow_node_removal: self.reorder_gc_prepared,
1234            level_front: 0,
1235            level_back: self.unique_table.len() as LevelNo,
1236            it: self.unique_table.iter(),
1237        }
1238    }
1239
1240    #[inline]
1241    fn get_terminal(&self, terminal: Self::Terminal) -> AllocResult<Self::Edge> {
1242        self.store().terminal_manager.get_edge(terminal)
1243    }
1244
1245    #[inline]
1246    fn num_terminals(&self) -> usize {
1247        self.store().terminal_manager.len()
1248    }
1249
1250    #[inline]
1251    fn terminals(&self) -> Self::TerminalIterator<'_> {
1252        self.store().terminal_manager.iter()
1253    }
1254
1255    fn gc(&self) -> usize {
1256        if !self.gc_ongoing.try_lock() {
1257            // We don't want two concurrent garbage collections
1258            return 0;
1259        }
1260        self.gc_count.fetch_add(1, Relaxed);
1261        let guard = AbortOnDrop("Garbage collection panicked.");
1262
1263        #[cfg(feature = "statistics")]
1264        eprintln!(
1265            "[oxidd-manager-index] garbage collection started at {}",
1266            std::time::SystemTime::now()
1267                .duration_since(std::time::UNIX_EPOCH)
1268                .unwrap()
1269                .as_secs()
1270        );
1271
1272        if !self.reorder_gc_prepared {
1273            self.data.pre_gc(self);
1274        }
1275
1276        let store = self.store();
1277        let mut collected = 0;
1278        for level in &self.unique_table {
1279            let mut level = level.lock();
1280            collected += level.len() as u32;
1281            // SAFETY: We prepared the garbage collection, hence there are no
1282            // "weak" edges.
1283            unsafe { level.gc(store) };
1284            collected -= level.len() as u32;
1285        }
1286        collected += store.terminal_manager.gc();
1287
1288        if !self.reorder_gc_prepared {
1289            // SAFETY: We called `pre_gc`, the garbage collection is done.
1290            unsafe { self.data.post_gc(self) };
1291        }
1292        self.gc_ongoing.unlock();
1293        guard.defuse();
1294
1295        #[cfg(feature = "statistics")]
1296        eprintln!(
1297            "[oxidd-manager-index] garbage collection finished at {}: removed {} nodes",
1298            std::time::SystemTime::now()
1299                .duration_since(std::time::UNIX_EPOCH)
1300                .unwrap()
1301                .as_secs(),
1302            collected,
1303        );
1304        collected as usize
1305    }
1306
1307    fn reorder<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
1308        if self.reorder_gc_prepared {
1309            // Reordering was already prepared (nested `reorder` call).
1310            // Important: We must return here to not finalize the reordering
1311            // before the parent `reorder` closure returns.
1312            return f(self);
1313        }
1314        let guard = AbortOnDrop("Reordering panicked.");
1315
1316        self.data.pre_gc(self);
1317        self.reorder_gc_prepared = true;
1318        self.data.pre_reorder(self);
1319        MD::pre_reorder_mut(self);
1320
1321        let res = f(self);
1322
1323        self.data.post_reorder(self);
1324        MD::post_reorder_mut(self);
1325        self.reorder_gc_prepared = false;
1326        // SAFETY: We called `pre_gc`, the reordering is done.
1327        unsafe { self.data.post_gc(self) };
1328
1329        guard.defuse();
1330        // Depending on the reordering implementation, garbage collections are
1331        // preformed, but not necessarily through `Self::gc`. So we increment
1332        // the GC count here.
1333        *self.gc_count.get_mut() += 1;
1334        self.reorder_count += 1;
1335        res
1336    }
1337
1338    #[inline]
1339    fn gc_count(&self) -> u64 {
1340        self.gc_count.load(Relaxed)
1341    }
1342
1343    #[inline]
1344    fn reorder_count(&self) -> u64 {
1345        self.reorder_count
1346    }
1347}
1348
1349impl<'id, N, ET, TM, R, MD, const TERMINALS: usize> oxidd_core::HasWorkers
1350    for Manager<'id, N, ET, TM, R, MD, TERMINALS>
1351where
1352    N: NodeBase + InnerNode<Edge<'id, N, ET>> + Send + Sync,
1353    ET: Tag + Send + Sync,
1354    TM: TerminalManager<'id, N, ET, TERMINALS> + Send + Sync,
1355    R: oxidd_core::DiagramRules<Edge<'id, N, ET>, N, TM::TerminalNode>,
1356    MD: DropWith<Edge<'id, N, ET>> + ManagerEventSubscriber<Self> + Send + Sync,
1357{
1358    type WorkerPool = crate::workers::Workers;
1359
1360    #[inline]
1361    fn workers(&self) -> &Self::WorkerPool {
1362        &self.store().workers
1363    }
1364}
1365
1366// === Unique Table ============================================================
1367
1368/// The underlying data structure for level views
1369///
1370/// Conceptually, every [`LevelViewSet`] is tied to a [`Manager`]. It is a hash
1371/// table ensuring the uniqueness of nodes. However, it does not store nodes
1372/// internally, but edges referencing nodes. These edges are always untagged and
1373/// reference inner nodes.
1374///
1375/// Because a [`LevelViewSet`] on its own is not sufficient to drop nodes
1376/// accordingly, this will simply leak all contained edges, not calling the
1377/// `Edge`'s `Drop` implementation.
1378struct LevelViewSet<'id, N, ET, TM, R, MD, const TERMINALS: usize>(
1379    huge::RawTable<Edge<'id, N, ET>>,
1380    PhantomData<(TM, R, MD)>,
1381);
1382
1383#[inline]
1384fn hash_node<N: NodeBase>(node: &N) -> u64 {
1385    let mut hasher = FxHasher::default();
1386    node.hash(&mut hasher);
1387    hasher.finish()
1388}
1389
1390impl<'id, N, ET, TM, R, MD, const TERMINALS: usize> LevelViewSet<'id, N, ET, TM, R, MD, TERMINALS>
1391where
1392    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
1393    ET: Tag,
1394    TM: TerminalManager<'id, N, ET, TERMINALS>,
1395    MD: DropWith<Edge<'id, N, ET>>,
1396{
1397    /// Get the number of nodes on this level
1398    #[inline]
1399    fn len(&self) -> usize {
1400        self.0.len()
1401    }
1402
1403    /// Get an equality function for entries
1404    ///
1405    /// SAFETY: The returned function must be called on untagged edges
1406    /// referencing inner nodes only.
1407    #[inline]
1408    unsafe fn eq<'a>(
1409        nodes: &'a SlotSlice<'id, N, TERMINALS>,
1410        node: &'a N,
1411    ) -> impl Fn(&Edge<'id, N, ET>) -> bool + 'a {
1412        move |edge| unsafe { nodes.inner_node_unchecked(edge) == node }
1413    }
1414
1415    /// Reserve space for `additional` nodes on this level
1416    #[inline]
1417    fn reserve(&mut self, additional: usize) {
1418        // SAFETY: The hash table only contains untagged edges referencing inner
1419        // nodes.
1420        self.0.reserve(additional)
1421    }
1422
1423    #[inline]
1424    fn get(&self, nodes: &SlotSlice<'id, N, TERMINALS>, node: &N) -> Option<&Edge<'id, N, ET>> {
1425        let hash = hash_node(node);
1426        // SAFETY: The hash table only contains untagged edges referencing inner
1427        // nodes.
1428        self.0.get(hash, unsafe { Self::eq(nodes, node) })
1429    }
1430
1431    /// Insert the given edge, assuming that the referenced node is already
1432    /// stored in `nodes`.
1433    ///
1434    /// Returns `true` if the edge was inserted, `false` if it was already
1435    /// present.
1436    ///
1437    /// Panics if `edge` points to a terminal node. May furthermore panic if
1438    /// `edge` is tagged, depending on the configuration.
1439    #[inline]
1440    fn insert(&mut self, nodes: &SlotSlice<'id, N, TERMINALS>, edge: Edge<'id, N, ET>) -> bool {
1441        debug_assert!(!edge.is_tagged(), "`edge` must not be tagged");
1442        let node = nodes.inner_node(&edge);
1443        let hash = hash_node(node);
1444        // SAFETY (next 2): The hash table only contains untagged edges
1445        // referencing inner nodes.
1446        match self
1447            .0
1448            .find_or_find_insert_slot(hash, unsafe { Self::eq(nodes, node) })
1449        {
1450            Ok(_) => {
1451                // We need to drop `edge`. This simply amounts to decrementing
1452                // the reference counter, since there is still one edge
1453                // referencing `node` stored in here.
1454                std::mem::forget(edge);
1455                // SAFETY: We forgot `edge`.
1456                let _old_rc = unsafe { node.release() };
1457                debug_assert!(_old_rc > 1);
1458
1459                false
1460            }
1461            Err(slot) => {
1462                // SAFETY: `slot` was returned by `find_or_find_insert_slot`.
1463                // We have exclusive access to the hash table and did not modify
1464                // it in between.
1465                unsafe { self.0.insert_in_slot_unchecked(hash, slot, edge) };
1466                true
1467            }
1468        }
1469    }
1470
1471    /// Get an edge for `node`
1472    ///
1473    /// If `node` is not yet present in the hash table, `insert` is called to
1474    /// insert the node into `nodes`.
1475    #[inline]
1476    fn get_or_insert(
1477        &mut self,
1478        nodes: &SlotSlice<'id, N, TERMINALS>,
1479        node: N,
1480        insert: impl FnOnce(N) -> AllocResult<[Edge<'id, N, ET>; 2]>,
1481        drop: impl FnOnce(N),
1482    ) -> AllocResult<Edge<'id, N, ET>> {
1483        let hash = hash_node(&node);
1484        // SAFETY (next 2): The hash table only contains untagged edges
1485        // referencing inner nodes.
1486        match self
1487            .0
1488            .find_or_find_insert_slot(hash, unsafe { Self::eq(nodes, &node) })
1489        {
1490            Ok(slot) => {
1491                drop(node);
1492                // SAFETY:
1493                // - `slot` was returned by `find_or_find_insert_slot`. We have exclusive access
1494                //   to the hash table and did not modify it in between.
1495                // - All edges in the table are untagged and refer to inner nodes.
1496                Ok(unsafe { nodes.clone_edge_unchecked(self.0.get_at_slot_unchecked(slot)) })
1497            }
1498            Err(slot) => {
1499                let [e1, e2] = insert(node)?;
1500                // SAFETY: `slot` was returned by `find_or_find_insert_slot`.
1501                // We have exclusive access to the hash table and did not modify
1502                // it in between.
1503                unsafe { self.0.insert_in_slot_unchecked(hash, slot, e1) };
1504                Ok(e2)
1505            }
1506        }
1507    }
1508
1509    /// Perform garbage collection, i.e., remove all nodes without references
1510    /// besides the internal edge
1511    ///
1512    /// SAFETY: There must not be any "weak" edges, i.e., edges where the
1513    /// reference count is not materialized (apply cache implementations exploit
1514    /// this).
1515    unsafe fn gc(&mut self, store: &Store<'id, N, ET, TM, R, MD, TERMINALS>) {
1516        let inner_nodes = &*store.inner_nodes;
1517        self.0.retain(
1518            |edge| {
1519                // SAFETY: All edges in unique tables are untagged and point to
1520                // inner nodes.
1521                unsafe { inner_nodes.inner_node_unchecked(edge) }.load_rc(Acquire) != 1
1522            },
1523            |edge| {
1524                // SAFETY (next 2): `edge` is untagged and points to an inner node
1525                let slot_ptr = unsafe { inner_nodes.slot_pointer_unchecked(&edge) };
1526                let id = unsafe { edge.node_id_unchecked() };
1527                std::mem::forget(edge);
1528
1529                // SAFETY: Since `rc` is 1, this is the last reference. We use
1530                // `Acquire` order above and `Release` order when decrementing
1531                // reference counters, so we have exclusive node access now. The
1532                // slot contains a node. `id` is the ID of the slot.
1533                unsafe { store.free_slot(&mut *slot_ptr, id) };
1534            },
1535        );
1536    }
1537
1538    /// Remove `node`
1539    ///
1540    /// Returns `Some(edge)` if `node` was present, `None` otherwise
1541    ///
1542    /// SAFETY: There must not be any "weak" edges, i.e., edges where the
1543    /// reference count is not materialized (apply cache implementations exploit
1544    /// this).
1545    #[inline]
1546    unsafe fn remove(
1547        &mut self,
1548        nodes: &SlotSlice<'id, N, TERMINALS>,
1549        node: &N,
1550    ) -> Option<Edge<'id, N, ET>> {
1551        let hash = hash_node(node);
1552        // SAFETY: The hash table only contains untagged edges referencing inner
1553        // nodes.
1554        self.0.remove_entry(hash, unsafe { Self::eq(nodes, node) })
1555    }
1556
1557    /// Iterate over all edges pointing to nodes in the set
1558    #[inline]
1559    fn iter(&self) -> LevelViewIter<'_, 'id, N, ET> {
1560        LevelViewIter(self.0.iter())
1561    }
1562
1563    /// Iterator that consumes all [`Edge`]s in the set
1564    #[inline]
1565    fn drain(&mut self) -> linear_hashtbl::raw::Drain<'_, Edge<'id, N, ET>, u32> {
1566        self.0.drain()
1567    }
1568}
1569
1570impl<N, ET, TM, R, MD, const TERMINALS: usize> Drop
1571    for LevelViewSet<'_, N, ET, TM, R, MD, TERMINALS>
1572{
1573    #[inline]
1574    fn drop(&mut self) {
1575        // If the nodes need drop, this is handled by the `Manager` `Drop` impl
1576        // or the `TakenLevelView` `Drop` impl, respectively.
1577        self.0.reset_no_drop();
1578    }
1579}
1580
1581impl<N, ET, TM, R, MD, const TERMINALS: usize> Default
1582    for LevelViewSet<'_, N, ET, TM, R, MD, TERMINALS>
1583{
1584    #[inline]
1585    fn default() -> Self {
1586        Self(Default::default(), PhantomData)
1587    }
1588}
1589
1590impl<'id, N, ET, TM, R, MD, const TERMINALS: usize> IntoIterator
1591    for LevelViewSet<'id, N, ET, TM, R, MD, TERMINALS>
1592{
1593    type Item = Edge<'id, N, ET>;
1594
1595    type IntoIter = huge::RawTableIntoIter<Edge<'id, N, ET>>;
1596
1597    fn into_iter(self) -> Self::IntoIter {
1598        let this = ManuallyDrop::new(self);
1599        // SAFETY: We move out of `this` (and forget `this`)
1600        let set = unsafe { std::ptr::read(&this.0) };
1601        set.into_iter()
1602    }
1603}
1604
1605pub struct LevelViewIter<'a, 'id, N, ET>(linear_hashtbl::raw::Iter<'a, Edge<'id, N, ET>, u32>);
1606
1607impl<'a, 'id, N, ET> Iterator for LevelViewIter<'a, 'id, N, ET> {
1608    type Item = &'a Edge<'id, N, ET>;
1609
1610    #[inline]
1611    fn next(&mut self) -> Option<Self::Item> {
1612        self.0.next()
1613    }
1614
1615    #[inline]
1616    fn size_hint(&self) -> (usize, Option<usize>) {
1617        self.0.size_hint()
1618    }
1619}
1620
1621impl<N, ET> ExactSizeIterator for LevelViewIter<'_, '_, N, ET> {
1622    #[inline(always)]
1623    fn len(&self) -> usize {
1624        self.0.len()
1625    }
1626}
1627impl<'a, 'id, N, ET> FusedIterator for LevelViewIter<'a, 'id, N, ET> where
1628    linear_hashtbl::raw::Iter<'a, Edge<'id, N, ET>, u32>: FusedIterator
1629{
1630}
1631
1632pub struct LevelView<'a, 'id, N, ET, TM, R, MD, const TERMINALS: usize>
1633where
1634    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
1635    ET: Tag,
1636    TM: TerminalManager<'id, N, ET, TERMINALS>,
1637    MD: DropWith<Edge<'id, N, ET>>,
1638{
1639    store: &'a Store<'id, N, ET, TM, R, MD, TERMINALS>,
1640    var_level_map: &'a VarLevelMap,
1641    /// SAFETY invariant: If set to true, a garbage collection is prepared
1642    /// (i.e., there are no "weak" edges).
1643    allow_node_removal: bool,
1644    level: LevelNo,
1645    set: MutexGuard<'a, LevelViewSet<'id, N, ET, TM, R, MD, TERMINALS>>,
1646}
1647
1648unsafe impl<'a, 'id, N, ET, TM, R, MD, const TERMINALS: usize>
1649    oxidd_core::LevelView<Edge<'id, N, ET>, N> for LevelView<'a, 'id, N, ET, TM, R, MD, TERMINALS>
1650where
1651    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
1652    ET: Tag,
1653    TM: TerminalManager<'id, N, ET, TERMINALS>,
1654    MD: DropWith<Edge<'id, N, ET>>,
1655{
1656    type Iterator<'b>
1657        = LevelViewIter<'b, 'id, N, ET>
1658    where
1659        Self: 'b,
1660        Edge<'id, N, ET>: 'b;
1661
1662    type Taken = TakenLevelView<'a, 'id, N, ET, TM, R, MD, TERMINALS>;
1663
1664    #[inline(always)]
1665    fn len(&self) -> usize {
1666        self.set.len()
1667    }
1668
1669    #[inline(always)]
1670    fn level_no(&self) -> LevelNo {
1671        self.level
1672    }
1673
1674    #[inline]
1675    fn reserve(&mut self, additional: usize) {
1676        self.set.reserve(additional);
1677    }
1678
1679    #[inline]
1680    fn get(&self, node: &N) -> Option<&Edge<'id, N, ET>> {
1681        self.set.get(&self.store.inner_nodes, node)
1682    }
1683
1684    #[inline]
1685    fn insert(&mut self, edge: Edge<'id, N, ET>) -> bool {
1686        debug_assert!(!edge.is_tagged(), "`edge` should not be tagged");
1687        // No need to check if the node referenced by `edge` is stored in
1688        // `self.store` due to lifetime restrictions.
1689        let nodes = &self.store.inner_nodes;
1690        nodes.inner_node(&edge).assert_level_matches(self.level);
1691        self.set.insert(nodes, edge)
1692    }
1693
1694    #[inline]
1695    unsafe fn insert_unchecked(&mut self, edge: Edge<'id, N, ET>) -> bool {
1696        debug_assert!(!edge.is_tagged(), "`edge` should not be tagged");
1697        // No need to check if the node referenced by `edge` is stored in
1698        // `self.store` due to lifetime restrictions.
1699        self.set.insert(&self.store.inner_nodes, edge)
1700    }
1701
1702    #[inline(always)]
1703    fn get_or_insert(&mut self, node: N) -> AllocResult<Edge<'id, N, ET>> {
1704        node.assert_level_matches(self.level);
1705        // No need to check if the children of `node` are stored in `self.store`
1706        // due to lifetime restrictions.
1707        self.set.get_or_insert(
1708            &self.store.inner_nodes,
1709            node,
1710            |node| self.store.add_node(node),
1711            |node| node.drop_with(|edge| self.store.drop_edge(edge)),
1712        )
1713    }
1714
1715    #[inline(always)]
1716    unsafe fn get_or_insert_unchecked(&mut self, node: N) -> AllocResult<Edge<'id, N, ET>> {
1717        // No need to check if the children of `node` are stored in `self.store`
1718        // due to lifetime restrictions.
1719        self.set.get_or_insert(
1720            &self.store.inner_nodes,
1721            node,
1722            |node| self.store.add_node(node),
1723            |node| node.drop_with(|edge| self.store.drop_edge(edge)),
1724        )
1725    }
1726
1727    #[inline]
1728    fn gc(&mut self) {
1729        if self.allow_node_removal {
1730            // SAFETY: By invariant, node removal is allowed.
1731            unsafe { self.set.gc(self.store) };
1732        }
1733    }
1734
1735    #[inline]
1736    fn remove(&mut self, node: &N) -> bool {
1737        if !self.allow_node_removal {
1738            return false;
1739        }
1740        // SAFETY: By invariant, node removal is allowed.
1741        match unsafe { self.set.remove(&self.store.inner_nodes, node) } {
1742            Some(edge) => {
1743                // SAFETY: `edge` is untagged and points to an inner node
1744                unsafe { self.store.drop_unique_table_edge(edge) };
1745                true
1746            }
1747            None => false,
1748        }
1749    }
1750
1751    #[inline]
1752    unsafe fn swap(&mut self, other: &mut Self) {
1753        self.var_level_map.swap_levels(self.level, other.level);
1754        std::mem::swap(&mut *self.set, &mut *other.set);
1755    }
1756
1757    #[inline]
1758    fn iter(&self) -> Self::Iterator<'_> {
1759        self.set.iter()
1760    }
1761
1762    #[inline(always)]
1763    fn take(&mut self) -> Option<Self::Taken> {
1764        if self.allow_node_removal {
1765            Some(TakenLevelView {
1766                store: self.store,
1767                var_level_map: self.var_level_map,
1768                level: self.level,
1769                set: std::mem::take(&mut self.set),
1770            })
1771        } else {
1772            None
1773        }
1774    }
1775}
1776
1777/// Owned level view provided by the unique table
1778//
1779// SAFETY invariant: whenever a TakenLevelView is live, a garbage collection is
1780// prepared (i.e., there are no "weak" edges).
1781//
1782// Note that due to lifetime restrictions there is no way to have a
1783// non-empty `TakenLevelView` when the associated `Manager` has
1784// `reorder_gc_prepared` set to `false`.
1785pub struct TakenLevelView<'a, 'id, N, ET, TM, R, MD, const TERMINALS: usize>
1786where
1787    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
1788    ET: Tag,
1789    TM: TerminalManager<'id, N, ET, TERMINALS>,
1790    MD: DropWith<Edge<'id, N, ET>>,
1791{
1792    store: &'a Store<'id, N, ET, TM, R, MD, TERMINALS>,
1793    var_level_map: &'a VarLevelMap,
1794    level: LevelNo,
1795    set: LevelViewSet<'id, N, ET, TM, R, MD, TERMINALS>,
1796}
1797
1798unsafe impl<'id, N, ET, TM, R, MD, const TERMINALS: usize>
1799    oxidd_core::LevelView<Edge<'id, N, ET>, N>
1800    for TakenLevelView<'_, 'id, N, ET, TM, R, MD, TERMINALS>
1801where
1802    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
1803    ET: Tag,
1804    TM: TerminalManager<'id, N, ET, TERMINALS>,
1805    MD: DropWith<Edge<'id, N, ET>>,
1806{
1807    type Iterator<'b>
1808        = LevelViewIter<'b, 'id, N, ET>
1809    where
1810        Self: 'b,
1811        Edge<'id, N, ET>: 'b;
1812
1813    type Taken = Self;
1814
1815    #[inline(always)]
1816    fn len(&self) -> usize {
1817        self.set.len()
1818    }
1819
1820    #[inline(always)]
1821    fn level_no(&self) -> LevelNo {
1822        self.level
1823    }
1824
1825    #[inline]
1826    fn reserve(&mut self, additional: usize) {
1827        self.set.reserve(additional);
1828    }
1829
1830    #[inline]
1831    fn get(&self, node: &N) -> Option<&Edge<'id, N, ET>> {
1832        self.set.get(&self.store.inner_nodes, node)
1833    }
1834
1835    #[inline]
1836    fn insert(&mut self, edge: Edge<'id, N, ET>) -> bool {
1837        debug_assert!(!edge.is_tagged(), "`edge` should not be tagged");
1838        // No need to check if the node referenced by `edge` is stored in
1839        // `self.store` due to lifetime restrictions.
1840        let nodes = &self.store.inner_nodes;
1841        nodes.inner_node(&edge).assert_level_matches(self.level);
1842        self.set.insert(nodes, edge)
1843    }
1844
1845    #[inline]
1846    unsafe fn insert_unchecked(&mut self, edge: Edge<'id, N, ET>) -> bool {
1847        debug_assert!(!edge.is_tagged(), "`edge` should not be tagged");
1848        // No need to check if the node referenced by `edge` is stored in
1849        // `self.store` due to lifetime restrictions.
1850        self.set.insert(&self.store.inner_nodes, edge)
1851    }
1852
1853    #[inline(always)]
1854    fn get_or_insert(&mut self, node: N) -> AllocResult<Edge<'id, N, ET>> {
1855        node.assert_level_matches(self.level);
1856        // No need to check if the children of `node` are stored in `self.store`
1857        // due to lifetime restrictions.
1858        self.set.get_or_insert(
1859            &self.store.inner_nodes,
1860            node,
1861            |node| self.store.add_node(node),
1862            |node| node.drop_with(|edge| self.store.drop_edge(edge)),
1863        )
1864    }
1865
1866    #[inline(always)]
1867    unsafe fn get_or_insert_unchecked(&mut self, node: N) -> AllocResult<Edge<'id, N, ET>> {
1868        // No need to check if the children of `node` are stored in `self.store`
1869        // due to lifetime restrictions.
1870        self.set.get_or_insert(
1871            &self.store.inner_nodes,
1872            node,
1873            |node| self.store.add_node(node),
1874            |node| node.drop_with(|edge| self.store.drop_edge(edge)),
1875        )
1876    }
1877
1878    #[inline]
1879    fn gc(&mut self) {
1880        // SAFETY: By invariant, node removal is allowed or the set is empty.
1881        unsafe { self.set.gc(self.store) };
1882    }
1883
1884    #[inline]
1885    fn remove(&mut self, node: &N) -> bool {
1886        // SAFETY: By invariant, node removal is allowed or the set is empty.
1887        match unsafe { self.set.remove(&self.store.inner_nodes, node) } {
1888            Some(edge) => {
1889                // SAFETY: `edge` is untagged and points to an inner node
1890                unsafe { self.store.drop_unique_table_edge(edge) };
1891                true
1892            }
1893            None => false,
1894        }
1895    }
1896
1897    #[inline(always)]
1898    unsafe fn swap(&mut self, other: &mut Self) {
1899        self.var_level_map.swap_levels(self.level, other.level);
1900        std::mem::swap(&mut self.set, &mut other.set);
1901    }
1902
1903    #[inline]
1904    fn iter(&self) -> Self::Iterator<'_> {
1905        self.set.iter()
1906    }
1907
1908    #[inline(always)]
1909    fn take(&mut self) -> Option<Self::Taken> {
1910        Some(Self {
1911            store: self.store,
1912            var_level_map: self.var_level_map,
1913            level: self.level,
1914            set: std::mem::take(&mut self.set),
1915        })
1916    }
1917}
1918
1919impl<'id, N, ET, TM, R, MD, const TERMINALS: usize> Drop
1920    for TakenLevelView<'_, 'id, N, ET, TM, R, MD, TERMINALS>
1921where
1922    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
1923    ET: Tag,
1924    TM: TerminalManager<'id, N, ET, TERMINALS>,
1925    MD: DropWith<Edge<'id, N, ET>>,
1926{
1927    fn drop(&mut self) {
1928        for edge in self.set.drain() {
1929            // SAFETY: Edges in unique tables are untagged and point to inner
1930            // nodes.
1931            unsafe { self.store.drop_unique_table_edge(edge) }
1932        }
1933    }
1934}
1935
1936// --- LevelIterator -----------------------------------------------------------
1937
1938pub struct LevelIter<'a, 'id, N, ET, TM, R, MD, const TERMINALS: usize>
1939where
1940    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
1941    ET: Tag,
1942    TM: TerminalManager<'id, N, ET, TERMINALS>,
1943    MD: DropWith<Edge<'id, N, ET>>,
1944{
1945    store: &'a Store<'id, N, ET, TM, R, MD, TERMINALS>,
1946    var_level_map: &'a VarLevelMap,
1947    /// SAFETY invariant: If set to true, a garbage collection is prepared
1948    /// (i.e., there are no "weak" edges).
1949    allow_node_removal: bool,
1950    level_front: LevelNo,
1951    level_back: LevelNo,
1952    it: std::slice::Iter<'a, Mutex<LevelViewSet<'id, N, ET, TM, R, MD, TERMINALS>>>,
1953}
1954
1955impl<'a, 'id, N, ET, TM, R, MD, const TERMINALS: usize> Iterator
1956    for LevelIter<'a, 'id, N, ET, TM, R, MD, TERMINALS>
1957where
1958    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
1959    ET: Tag,
1960    TM: TerminalManager<'id, N, ET, TERMINALS>,
1961    MD: DropWith<Edge<'id, N, ET>>,
1962{
1963    type Item = LevelView<'a, 'id, N, ET, TM, R, MD, TERMINALS>;
1964
1965    #[inline]
1966    fn next(&mut self) -> Option<Self::Item> {
1967        let mutex = self.it.next()?;
1968        let level = self.level_front;
1969        self.level_front += 1;
1970        Some(LevelView {
1971            store: self.store,
1972            var_level_map: self.var_level_map,
1973            allow_node_removal: self.allow_node_removal,
1974            level,
1975            set: mutex.lock(),
1976        })
1977    }
1978
1979    #[inline]
1980    fn size_hint(&self) -> (usize, Option<usize>) {
1981        self.it.size_hint()
1982    }
1983}
1984
1985impl<'id, N, ET, TM, R, MD, const TERMINALS: usize> ExactSizeIterator
1986    for LevelIter<'_, 'id, N, ET, TM, R, MD, TERMINALS>
1987where
1988    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
1989    ET: Tag,
1990    TM: TerminalManager<'id, N, ET, TERMINALS>,
1991    MD: DropWith<Edge<'id, N, ET>>,
1992{
1993    #[inline(always)]
1994    fn len(&self) -> usize {
1995        self.it.len()
1996    }
1997}
1998impl<'a, 'id, N, ET, TM, R, MD, const TERMINALS: usize> FusedIterator
1999    for LevelIter<'a, 'id, N, ET, TM, R, MD, TERMINALS>
2000where
2001    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
2002    ET: Tag,
2003    TM: TerminalManager<'id, N, ET, TERMINALS>,
2004    MD: DropWith<Edge<'id, N, ET>>,
2005    std::slice::Iter<'a, Mutex<LevelViewSet<'id, N, ET, TM, R, MD, TERMINALS>>>: FusedIterator,
2006{
2007}
2008
2009impl<'id, N, ET, TM, R, MD, const TERMINALS: usize> DoubleEndedIterator
2010    for LevelIter<'_, 'id, N, ET, TM, R, MD, TERMINALS>
2011where
2012    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
2013    ET: Tag,
2014    TM: TerminalManager<'id, N, ET, TERMINALS>,
2015    MD: DropWith<Edge<'id, N, ET>>,
2016{
2017    fn next_back(&mut self) -> Option<Self::Item> {
2018        let mutex = self.it.next_back()?;
2019        self.level_back -= 1;
2020        Some(LevelView {
2021            store: self.store,
2022            var_level_map: self.var_level_map,
2023            allow_node_removal: self.allow_node_removal,
2024            level: self.level_back,
2025            set: mutex.lock(),
2026        })
2027    }
2028}
2029
2030// === ManagerRef ==============================================================
2031
2032#[repr(transparent)]
2033#[derive_where(Clone)]
2034pub struct ManagerRef<
2035    NC: InnerNodeCons<ET>,
2036    ET: Tag,
2037    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2038    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2039    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2040    const TERMINALS: usize,
2041>(
2042    Arc<
2043        Store<
2044            'static,
2045            NC::T<'static>,
2046            ET,
2047            TMC::T<'static>,
2048            RC::T<'static>,
2049            MDC::T<'static>,
2050            TERMINALS,
2051        >,
2052    >,
2053);
2054
2055impl<
2056    NC: InnerNodeCons<ET>,
2057    ET: Tag,
2058    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2059    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2060    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2061    const TERMINALS: usize,
2062> Drop for ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS>
2063{
2064    fn drop(&mut self) {
2065        if Arc::strong_count(&self.0) == 2 {
2066            // This is the second last reference. The last reference belongs to
2067            // the gc thread. Terminate it.
2068            let gc_signal = &self.0.gc_signal;
2069            *gc_signal.0.lock() = GCSignal::Quit;
2070            gc_signal.1.notify_one();
2071        }
2072    }
2073}
2074
2075impl<
2076    NC: InnerNodeCons<ET>,
2077    ET: Tag,
2078    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2079    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2080    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2081    const TERMINALS: usize,
2082> ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS>
2083{
2084    /// Convert `self` into a raw pointer, e.g., for usage in a foreign function
2085    /// interface.
2086    ///
2087    /// This method does not change any reference counters. To avoid a memory
2088    /// leak, use [`Self::from_raw()`] to convert the pointer back into a
2089    /// `ManagerRef`.
2090    #[inline(always)]
2091    pub fn into_raw(self) -> *const std::ffi::c_void {
2092        let this = ManuallyDrop::new(self);
2093        // SAFETY: we move out of `this`
2094        Arc::into_raw(unsafe { std::ptr::read(&this.0) }).cast()
2095    }
2096
2097    /// Convert `raw` into a `ManagerRef`
2098    ///
2099    /// # Safety
2100    ///
2101    /// `raw` must have been obtained via [`Self::into_raw()`]. This function
2102    /// does not change any reference counters, so calling this function
2103    /// multiple times for the same pointer may lead to use after free bugs
2104    /// depending on the usage of the returned `ManagerRef`.
2105    #[inline(always)]
2106    pub unsafe fn from_raw(raw: *const std::ffi::c_void) -> Self {
2107        // SAFETY: Invariants are upheld by the caller.
2108        Self(unsafe { Arc::from_raw(raw.cast()) })
2109    }
2110}
2111
2112impl<
2113    NC: InnerNodeCons<ET>,
2114    ET: Tag,
2115    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2116    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2117    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2118    const TERMINALS: usize,
2119> PartialEq for ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS>
2120{
2121    #[inline]
2122    fn eq(&self, other: &Self) -> bool {
2123        Arc::ptr_eq(&self.0, &other.0)
2124    }
2125}
2126impl<
2127    NC: InnerNodeCons<ET>,
2128    ET: Tag,
2129    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2130    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2131    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2132    const TERMINALS: usize,
2133> Eq for ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS>
2134{
2135}
2136impl<
2137    NC: InnerNodeCons<ET>,
2138    ET: Tag,
2139    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2140    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2141    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2142    const TERMINALS: usize,
2143> PartialOrd for ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS>
2144{
2145    #[inline]
2146    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
2147        Some(self.cmp(other))
2148    }
2149}
2150impl<
2151    NC: InnerNodeCons<ET>,
2152    ET: Tag,
2153    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2154    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2155    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2156    const TERMINALS: usize,
2157> Ord for ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS>
2158{
2159    #[inline]
2160    fn cmp(&self, other: &Self) -> Ordering {
2161        std::ptr::from_ref(&*self.0).cmp(&std::ptr::from_ref(&*other.0))
2162    }
2163}
2164impl<
2165    NC: InnerNodeCons<ET>,
2166    ET: Tag,
2167    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2168    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2169    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2170    const TERMINALS: usize,
2171> Hash for ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS>
2172{
2173    #[inline]
2174    fn hash<H: Hasher>(&self, state: &mut H) {
2175        std::ptr::from_ref(&*self.0).hash(state);
2176    }
2177}
2178
2179impl<
2180    'a,
2181    'id,
2182    NC: InnerNodeCons<ET>,
2183    ET: Tag,
2184    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2185    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2186    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2187    const TERMINALS: usize,
2188> From<&'a M<'id, NC, ET, TMC, RC, MDC, TERMINALS>>
2189    for ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS>
2190{
2191    fn from(manager: &'a M<'id, NC, ET, TMC, RC, MDC, TERMINALS>) -> Self {
2192        let ptr: *const M<'static, NC, ET, TMC, RC, MDC, TERMINALS> =
2193            std::ptr::from_ref(manager).cast();
2194        // SAFETY:
2195        // - We just changed "identifier" lifetimes.
2196        // - The pointer was obtained via `Arc::into_raw()`, and since we have a
2197        //   `&Manager` reference, the counter is at least 1.
2198        ManagerRef(unsafe {
2199            let manager = &*ptr;
2200            Arc::increment_strong_count(manager.store);
2201            Arc::from_raw(manager.store)
2202        })
2203    }
2204}
2205
2206impl<
2207    NC: InnerNodeCons<ET>,
2208    ET: Tag,
2209    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2210    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2211    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2212    const TERMINALS: usize,
2213> oxidd_core::ManagerRef for ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS>
2214{
2215    type Manager<'id> = M<'id, NC, ET, TMC, RC, MDC, TERMINALS>;
2216
2217    fn with_manager_shared<F, T>(&self, f: F) -> T
2218    where
2219        F: for<'id> FnOnce(&Self::Manager<'id>) -> T,
2220    {
2221        let local_guard = self.0.prepare_local_state();
2222        let res = f(&self.0.manager.shared());
2223        drop(local_guard);
2224        res
2225    }
2226
2227    fn with_manager_exclusive<F, T>(&self, f: F) -> T
2228    where
2229        F: for<'id> FnOnce(&mut Self::Manager<'id>) -> T,
2230    {
2231        let local_guard = self.0.prepare_local_state();
2232        let res = f(&mut self.0.manager.exclusive());
2233        drop(local_guard);
2234        res
2235    }
2236}
2237
2238/// Create a new manager
2239pub fn new_manager<
2240    NC: InnerNodeCons<ET> + 'static,
2241    ET: Tag + Send + Sync + 'static,
2242    TMC: TerminalManagerCons<NC, ET, TERMINALS> + 'static,
2243    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS> + 'static,
2244    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS> + 'static,
2245    const TERMINALS: usize,
2246>(
2247    inner_node_capacity: u32,
2248    terminal_node_capacity: u32,
2249    threads: u32,
2250    data: MDC::T<'static>,
2251) -> ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS> {
2252    // Evaluate constant for assertions
2253    let () = Store::<
2254        'static,
2255        NC::T<'static>,
2256        ET,
2257        TMC::T<'static>,
2258        RC::T<'static>,
2259        MDC::T<'static>,
2260        TERMINALS,
2261    >::CHECK_TERMINALS;
2262
2263    let max_inner_capacity = if usize::BITS > u32::BITS {
2264        ((1usize << (u32::BITS - Edge::<NC::T<'static>, ET>::TAG_BITS)) - TERMINALS) as u32
2265    } else {
2266        // 32 bit address space. Every node consumes 16 byte plus at least
2267        // 4 byte in the unique table. Furthermore, there is the apply cache.
2268        // If we try to reserve space for more than `u32::MAX / 24` nodes, we
2269        // will most likely run out of memory.
2270        u32::MAX / 24
2271    };
2272    let inner_node_capacity = std::cmp::min(inner_node_capacity, max_inner_capacity);
2273
2274    let gc_lwm = inner_node_capacity / 100 * 90;
2275    let gc_hwm = inner_node_capacity / 100 * 95;
2276
2277    let arc = Arc::new(Store {
2278        inner_nodes: SlotSlice::new_boxed(inner_node_capacity),
2279        state: CachePadded::new(Mutex::new(SharedStoreState {
2280            next_free: Vec::new(),
2281            allocated: 0,
2282            node_count: 0,
2283            gc_state: if gc_lwm < gc_hwm {
2284                GCState::Init
2285            } else {
2286                GCState::Disabled
2287            },
2288            gc_lwm,
2289            gc_hwm,
2290        })),
2291        manager: RwLock::new(Manager {
2292            unique_table: Vec::new(),
2293            var_level_map: VarLevelMap::new(),
2294            var_name_map: VarNameMap::new(),
2295            data: ManuallyDrop::new(data),
2296            store: std::ptr::null(),
2297            gc_count: AtomicU64::new(0),
2298            reorder_count: 0,
2299            gc_ongoing: TryLock::new(),
2300            reorder_gc_prepared: false,
2301        }),
2302        terminal_manager: TMC::T::<'static>::with_capacity(terminal_node_capacity),
2303        gc_signal: (Mutex::new(GCSignal::RunGc), Condvar::new()),
2304        workers: crate::workers::Workers::new(threads),
2305    });
2306
2307    let mut manager = arc.manager.exclusive();
2308    manager.store = Arc::as_ptr(&arc);
2309    drop(manager);
2310
2311    let store_addr = addr(&*arc);
2312    arc.workers.pool.spawn_broadcast(move |_| {
2313        // The workers are dedicated to this store.
2314        LOCAL_STORE_STATE.with(|state| state.current_store.set(store_addr))
2315    });
2316
2317    // spell-checker:ignore mref
2318    let gc_mref: ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS> = ManagerRef(arc.clone());
2319    std::thread::Builder::new()
2320        .name("oxidd mi gc".to_string())
2321        .spawn(move || {
2322            // The worker is dedicated to this store.
2323            LOCAL_STORE_STATE.with(|state| state.current_store.set(store_addr));
2324
2325            let store = &*gc_mref.0;
2326            loop {
2327                let mut lock = store.gc_signal.0.lock();
2328                store.gc_signal.1.wait(&mut lock);
2329                if *lock == GCSignal::Quit {
2330                    break;
2331                }
2332                drop(lock);
2333
2334                // parking_lot `Condvar`s have no spurious wakeups -> run gc now
2335                oxidd_core::ManagerRef::with_manager_shared(&gc_mref, |manager| {
2336                    oxidd_core::Manager::gc(manager);
2337                });
2338
2339                let mut shared = store.state.lock();
2340                LOCAL_STORE_STATE.with(|local| {
2341                    if local.next_free.get() != 0 {
2342                        shared.node_count += local.node_count_delta.replace(0) as i64;
2343                        shared.next_free.push(local.next_free.replace(0));
2344                    }
2345                });
2346
2347                if shared.node_count < shared.gc_lwm as i64 && shared.gc_state != GCState::Disabled
2348                {
2349                    shared.gc_state = GCState::Init;
2350                }
2351            }
2352        })
2353        .unwrap();
2354
2355    // initialize the manager data
2356    let local_guard = arc.prepare_local_state();
2357    arc.manager.exclusive().init();
2358    drop(local_guard);
2359
2360    ManagerRef(arc)
2361}
2362
2363impl<
2364    NC: InnerNodeCons<ET>,
2365    ET: Tag + Send + Sync,
2366    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2367    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2368    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2369    const TERMINALS: usize,
2370> oxidd_core::HasWorkers for ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS>
2371{
2372    type WorkerPool = crate::workers::Workers;
2373
2374    #[inline]
2375    fn workers(&self) -> &Self::WorkerPool {
2376        &self.0.workers
2377    }
2378}
2379
2380// === Function ================================================================
2381
2382#[repr(C)]
2383#[derive_where(PartialEq, Eq, PartialOrd, Ord, Hash)]
2384pub struct Function<
2385    NC: InnerNodeCons<ET>,
2386    ET: Tag,
2387    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2388    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2389    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2390    const TERMINALS: usize,
2391> {
2392    store: ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS>,
2393    edge: ManuallyDrop<Edge<'static, NC::T<'static>, ET>>,
2394}
2395
2396impl<
2397    NC: InnerNodeCons<ET>,
2398    ET: Tag,
2399    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2400    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2401    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2402    const TERMINALS: usize,
2403> Function<NC, ET, TMC, RC, MDC, TERMINALS>
2404{
2405    /// Convert `self` into a raw pointer and edge value, e.g., for usage in a
2406    /// foreign function interface.
2407    ///
2408    /// This method does not change any reference counters. To avoid a memory
2409    /// leak, use [`Self::from_raw()`] to convert pointer and edge value back
2410    /// into a `Function`.
2411    #[inline(always)]
2412    pub fn into_raw(self) -> (*const std::ffi::c_void, u32) {
2413        let this = ManuallyDrop::new(self);
2414        // SAFETY: We forget `this`
2415        (
2416            unsafe { std::ptr::read(&this.store) }.into_raw(),
2417            this.edge.0,
2418        )
2419    }
2420
2421    /// Convert `ptr` and `edge_val` into a `Function`
2422    ///
2423    /// # Safety
2424    ///
2425    /// `raw` and `edge_val` must have been obtained via [`Self::into_raw()`].
2426    /// This function does not change any reference counters, so calling this
2427    /// function multiple times for the same pointer may lead to use after free
2428    /// bugs depending on the usage of the returned `Function`.
2429    #[inline(always)]
2430    pub unsafe fn from_raw(ptr: *const std::ffi::c_void, edge_val: u32) -> Self {
2431        // SAFETY: Invariants are upheld by the caller.
2432        Self {
2433            store: unsafe { ManagerRef::from_raw(ptr) },
2434            edge: ManuallyDrop::new(Edge(edge_val, PhantomData)),
2435        }
2436    }
2437}
2438
2439impl<
2440    NC: InnerNodeCons<ET>,
2441    ET: Tag,
2442    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2443    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2444    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2445    const TERMINALS: usize,
2446> Drop for Function<NC, ET, TMC, RC, MDC, TERMINALS>
2447{
2448    #[inline]
2449    fn drop(&mut self) {
2450        // SAFETY: `self.edge` is never used again.
2451        let edge = unsafe { ManuallyDrop::take(&mut self.edge) };
2452        self.store.0.drop_edge(edge);
2453    }
2454}
2455
2456impl<
2457    NC: InnerNodeCons<ET>,
2458    ET: Tag,
2459    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2460    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2461    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2462    const TERMINALS: usize,
2463> Clone for Function<NC, ET, TMC, RC, MDC, TERMINALS>
2464{
2465    #[inline]
2466    fn clone(&self) -> Self {
2467        Self {
2468            store: self.store.clone(),
2469            edge: ManuallyDrop::new(self.store.0.clone_edge(&self.edge)),
2470        }
2471    }
2472}
2473
2474unsafe impl<
2475    NC: InnerNodeCons<ET>,
2476    ET: Tag,
2477    TMC: TerminalManagerCons<NC, ET, TERMINALS>,
2478    RC: DiagramRulesCons<NC, ET, TMC, MDC, TERMINALS>,
2479    MDC: ManagerDataCons<NC, ET, TMC, RC, TERMINALS>,
2480    const TERMINALS: usize,
2481> oxidd_core::function::Function for Function<NC, ET, TMC, RC, MDC, TERMINALS>
2482{
2483    const REPR_ID: &str = "<none>";
2484
2485    type Manager<'id> = M<'id, NC, ET, TMC, RC, MDC, TERMINALS>;
2486
2487    type ManagerRef = ManagerRef<NC, ET, TMC, RC, MDC, TERMINALS>;
2488
2489    #[inline]
2490    fn from_edge<'id>(manager: &Self::Manager<'id>, edge: EdgeOfFunc<'id, Self>) -> Self {
2491        let ptr: *const Self::Manager<'static> = std::ptr::from_ref(manager).cast();
2492        // SAFETY:
2493        // - We just changed "identifier" lifetimes.
2494        // - The pointer was obtained via `Arc::into_raw()`, and since we have a
2495        //   `&Manager` reference, the counter is at least 1.
2496        let store = ManagerRef(unsafe {
2497            let manager = &*ptr;
2498            Arc::increment_strong_count(manager.store);
2499            Arc::from_raw(manager.store)
2500        });
2501        // Avoid transmuting `edge` for changing lifetimes
2502        let id = edge.0;
2503        std::mem::forget(edge);
2504        Self {
2505            store,
2506            edge: ManuallyDrop::new(Edge(id, PhantomData)),
2507        }
2508    }
2509
2510    #[inline]
2511    fn as_edge<'id>(&self, manager: &Self::Manager<'id>) -> &EdgeOfFunc<'id, Self> {
2512        assert!(
2513            std::ptr::eq(self.store.0.manager.data_ptr().cast(), manager),
2514            "This function does not belong to `manager`"
2515        );
2516
2517        let ptr: *const EdgeOfFunc<'id, Self> = std::ptr::from_ref(&*self.edge).cast();
2518        // SAFETY: Just changing lifetimes; we checked that `self.edge` belongs
2519        // to `manager` above.
2520        unsafe { &*ptr }
2521    }
2522
2523    #[inline]
2524    fn into_edge<'id>(self, manager: &Self::Manager<'id>) -> EdgeOfFunc<'id, Self> {
2525        assert!(
2526            std::ptr::eq(self.store.0.manager.data_ptr().cast(), manager),
2527            "This function does not belong to `manager`"
2528        );
2529        // We only want to drop the store ref (but `Function` implements `Drop`,
2530        // so we cannot destruct `self`).
2531        let mut this = ManuallyDrop::new(self);
2532        let edge = Edge(this.edge.0, PhantomData);
2533        // SAFETY: we forget `self`
2534        unsafe { std::ptr::drop_in_place(&mut this.store) };
2535        edge
2536    }
2537
2538    #[inline]
2539    fn manager_ref(&self) -> Self::ManagerRef {
2540        self.store.clone()
2541    }
2542
2543    fn with_manager_shared<F, T>(&self, f: F) -> T
2544    where
2545        F: for<'id> FnOnce(&Self::Manager<'id>, &EdgeOfFunc<'id, Self>) -> T,
2546    {
2547        let local_guard = self.store.0.prepare_local_state();
2548        let res = f(&self.store.0.manager.shared(), &self.edge);
2549        drop(local_guard);
2550        res
2551    }
2552
2553    fn with_manager_exclusive<F, T>(&self, f: F) -> T
2554    where
2555        F: for<'id> FnOnce(&mut Self::Manager<'id>, &EdgeOfFunc<'id, Self>) -> T,
2556    {
2557        let local_guard = self.store.0.prepare_local_state();
2558        let res = f(&mut self.store.0.manager.exclusive(), &self.edge);
2559        drop(local_guard);
2560        res
2561    }
2562}
2563
2564// === NodeSet =================================================================
2565
2566/// Node set implementation using a [bit set][FixedBitSet]
2567///
2568/// Since nodes are stored in an array, we can use a single bit vector. This
2569/// reduces space consumption dramatically and increases the performance.
2570#[derive(Default, Clone, PartialEq, Eq)]
2571pub struct NodeSet {
2572    len: usize,
2573    data: FixedBitSet,
2574}
2575
2576impl<'id, N: NodeBase, ET: Tag> oxidd_core::util::NodeSet<Edge<'id, N, ET>> for NodeSet {
2577    #[inline(always)]
2578    fn len(&self) -> usize {
2579        self.len
2580    }
2581
2582    #[inline]
2583    fn insert(&mut self, edge: &Edge<'id, N, ET>) -> bool {
2584        let index = edge.node_id();
2585        if index < self.data.len() && self.data.contains(index) {
2586            return false;
2587        }
2588        self.data.grow_and_insert(index);
2589        self.len += 1;
2590        true
2591    }
2592
2593    #[inline]
2594    fn contains(&self, edge: &Edge<'id, N, ET>) -> bool {
2595        let index = edge.node_id();
2596        if index < self.data.len() {
2597            self.data.contains(index)
2598        } else {
2599            false
2600        }
2601    }
2602
2603    #[inline]
2604    fn remove(&mut self, edge: &Edge<'id, N, ET>) -> bool {
2605        let index = edge.node_id();
2606        if index < self.data.len() && self.data.contains(index) {
2607            self.len -= 1;
2608            self.data.remove(index);
2609            true
2610        } else {
2611            false
2612        }
2613    }
2614}
2615
2616// === Additional Trait Implementations ========================================
2617
2618impl<
2619    'id,
2620    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
2621    ET: Tag,
2622    TM: TerminalManager<'id, N, ET, TERMINALS>,
2623    R: DiagramRules<Edge<'id, N, ET>, N, TM::TerminalNode>,
2624    MD: oxidd_core::HasApplyCache<Self, O> + ManagerEventSubscriber<Self> + DropWith<Edge<'id, N, ET>>,
2625    O: Copy,
2626    const TERMINALS: usize,
2627> oxidd_core::HasApplyCache<Self, O> for Manager<'id, N, ET, TM, R, MD, TERMINALS>
2628{
2629    type ApplyCache = MD::ApplyCache;
2630
2631    #[inline]
2632    fn apply_cache(&self) -> &Self::ApplyCache {
2633        self.data.apply_cache()
2634    }
2635
2636    #[inline]
2637    fn apply_cache_mut(&mut self) -> &mut Self::ApplyCache {
2638        self.data.apply_cache_mut()
2639    }
2640}
2641
2642impl<'id, T, N, ET, TM, R, MD, const TERMINALS: usize> AsRef<T>
2643    for Manager<'id, N, ET, TM, R, MD, TERMINALS>
2644where
2645    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
2646    ET: Tag,
2647    TM: TerminalManager<'id, N, ET, TERMINALS>,
2648    MD: DropWith<Edge<'id, N, ET>> + AsRef<T>,
2649{
2650    #[inline(always)]
2651    fn as_ref(&self) -> &T {
2652        self.data.as_ref()
2653    }
2654}
2655
2656impl<'id, T, N, ET, TM, R, MD, const TERMINALS: usize> AsMut<T>
2657    for Manager<'id, N, ET, TM, R, MD, TERMINALS>
2658where
2659    N: NodeBase + InnerNode<Edge<'id, N, ET>>,
2660    ET: Tag,
2661    TM: TerminalManager<'id, N, ET, TERMINALS>,
2662    MD: DropWith<Edge<'id, N, ET>> + AsMut<T>,
2663{
2664    #[inline(always)]
2665    fn as_mut(&mut self) -> &mut T {
2666        self.data.as_mut()
2667    }
2668}