cranelift_egraph/
lib.rs

1//! # ægraph (aegraph, or acyclic e-graph) implementation.
2//!
3//! An aegraph is a form of e-graph. We will first describe the
4//! e-graph, then the aegraph as a slightly less powerful but highly
5//! optimized variant of it.
6//!
7//! The main goal of this library is to be explicitly memory-efficient
8//! and light on allocations. We need to be as fast and as small as
9//! possible in order to minimize impact on compile time in a
10//! production compiler.
11//!
12//! ## The e-graph
13//!
14//! An e-graph, or equivalence graph, is a kind of node-based
15//! intermediate representation (IR) data structure that consists of
16//! *eclasses* and *enodes*. An eclass contains one or more enodes;
17//! semantically an eclass is like a value, and an enode is one way to
18//! compute that value. If several enodes are in one eclass, the data
19//! structure is asserting that any of these enodes, if evaluated,
20//! would produce the value.
21//!
22//! An e-graph also contains a deduplicating hash-map of nodes, so if
23//! the user creates the same e-node more than once, they get the same
24//! e-class ID.
25//!
26//! In the usual use-case, an e-graph is used to build a sea-of-nodes
27//! IR for a function body or other expression-based code, and then
28//! *rewrite rules* are applied to the e-graph. Each rewrite
29//! potentially introduces a new e-node that is equivalent to an
30//! existing e-node, and then unions the two e-nodes' classes
31//! together.
32//!
33//! In the trivial case this results in an e-class containing a series
34//! of e-nodes that are newly added -- all known forms of an
35//! expression -- but Note how if a rewrite rule rewrites into an
36//! existing e-node (discovered via deduplication), rewriting can
37//! result in unioning of two e-classes that have existed for some
38//! time.
39//!
40//! An e-graph's enodes refer to *classes* for their arguments, rather
41//! than other nodes directly. This is key to the ability of an
42//! e-graph to canonicalize: when two e-classes that are already used
43//! as arguments by other e-nodes are unioned, all e-nodes that refer
44//! to those e-classes are themselves re-canonicalized. This can
45//! result in "cascading" unioning of eclasses, in a process that
46//! discovers the transitive implications of all individual
47//! equalities. This process is known as "equality saturation".
48//!
49//! ## The acyclic e-graph (aegraph)
50//!
51//! An e-graph is powerful, but it can also be expensive to build and
52//! saturate: there are often many different forms an expression can
53//! take (because many different rewrites are possible), and cascading
54//! canonicalization requires heavyweight data structure bookkeeping
55//! that is expensive to maintain.
56//!
57//! This crate introduces the aegraph: an acyclic e-graph. This data
58//! structure stores an e-class as an *immutable persistent data
59//! structure*. An id can refer to some *level* of an eclass: a
60//! snapshot of the nodes in the eclass at one point in time. The
61//! nodes referred to by this id never change, though the eclass may
62//! grow later.
63//!
64//! A *union* is also an operation that creates a new eclass id: the
65//! original eclass IDs refer to the original eclass contents, while
66//! the id resulting from the `union()` operation refers to an eclass
67//! that has all nodes.
68//!
69//! In order to allow for adequate canonicalization, an enode normally
70//! stores the *latest* eclass id for each argument, but computes
71//! hashes and equality using a *canonical* eclass id. We define such
72//! a canonical id with a union-find data structure, just as for a
73//! traditional e-graph. It is normally the lowest id referring to
74//! part of the eclass.
75//!
76//! The persistent/immutable nature of this data structure yields one
77//! extremely important property: it is acyclic! This simplifies
78//! operation greatly:
79//!
80//! - When "elaborating" out of the e-graph back to linearized code,
81//!   so that we can generate machine code, we do not need to break
82//!   cycles. A given enode cannot indirectly refer back to itself.
83//!
84//! - When applying rewrite rules, the nodes visible from a given id
85//!   for an eclass never change. This means that we only need to
86//!   apply rewrite rules at that node id *once*.
87//!
88//! ## Data Structure and Example
89//!
90//! Each eclass id refers to a table entry ("eclass node", which is
91//! different than an "enode") that can be one of:
92//!
93//! - A single enode;
94//! - An enode and an earlier eclass id it is appended to (a "child"
95//!   eclass node);
96//! - A "union node" with two earlier eclass ids.
97//!
98//! Building the aegraph consists solely of adding new entries to the
99//! end of this table of eclass nodes. An enode referenced from any
100//! given eclass node can only refer to earlier eclass ids.
101//!
102//! For example, consider the following eclass table:
103//!
104//! ```plain
105//!
106//!    eclass/enode table
107//!
108//!     eclass1    iconst(1)
109//!     eclass2    blockparam(block0, 0)
110//!     eclass3    iadd(eclass1, eclass2)
111//! ```
112//!
113//! This represents the expression `iadd(blockparam(block0, 0),
114//! iconst(1))` (as the sole enode for eclass3).
115//!
116//! Now, say that as we further build the function body, we add
117//! another enode `iadd(eclass3, iconst(1))`. The `iconst(1)` will be
118//! deduplicated to `eclass1`, and the toplevel `iadd` will become its
119//! own new eclass (`eclass4`).
120//!
121//! ```plain
122//!     eclass4    iadd(eclass3, eclass1)
123//! ```
124//!
125//! Now we apply our body of rewrite rules, and these results can
126//! combine `x + 1 + 1` into `x + 2`; so we get:
127//!
128//! ```plain
129//!     eclass5    iconst(2)
130//!     eclass6    union(iadd(eclass2, eclass5), eclass4)
131//! ```
132//!
133//! Note that we added the nodes for the new expression, and then we
134//! union'd it with the earlier `eclass4`. Logically this represents a
135//! single eclass that contains two nodes -- the `x + 1 + 1` and `x +
136//! 2` representations -- and the *latest* id for the eclass,
137//! `eclass6`, can reach all nodes in the eclass (here the node stored
138//! in `eclass6` and the earlier one in `elcass4`).
139//!
140//! ## aegraph vs. egraph
141//!
142//! Where does an aegraph fall short of an e-graph -- or in other
143//! words, why maintain the data structures to allow for full
144//! (re)canonicalization at all, with e.g. parent pointers to
145//! recursively update parents?
146//!
147//! This question deserves further study, but right now, it appears
148//! that the difference is limited to a case like the following:
149//!
150//! - expression E1 is interned into the aegraph.
151//! - expression E2 is interned into the aegraph. It uses E1 as an
152//!   argument to one or more operators, and so refers to the
153//!   (currently) latest id for E1.
154//! - expression E3 is interned into the aegraph. A rewrite rule fires
155//!   that unions E3 with E1.
156//!
157//! In an e-graph, the last action would trigger a re-canonicalization
158//! of all "parents" (users) of E1; so E2 would be re-canonicalized
159//! using an id that represents the union of E1 and E3. At
160//! code-generation time, E2 could choose to use a value computed by
161//! either E1's or E3's operator. In an aegraph, this is not the case:
162//! E2's e-class and e-nodes are immutable once created, so E2 refers
163//! only to E1's representation of the value (a "slice" of the whole
164//! e-class).
165//!
166//! While at first this sounds quite limiting, there actually appears
167//! to be a nice mutually-beneficial interaction with the immediate
168//! application of rewrite rules: by applying all rewrites we know
169//! about right when E1 is interned, E2 can refer to the best version
170//! when it is created. The above scenario only leads to a missed
171//! optimization if:
172//!
173//! - a rewrite rule exists from E3 to E1, but not E1 to E3; and
174//! - E3 is *cheaper* than E1.
175//!
176//! Or in other words, this only matters if there is a rewrite rule
177//! that rewrites into a more expensive direction. This is unlikely
178//! for the sorts of rewrite rules we plan to write; it may matter
179//! more if many possible equalities are expressed, such as
180//! associativity, commutativity, etc.
181//!
182//! Note that the above represents the best of our understanding, but
183//! there may be cases we have missed; a more complete examination of
184//! this question would involve building a full equality saturation
185//! loop on top of the (a)egraph in this crate, and testing with many
186//! benchmarks to see if it makes any difference.
187//!
188//! ## Rewrite Rules (FLAX: Fast Localized Aegraph eXpansion)
189//!
190//! The most common use of an e-graph or aegraph is to serve as the IR
191//! for a compiler. In this use-case, we usually wish to transform the
192//! program using a body of rewrite rules that represent valid
193//! transformations (equivalent and hopefully simpler ways of
194//! computing results). An aegraph supports applying rules in a fairly
195//! straightforward way: whenever a new eclass entry is added to the
196//! table, we invoke a toplevel "apply all rewrite rules" entry
197//! point. This entry point creates new nodes as needed, and when
198//! done, unions the rewritten nodes with the original. We thus
199//! *immediately* expand a new value into all of its representations.
200//!
201//! This immediate expansion stands in contrast to a traditional
202//! "equality saturation" e-egraph system, in which it is usually best
203//! to apply rules in batches and then fix up the
204//! canonicalization. This approach was introduced in the `egg`
205//! e-graph engine [^1]. We call our system FLAX (because flax is an
206//! alternative to egg): Fast Localized Aegraph eXpansion.
207//!
208//! The reason that this is possible in an aegraph but not
209//! (efficiently, at least) in a traditional e-graph is that the data
210//! structure nodes are immutable once created: an eclass id will
211//! always refer to a fixed set of enodes. There is no
212//! recanonicalizing of eclass arguments as they union; but also this
213//! is not usually necessary, because args will have already been
214//! processed and eagerly rewritten as well. In other words, eager
215//! rewriting and the immutable data structure mutually allow each
216//! other to be practical; both work together.
217//!
218//! [^1]: M Willsey, C Nandi, Y R Wang, O Flatt, Z Tatlock, P
219//!       Panchekha. "egg: Fast and Flexible Equality Saturation." In
220//!       POPL 2021. <https://dl.acm.org/doi/10.1145/3434304>
221
222use cranelift_entity::PrimaryMap;
223use cranelift_entity::{entity_impl, packed_option::ReservedValue, SecondaryMap};
224use smallvec::{smallvec, SmallVec};
225use std::fmt::Debug;
226use std::hash::Hash;
227use std::marker::PhantomData;
228
229mod bumpvec;
230mod ctxhash;
231mod unionfind;
232
233pub use bumpvec::{BumpArena, BumpSlice, BumpVec};
234pub use ctxhash::{CtxEq, CtxHash, CtxHashMap, Entry};
235pub use unionfind::UnionFind;
236
237/// An eclass ID.
238#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
239pub struct Id(u32);
240entity_impl!(Id, "eclass");
241
242impl Id {
243    pub fn invalid() -> Id {
244        Self::reserved_value()
245    }
246}
247impl std::default::Default for Id {
248    fn default() -> Self {
249        Self::invalid()
250    }
251}
252
253/// A trait implemented by all "languages" (types that can be enodes).
254pub trait Language: CtxEq<Self::Node, Self::Node> + CtxHash<Self::Node> {
255    type Node: Debug;
256    fn children<'a>(&'a self, node: &'a Self::Node) -> &'a [Id];
257    fn children_mut<'a>(&'a mut self, ctx: &'a mut Self::Node) -> &'a mut [Id];
258    fn needs_dedup(&self, node: &Self::Node) -> bool;
259}
260
261/// A trait that allows the aegraph to compute a property of each
262/// node as it is created.
263pub trait Analysis {
264    type L: Language;
265    type Value: Clone + Default;
266    fn for_node(
267        &self,
268        ctx: &Self::L,
269        n: &<Self::L as Language>::Node,
270        values: &SecondaryMap<Id, Self::Value>,
271    ) -> Self::Value;
272    fn meet(&self, ctx: &Self::L, v1: &Self::Value, v2: &Self::Value) -> Self::Value;
273}
274
275/// Conditionally-compiled trace-log macro. (Borrowed from
276/// `cranelift-codegen`; it's not worth factoring out a common
277/// subcrate for this.)
278#[macro_export]
279macro_rules! trace {
280    ($($tt:tt)*) => {
281        if cfg!(feature = "trace-log") {
282            ::log::trace!($($tt)*);
283        }
284    };
285}
286
287/// An egraph.
288pub struct EGraph<L: Language, A: Analysis<L = L>> {
289    /// Node-allocation arena.
290    pub nodes: Vec<L::Node>,
291    /// Hash-consing map from Nodes to eclass IDs.
292    node_map: CtxHashMap<NodeKey, Id>,
293    /// Eclass definitions. Each eclass consists of an enode, and
294    /// child pointer to the rest of the eclass.
295    pub classes: PrimaryMap<Id, EClass>,
296    /// Union-find for canonical ID generation. This lets us name an
297    /// eclass with a canonical ID that is the same for all
298    /// generations of the class.
299    pub unionfind: UnionFind,
300    /// Analysis and per-node state.
301    pub analysis: Option<(A, SecondaryMap<Id, A::Value>)>,
302}
303
304/// A reference to a node.
305#[derive(Clone, Copy, Debug)]
306pub struct NodeKey {
307    index: u32,
308}
309
310impl NodeKey {
311    fn from_node_idx(node_idx: usize) -> NodeKey {
312        NodeKey {
313            index: u32::try_from(node_idx).unwrap(),
314        }
315    }
316
317    /// Get the node for this NodeKey, given the `nodes` from the
318    /// appropriate `EGraph`.
319    pub fn node<'a, N>(&self, nodes: &'a [N]) -> &'a N {
320        &nodes[self.index as usize]
321    }
322
323    fn bits(self) -> u32 {
324        self.index
325    }
326
327    fn from_bits(bits: u32) -> Self {
328        NodeKey { index: bits }
329    }
330}
331
332struct NodeKeyCtx<'a, 'b, L: Language> {
333    nodes: &'a [L::Node],
334    node_ctx: &'b L,
335}
336
337impl<'a, 'b, L: Language> CtxEq<NodeKey, NodeKey> for NodeKeyCtx<'a, 'b, L> {
338    fn ctx_eq(&self, a: &NodeKey, b: &NodeKey, uf: &mut UnionFind) -> bool {
339        let a = a.node(self.nodes);
340        let b = b.node(self.nodes);
341        self.node_ctx.ctx_eq(a, b, uf)
342    }
343}
344
345impl<'a, 'b, L: Language> CtxHash<NodeKey> for NodeKeyCtx<'a, 'b, L> {
346    fn ctx_hash(&self, value: &NodeKey, uf: &mut UnionFind) -> u64 {
347        self.node_ctx.ctx_hash(value.node(self.nodes), uf)
348    }
349}
350
351/// An EClass entry. Contains either a single new enode and a child
352/// eclass (i.e., adds one new enode), or unions two child eclasses
353/// together.
354#[derive(Debug, Clone, Copy)]
355pub struct EClass {
356    // formats:
357    //
358    // 00 | unused  (31 bits)        | NodeKey (31 bits)
359    // 01 | eclass_child   (31 bits) | NodeKey (31 bits)
360    // 10 | eclass_child_1 (31 bits) | eclass_child_id_2 (31 bits)
361    bits: u64,
362}
363
364impl EClass {
365    fn node(node: NodeKey) -> EClass {
366        let node_idx = node.bits() as u64;
367        debug_assert!(node_idx < (1 << 31));
368        EClass {
369            bits: (0b00 << 62) | node_idx,
370        }
371    }
372
373    fn node_and_child(node: NodeKey, eclass_child: Id) -> EClass {
374        let node_idx = node.bits() as u64;
375        debug_assert!(node_idx < (1 << 31));
376        debug_assert!(eclass_child != Id::invalid());
377        let child = eclass_child.0 as u64;
378        debug_assert!(child < (1 << 31));
379        EClass {
380            bits: (0b01 << 62) | (child << 31) | node_idx,
381        }
382    }
383
384    fn union(child1: Id, child2: Id) -> EClass {
385        debug_assert!(child1 != Id::invalid());
386        let child1 = child1.0 as u64;
387        debug_assert!(child1 < (1 << 31));
388
389        debug_assert!(child2 != Id::invalid());
390        let child2 = child2.0 as u64;
391        debug_assert!(child2 < (1 << 31));
392
393        EClass {
394            bits: (0b10 << 62) | (child1 << 31) | child2,
395        }
396    }
397
398    /// Get the node, if any, from a node-only or node-and-child
399    /// eclass.
400    pub fn get_node(&self) -> Option<NodeKey> {
401        self.as_node()
402            .or_else(|| self.as_node_and_child().map(|(node, _)| node))
403    }
404
405    /// Get the first child, if any.
406    pub fn child1(&self) -> Option<Id> {
407        self.as_node_and_child()
408            .map(|(_, p1)| p1)
409            .or(self.as_union().map(|(p1, _)| p1))
410    }
411
412    /// Get the second child, if any.
413    pub fn child2(&self) -> Option<Id> {
414        self.as_union().map(|(_, p2)| p2)
415    }
416
417    /// If this EClass is just a lone enode, return it.
418    pub fn as_node(&self) -> Option<NodeKey> {
419        if (self.bits >> 62) == 0b00 {
420            let node_idx = (self.bits & ((1 << 31) - 1)) as u32;
421            Some(NodeKey::from_bits(node_idx))
422        } else {
423            None
424        }
425    }
426
427    /// If this EClass is one new enode and a child, return the node
428    /// and child ID.
429    pub fn as_node_and_child(&self) -> Option<(NodeKey, Id)> {
430        if (self.bits >> 62) == 0b01 {
431            let node_idx = (self.bits & ((1 << 31) - 1)) as u32;
432            let child = ((self.bits >> 31) & ((1 << 31) - 1)) as u32;
433            Some((NodeKey::from_bits(node_idx), Id::from_bits(child)))
434        } else {
435            None
436        }
437    }
438
439    /// If this EClass is the union variety, return the two child
440    /// EClasses. Both are guaranteed not to be `Id::invalid()`.
441    pub fn as_union(&self) -> Option<(Id, Id)> {
442        if (self.bits >> 62) == 0b10 {
443            let child1 = ((self.bits >> 31) & ((1 << 31) - 1)) as u32;
444            let child2 = (self.bits & ((1 << 31) - 1)) as u32;
445            Some((Id::from_bits(child1), Id::from_bits(child2)))
446        } else {
447            None
448        }
449    }
450}
451
452/// A new or existing `T` when adding to a deduplicated set or data
453/// structure, like an egraph.
454#[derive(Clone, Copy, Debug)]
455pub enum NewOrExisting<T> {
456    New(T),
457    Existing(T),
458}
459
460impl<T> NewOrExisting<T> {
461    /// Get the underlying value.
462    pub fn get(self) -> T {
463        match self {
464            NewOrExisting::New(t) => t,
465            NewOrExisting::Existing(t) => t,
466        }
467    }
468}
469
470impl<L: Language, A: Analysis<L = L>> EGraph<L, A>
471where
472    L::Node: 'static,
473{
474    /// Create a new aegraph.
475    pub fn new(analysis: Option<A>) -> Self {
476        let analysis = analysis.map(|a| (a, SecondaryMap::new()));
477        Self {
478            nodes: vec![],
479            node_map: CtxHashMap::new(),
480            classes: PrimaryMap::new(),
481            unionfind: UnionFind::new(),
482            analysis,
483        }
484    }
485
486    /// Create a new aegraph with the given capacity.
487    pub fn with_capacity(nodes: usize, analysis: Option<A>) -> Self {
488        let analysis = analysis.map(|a| (a, SecondaryMap::with_capacity(nodes)));
489        Self {
490            nodes: Vec::with_capacity(nodes),
491            node_map: CtxHashMap::with_capacity(nodes),
492            classes: PrimaryMap::with_capacity(nodes),
493            unionfind: UnionFind::with_capacity(nodes),
494            analysis,
495        }
496    }
497
498    /// Add a new node.
499    pub fn add(&mut self, node: L::Node, node_ctx: &L) -> NewOrExisting<Id> {
500        // Push the node. We can then build a NodeKey that refers to
501        // it and look for an existing interned copy. If one exists,
502        // we can pop the pushed node and return the existing Id.
503        let node_idx = self.nodes.len();
504        trace!("adding node: {:?}", node);
505        let needs_dedup = node_ctx.needs_dedup(&node);
506        self.nodes.push(node);
507
508        let key = NodeKey::from_node_idx(node_idx);
509        if needs_dedup {
510            let ctx = NodeKeyCtx {
511                nodes: &self.nodes[..],
512                node_ctx,
513            };
514
515            match self.node_map.entry(key, &ctx, &mut self.unionfind) {
516                Entry::Occupied(o) => {
517                    let eclass_id = *o.get();
518                    self.nodes.pop();
519                    trace!(" -> existing id {}", eclass_id);
520                    NewOrExisting::Existing(eclass_id)
521                }
522                Entry::Vacant(v) => {
523                    // We're creating a new eclass now.
524                    let eclass_id = self.classes.push(EClass::node(key));
525                    trace!(" -> new node and eclass: {}", eclass_id);
526                    self.unionfind.add(eclass_id);
527
528                    // Add to interning map with a NodeKey referring to the eclass.
529                    v.insert(eclass_id);
530
531                    // Update analysis.
532                    let node_ctx = ctx.node_ctx;
533                    self.update_analysis_new(node_ctx, eclass_id, key);
534
535                    NewOrExisting::New(eclass_id)
536                }
537            }
538        } else {
539            let eclass_id = self.classes.push(EClass::node(key));
540            self.unionfind.add(eclass_id);
541            NewOrExisting::New(eclass_id)
542        }
543    }
544
545    /// Merge one eclass into another, maintaining the acyclic
546    /// property (args must have lower eclass Ids than the eclass
547    /// containing the node with those args). Returns the Id of the
548    /// merged eclass.
549    pub fn union(&mut self, ctx: &L, a: Id, b: Id) -> Id {
550        assert_ne!(a, Id::invalid());
551        assert_ne!(b, Id::invalid());
552        let (a, b) = (std::cmp::max(a, b), std::cmp::min(a, b));
553        trace!("union: id {} and id {}", a, b);
554        if a == b {
555            trace!(" -> no-op");
556            return a;
557        }
558
559        self.unionfind.union(a, b);
560
561        // If the younger eclass has no child, we can link it
562        // directly and return that eclass. Otherwise, we create a new
563        // union eclass.
564        if let Some(node) = self.classes[a].as_node() {
565            trace!(
566                " -> id {} is one-node eclass; making into node-and-child with id {}",
567                a,
568                b
569            );
570            self.classes[a] = EClass::node_and_child(node, b);
571            self.update_analysis_union(ctx, a, a, b);
572            return a;
573        }
574
575        let u = self.classes.push(EClass::union(a, b));
576        self.unionfind.add(u);
577        self.unionfind.union(u, b);
578        trace!(" -> union id {} and id {} into id {}", a, b, u);
579        self.update_analysis_union(ctx, u, a, b);
580        u
581    }
582
583    /// Get the canonical ID for an eclass. This may be an older
584    /// generation, so will not be able to see all enodes in the
585    /// eclass; but it will allow us to unambiguously refer to an
586    /// eclass, even across merging.
587    pub fn canonical_id_mut(&mut self, eclass: Id) -> Id {
588        self.unionfind.find_and_update(eclass)
589    }
590
591    /// Get the canonical ID for an eclass. This may be an older
592    /// generation, so will not be able to see all enodes in the
593    /// eclass; but it will allow us to unambiguously refer to an
594    /// eclass, even across merging.
595    pub fn canonical_id(&self, eclass: Id) -> Id {
596        self.unionfind.find(eclass)
597    }
598
599    /// Get the enodes for a given eclass.
600    pub fn enodes(&self, eclass: Id) -> NodeIter<L, A> {
601        NodeIter {
602            stack: smallvec![eclass],
603            _phantom1: PhantomData,
604            _phantom2: PhantomData,
605        }
606    }
607
608    /// Update analysis for a given eclass node (new-enode case).
609    fn update_analysis_new(&mut self, ctx: &L, eclass: Id, node: NodeKey) {
610        if let Some((analysis, state)) = self.analysis.as_mut() {
611            let node = node.node(&self.nodes);
612            state[eclass] = analysis.for_node(ctx, node, state);
613        }
614    }
615
616    /// Update analysis for a given eclass node (union case).
617    fn update_analysis_union(&mut self, ctx: &L, eclass: Id, a: Id, b: Id) {
618        if let Some((analysis, state)) = self.analysis.as_mut() {
619            let a = &state[a];
620            let b = &state[b];
621            state[eclass] = analysis.meet(ctx, a, b);
622        }
623    }
624
625    /// Get the analysis value for a given eclass. Panics if no analysis is present.
626    pub fn analysis_value(&self, eclass: Id) -> &A::Value {
627        &self.analysis.as_ref().unwrap().1[eclass]
628    }
629}
630
631/// An iterator over all nodes in an eclass.
632///
633/// Because eclasses are immutable once created, this does *not* need
634/// to hold an open borrow on the egraph; it is free to add new nodes,
635/// while our existing Ids will remain valid.
636pub struct NodeIter<L: Language, A: Analysis<L = L>> {
637    stack: SmallVec<[Id; 8]>,
638    _phantom1: PhantomData<L>,
639    _phantom2: PhantomData<A>,
640}
641
642impl<L: Language, A: Analysis<L = L>> NodeIter<L, A> {
643    #[inline(always)]
644    pub fn next<'a>(&mut self, egraph: &'a EGraph<L, A>) -> Option<&'a L::Node> {
645        while let Some(next) = self.stack.pop() {
646            let eclass = egraph.classes[next];
647            if let Some(node) = eclass.as_node() {
648                return Some(&egraph.nodes[node.index as usize]);
649            } else if let Some((node, child)) = eclass.as_node_and_child() {
650                if child != Id::invalid() {
651                    self.stack.push(child);
652                }
653                return Some(&egraph.nodes[node.index as usize]);
654            } else if let Some((child1, child2)) = eclass.as_union() {
655                debug_assert!(child1 != Id::invalid());
656                debug_assert!(child2 != Id::invalid());
657                self.stack.push(child2);
658                self.stack.push(child1);
659                continue;
660            } else {
661                unreachable!("Invalid eclass format");
662            }
663        }
664        None
665    }
666}