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}