E-graph
An e-graph, short for equality graph, is a data structure in computer science that compactly represents a set of expressions (terms) and a congruence relation over them, where equivalent expressions are grouped into equivalence classes to enable efficient reasoning about equalities.[1] It consists of e-nodes, which are canonical representations of function applications with children pointing to e-classes, and e-classes, which partition e-nodes based on provable equivalence under a set of rewrite rules.[1] This structure maintains a congruence invariant, ensuring that if two subexpressions are equivalent, the enclosing expressions are merged accordingly, allowing for compact storage of exponentially many equivalent program variants.[2]
E-graphs originated from theorem provers like Simplify, where they were used to represent equalities in satisfiability modulo theories, but gained prominence in programming language research through the introduction of equality saturation in 2009.[2] Equality saturation leverages e-graphs to apply rewrite rules exhaustively and order-independently, addressing the phase-ordering problem in compiler optimizations by saturating the graph with equalities before extracting the best representation using cost-based heuristics.[2] In this approach, an initial expression is added to the e-graph, rules are repeatedly matched and applied until no new equalities are found (saturation), and then the minimal-cost expression is extracted, enabling global optimization without destructive transformations.[1]
Subsequent advancements, such as the egg library introduced in 2020, have optimized e-graph implementations for speed and extensibility by incorporating techniques like rebuilding for efficient invariant maintenance and e-class analyses for domain-specific computations like constant folding.[1] These improvements have broadened e-graph applications beyond compilers to include superoptimization, where they generate optimal code snippets; symbolic computation in tools like Herbie for floating-point accuracy; and even hardware design verification.[1] Today, e-graphs power libraries in languages like Rust, Julia, and Haskell, facilitating automated program synthesis and equivalence checking across diverse domains.[1]
Fundamentals
Definition
An E-graph, short for equality graph, is a data structure that compactly represents a set of equivalent expressions over uninterpreted function symbols and constants, maintaining a congruence relation among them.[3] It consists of e-nodes, which are canonical representations of function applications pointing to child e-classes, and e-classes, which partition equivalent e-nodes into equivalence classes.[4] Terms in an E-graph are modeled as trees, where leaves are constants or variables and internal nodes are applications of uninterpreted functions; each such application forms an e-node whose children belong to specific e-classes.[4]
To enforce equivalence, E-graphs use a union-find data structure to dynamically merge e-classes when new equalities are asserted, ensuring that equivalent terms share the same canonical representative.[4] Additionally, hash-consing is employed to create a canonical representation for e-nodes by mapping unique combinations of function symbols and child e-classes to a single instance, preventing duplication and enabling efficient lookups.[4] Key invariants include congruence closure, which guarantees that if two subterms are equivalent, then any enclosing term applications are also considered equivalent, and the persistence of all established equivalences throughout operations on the structure.[3]
For example, consider arithmetic expressions where addition is commutative: inserting both (add x y) and (add y x) into an E-graph would merge their root e-classes via union-find, as the subterms x and y are equivalent to y and x respectively under congruence closure, resulting in a single e-class representing both forms.[4] This structure supports techniques like equality saturation, where rewrites are applied exhaustively to explore equivalent expressions.[4]
History
The concept of e-graphs originated in the late 1970s within the domain of automated theorem proving, particularly through foundational work on congruence closure algorithms, such as the 1980 paper by C. G. Nelson and D. C. Oppen.[3] These structures were initially employed to efficiently represent equivalence relations over terms in first-order logic, enabling scalable reasoning about equalities in logical formulas.[5]
A pivotal development occurred in 2007 with the introduction of efficient E-matching techniques in the Z3 SMT solver by Leonardo de Moura and Nikolaj Bjørner.[6] Their work formalized E-graphs as a core component for incremental pattern matching and congruence closure, allowing Z3 to handle complex quantifier instantiations and theory-specific equalities with improved performance.[6][5] This integration marked the first widespread use of E-graphs in production software verification tools, influencing subsequent SMT solver designs.[7]
In the 2010s, E-graphs were further formalized and extended beyond SMT solvers for applications in term rewriting and program optimization. A key influence came from abstract congruence matching, as explored by Michael Stepp, Ross Tate, and Sorin Lerner in their 2011 work on equality-based translation validation for compilers like LLVM. This approach leveraged E-graphs to verify semantic equivalence between source and target code representations, bridging automated verification with compiler correctness.
The egg library, developed by Max Willsey and colleagues and introduced in 2020, provided an efficient implementation of equality saturation (a technique originally introduced in 2009), addressing scalability limitations of prior E-graph implementations by introducing specialized analysis and reconstruction algorithms, enabling its application to program synthesis and optimization tasks that were previously infeasible.[8]
Post-2020 developments expanded E-graphs into production compilers and diverse optimization domains. Integration efforts in LLVM-based systems, such as the eqsat dialect for MLIR in 2025, allowed non-destructive rewriting passes using equality saturation directly within the compiler infrastructure. Concurrently, 2022–2023 research incorporated acyclic E-graphs into compilers like Cranelift (part of the LLVM ecosystem) to resolve pass-ordering issues and enhance mid-end optimizations.[9] Key milestones included relational E-matching in 2022, which improved efficiency by modeling pattern searches as relational joins for worst-case optimal query plans, and Wasm-mutate in the same year, which used E-graphs to generate semantically equivalent WebAssembly variants for compiler fuzzing and binary diversification.[10][11]
By 2025, E-graphs saw innovative applications in diagram-based theorem proving at the Topos Institute, where they were adapted to maintain congruence relations over diagrammatic representations for algebraic proofs.[12] Additionally, educational and practical advancements, such as technology mapping techniques taught in Cornell's CS 6120 course, demonstrated E-graphs' utility in synthesizing optimal circuit implementations from expression equivalences.[13] These evolutions underscore E-graphs' transition from niche verification tools to versatile structures in optimization and synthesis.
Structure and Operations
E-classes and Invariants
In an e-graph, e-classes form the core partitioning mechanism, grouping terms that are equivalent under the defined rewrite rules into disjoint sets. Each e-class encapsulates multiple e-nodes, serving as compact representations of equivalent expressions while avoiding redundant storage of identical terms. This structure allows the e-graph to efficiently represent an exponentially large set of equivalent programs as a polynomial-sized data structure.[1]
An e-node within an e-class specifies a function symbol, such as an operator like addition or multiplication, along with an ordered list of child e-classes that denote its subterms. For instance, an e-node for binary addition might reference two child e-classes representing the operands. To maintain a canonical form, e-nodes are uniquely identified through hash-consing, ensuring that no two e-nodes with the same operator and equivalent children coexist in the same e-class, which prevents duplication and facilitates efficient lookups.[1][14]
E-graphs uphold several key invariants to ensure correctness and usability. Acyclicity prohibits cycles in the graph to prevent the representation of infinite terms, typically enforcing a directed acyclic graph (DAG) structure for term languages. Completeness guarantees that all equivalences logically implied by the initially added equalities are fully represented across the e-classes. Congruence ensures that equivalence is preserved under contextual substitution: if subterms in child e-classes are equivalent, then the corresponding parent e-nodes must reside in the same e-class.[1][2]
Invariant maintenance relies on a union-find data structure to track e-class equivalences, employing path compression during finds to canonicalize references and union-by-rank to balance the tree for amortized efficiency in merges. Each e-class also tracks parent lists—enumerating e-nodes that reference it as a child—and child references for traversal, enabling systematic checks for new congruence opportunities during updates. In practice, these are often relaxed during incremental additions for performance, with periodic rebuilding to restore full compliance.[1]
For example, consider merging e-classes under a commutative rewrite like x + y \equiv y + x: the e-graph adds an e-node for y + x with children swapped from the original x + y e-node, then unions the parent e-classes, linking all variants without duplication while propagating the change through parent lists to identify further congruent merges. This process upholds the invariants, allowing the e-graph to compactly capture both orders as equivalent without exponential growth.[1][14]
Basic Operations
The add operation in an E-graph inserts a term into the structure by creating an e-node representing the term's operator and its child e-classes, if they do not already exist.[15] To avoid duplicates, the operation employs hash-consing: a hash table checks for an identical e-node (same operator and child e-classes), returning its existing e-class identifier if present; otherwise, a new singleton e-class is created for the e-node and added to the e-graph.[4] This ensures efficient storage and sharing of subterms, as ground terms (constants like variables or literals) are inserted as leaf e-nodes in dedicated e-classes without children.[4]
The merge operation unions two e-classes to enforce equivalence, updating the e-graph to reflect this relation while preserving congruence—the property that equivalent inputs to the same function yield equivalent outputs.[15] It begins by canonicalizing the input e-class identifiers using the underlying union-find structure to locate their representatives. If the representatives differ, one is redirected to the other, effectively merging the e-classes and combining their e-nodes.[4] To maintain congruence, the merge triggers propagation: parents of the merged e-classes are examined, and any e-nodes with now-equivalent children are added to a worklist for rebuilding, where congruent e-nodes are detected via hash-consing and merged iteratively.[4] For efficiency, implementations handle inverse implications during path compression in the union-find find operation, allowing quick retrieval of canonical representatives without repeated traversals.[4]
The following pseudocode illustrates the core merge process, adapted from standard e-graph implementations:
function merge(egraph, class1, class2):
rep1 = find(egraph, class1)
rep2 = find(egraph, class2)
if rep1 == rep2:
return false // already equivalent
// Union: redirect rep1 to rep2
egraph.union_find[rep1] = rep2
// Propagate: add parents to worklist for congruence
for parent in parents_of(rep1):
add_to_worklist(parent)
rebuild(egraph, worklist) // canonicalize and merge congruents
return true
function merge(egraph, class1, class2):
rep1 = find(egraph, class1)
rep2 = find(egraph, class2)
if rep1 == rep2:
return false // already equivalent
// Union: redirect rep1 to rep2
egraph.union_find[rep1] = rep2
// Propagate: add parents to worklist for congruence
for parent in parents_of(rep1):
add_to_worklist(parent)
rebuild(egraph, worklist) // canonicalize and merge congruents
return true
Here, find follows union-find paths with compression to return the representative e-class, and rebuild processes the worklist by recreating e-nodes with canonical children and merging duplicates.[4]
The find operation retrieves the canonical representative e-class identifier for a given term or e-node by following the union-find parent pointers, applying path compression for amortized efficiency.[4] Canonicalization extends this by rebuilding a term from its representative e-class, selecting a canonical e-node (often the first or simplest in some order) and recursively reconstructing children, simulating function application without full evaluation—for instance, applying an operator to child e-classes yields a new e-node in the result e-class.[4] This allows querying equivalences and extracting terms while preserving invariants like congruence.[15]
As an example, consider building an E-graph for arithmetic equalities such as x + y = y + x. First, add the ground terms x and y as singleton e-classes. Adding x + y creates an e-node with children pointing to the x and y e-classes, forming a new e-class. Similarly, adding y + x creates another e-node in a distinct e-class. To enforce commutativity, merge the two e-classes: their representatives are unioned, and propagation examines any parent e-nodes (none initially), but subsequent additions like (x + y) + z would trigger merging with (y + x) + z to maintain congruence. The resulting structure shares the x and y subterms, with find returning the same representative for both addition forms.[4]
E-graphs can be formulated as an augmented union-find data structure, where equivalence classes are managed via union-find components to track equal terms, and each component stores a list of e-nodes representing the expressions in that class. This approach extends the standard union-find to maintain congruence closure, allowing efficient merging of classes when new equalities are added.[4]
In the term DAG view, E-graphs are represented as directed acyclic graphs of terms, where shared subterms across nodes encode equivalences, generalizing conventional term DAGs that typically handle only common subexpression elimination without full equivalence classes. When each e-class contains a single e-node, the structure reduces to a standard term DAG, but in general, multiple e-nodes per class enable richer sharing of equivalent substructures.[4]
The relational formulation models E-graphs using binary relations to capture congruences between terms, treating e-nodes and e-classes as relational tables where matches are computed via joins, as in relational e-matching. This perspective draws on database techniques to optimize pattern matching over the equivalence structure.[16]
Algebraically, an E-graph can be viewed as a quotient structure on the free term algebra modulo the congruence generated by the stored equalities, where e-classes partition terms into equivalence sets under the induced relation.[4]
These formulations support distinct optimizations: the union-find approach enables fast in-place unions for dynamic rewrites, while the term DAG view promotes space efficiency through subterm sharing, reducing redundancy in large expression sets compared to naive tree representations.[4]
For example, consider an E-graph encoding the term (a \times 2) / 2 with the rewrite x \times 2 \equiv x \ll 1. In the term DAG view, this translates to a graph with a shared node for the constant 2 and a node for a, where the multiplication and division operators point to these shared children, and the rewrite adds a left-shift node also sharing the a and 2 nodes, visualizing equivalences without duplicating subterms.
Key Algorithms
E-matching
E-matching is the process of searching an e-graph for all substitutions that map the variables of a given pattern to e-classes such that the instantiated pattern is equivalent to some term represented in the e-graph.[6] This operation is essential for identifying opportunities to apply rewrite rules during equality saturation, where patterns represent the left-hand sides of such rules.[17]
Traditional e-matching algorithms employ bottom-up matching augmented with discrimination trees to efficiently locate potential matches by indexing e-nodes grouped by their function symbols.[6] These trees, often structured as code trees or tries, allow for rapid traversal and filtering of e-graph enodes that could bind to pattern subterms, while an inverted path index prunes irrelevant branches to avoid exhaustive search.[6] To handle backtracking and multiple matches—arising from non-deterministic bindings or commutative operators—an abstract machine with a backtracking stack is used, enabling the enumeration of all valid substitutions without redundant recomputation.[6]
A key challenge in e-matching is mitigating exponential blowup in time complexity, as the problem is NP-hard in general due to the combinatorial explosion of pattern variables and e-graph depth.[6] Optimizations such as caching intermediate match results in the discrimination trees and heuristic variable ordering during backtracking help bound the search space, ensuring practicality for patterns encountered in SMT solvers and program optimization.[6]
More recent advancements introduce a relational formulation of e-matching, which models the search as a Datalog-like conjunctive query over the e-graph's relational representation, reducing the problem to a fixed-point computation via generic semiring joins.[17] This approach achieves worst-case optimality, with time complexity bounded by O(|M| + N \log N) in many practical scenarios, where N is the e-graph size and |M| is the output size of matches, outperforming traditional methods by orders of magnitude on complex patterns.[17] It naturally accommodates multiple patterns and backtracking through query unfolding and join ordering, leveraging database techniques for efficiency.[17]
For example, consider matching the pattern (add ?x ?y) in an arithmetic e-graph containing e-classes for numbers and operations; e-matching would yield substitutions for all pairs of e-classes and where the e-graph entails [a + b], including commutative variants like (add ?y ?x) without duplicating bindings.[17]
In e-graphs, extraction refers to the process of selecting a ground term from an e-class that minimizes a user-defined cost, such as the size or depth of the abstract syntax tree (AST), or a domain-specific metric like the number of instructions in a compiler backend. This step typically follows equality saturation, where the e-graph has been populated with equivalent expressions, and aims to produce an optimal or near-optimal representation for the final output. Cost functions are often local and recursive, computing the total cost of a term based on the costs of its subterms plus a fixed cost for the root operator; for instance, in program optimization, the cost might reflect register pressure or code size.[18][19]
Common algorithms for extraction leverage dynamic programming to traverse the e-graph bottom-up, annotating each e-class with the minimal cost and corresponding e-node selection achievable from its children. For approximation, greedy heuristics select the lowest-cost e-node at each step without backtracking, while A* search can incorporate heuristics like admissible estimates of remaining costs to guide exploration toward optimality. Exact solutions frequently reduce the problem to integer linear programming (ILP), encoding variable assignments for e-node selections subject to acyclicity and coverage constraints, though this scales poorly for large e-graphs.[18][20][21]
The extraction problem is NP-hard in general, even when allowing common subexpression sharing and using simple additive costs, as it encompasses challenges like finding minimum-cost spanning trees in dense graphs. Polynomial-time algorithms exist for restricted cases, such as e-graphs with bounded treewidth—where the graph's "tree-likeness" limits structural complexity—or when costs are linear in the term structure, enabling fixed-parameter tractable solutions via dynamic programming on tree decompositions.[22]
Cost models in practice prioritize metrics that align with application goals; for example, AST size minimizes the number of nodes to reduce parse time, while instruction count in compilers estimates generated machine code length, often incorporating recursive analyses to propagate semantic properties like data types or bounds. Handling recursive costs requires e-class analyses that merge information during saturation, ensuring costs reflect shared substructures without recomputation.[18][23]
As an illustrative example, consider an e-class representing the arithmetic expression equivalent to a \times b + a \times c, which might contain variants like (a \times b) + (a \times c) and a \times (b + c). Using a cost model where each operator (+ or ×) incurs a cost of 1 and leaves cost 0, dynamic programming would evaluate sub-e-classes to select a \times (b + c) as the minimal term with cost 2, rather than the unfolded variant with cost 3.[18]
Recent advancements from 2023 to 2025 have focused on scalable heuristics for extraction in tools like egg and LLVM-integrated systems. In egg, the ILP extractor was enhanced with acyclic constraints to detect and penalize cycles during solving, improving performance on real-world optimizer benchmarks by up to 10×. LLVM's eqsat dialect for MLIR incorporates e-graph rewriting with heuristic extraction tailored to hardware mapping, while broader innovations include e-boost's parallelized adaptive heuristics that warm-start exact solvers for 558× speedups on tensor programs, and SmoothE's differentiable approach for GPU-accelerated optimization under complex, non-linear costs.[20][24][21][23]
Equality Saturation
Equality saturation is a paradigm for program optimization that leverages e-graphs to exhaustively apply rewrite rules until a fixed point is reached, where no further rewrites are possible.[25] Introduced as a solution to the phase-ordering problem in compilers, it represents expressions as sets of equivalent terms within an e-graph, allowing the exploration of multiple optimization paths without committing to a single rewrite sequence early on.[25] This approach ensures that all possible equivalences derivable from the initial term and a set of rules are discovered, enabling the selection of an optimal form post-saturation.[8]
The workflow begins with an initial term inserted into an empty e-graph, forming the starting e-class.[8] Rewrite rules, which are bidirectional (i.e., if A = B, then B = A), are then applied iteratively using e-matching to identify subterms that match rule patterns.[25] Each successful match adds new e-nodes and merges equivalent e-classes, growing the e-graph while canonicalizing representations to prevent exponential term explosion.[8] This process repeats—scanning all rules against the current e-graph—until saturation, where additional iterations yield no changes, at which point extraction selects the best representative from the e-graph.[25] E-matching serves as the core engine for rule application in this loop.[8]
A key benefit of equality saturation is its language-agnostic nature, as it operates on abstract syntax trees or term representations without relying on domain-specific heuristics.[8] It excels at discovering non-local rewrites that traditional linear-pass optimizers might miss, such as algebraic identities spanning multiple subexpressions.[25]
Prominent implementations include the egg library, released in 2021 and written in Rust, which optimizes e-graph operations for speed and extensibility through techniques like analysis-driven e-matching and backpropagation of cost information.[8] Recent extensions from 2022 to 2025 have focused on scalability, such as guided equality saturation, which decomposes complex problems into sequences of smaller saturations to handle larger inputs efficiently.[26]
Consider a simple example of saturating the expression (a + b) \times c using algebraic distribution rules. The initial e-graph contains the term as a single e-node with children + (of a and b) and \times (applied to the sum and c). Applying the rule x + y = y + x (commutativity) adds a symmetric variant, merging the children into the same e-class. Next, the distribution rule (x + y) \times z = x \times z + y \times z matches the structure, introducing new e-nodes for a \times c and b \times c, which are merged under a sum e-class equivalent to the original. Further rules, like associativity x + (y + z) = (x + y) + z, may propagate if additional terms are present, but saturation halts once all derivable forms—such as a \times c + b \times c—are represented without redundancy. Extraction then costs these equivalents to pick the simplest, like the distributed form for code generation.[14]
Despite its power, equality saturation faces challenges from rule explosion, where a large set of rules can cause the e-graph to grow uncontrollably in size and time. This is often mitigated by rule scheduling—prioritizing high-impact rewrites—or partial saturation, which limits iterations or focuses on subgraphs.[26]
Theoretical Aspects
The construction of an E-graph from a set of n equalities can be performed in O(n \log n) time using algorithms based on union-find structures with path compression and union by rank, as originally analyzed for congruence closure.[27] Space usage is O(n) when employing hash-consing to represent terms compactly, avoiding redundant storage of equivalent subexpressions.[8]
E-matching, the process of finding subgraphs matching a given pattern, has worst-case exponential time complexity in the size of the pattern due to the need to explore all possible bindings in backtracking approaches. However, relational variants of e-matching, which model the search as a conjunctive query over a relational representation of the E-graph, achieve polynomial data complexity, such as O(|Q(I)|^{1/2} \cdot N^{m/2}) where N is the number of e-nodes, m is fixed for the pattern (number of relations), and |Q(I)| is the output size; this provides worst-case optimality relative to the output.[16]
Extraction, which selects a minimal-cost representative term from an e-class, is NP-hard in general when costs are assigned to subexpressions and common subexpression sharing is allowed.[28] For restricted cases, such as linear cost functions (where cost depends only on the root operator) or fixed-width patterns (bounded treewidth), extraction can be solved in O(n) time via dynamic programming or fixed-point traversals.
In practice, E-graph operations exhibit amortized efficiency due to the use of invariants like e-class analyses, which prune redundant computations during saturation; however, dense E-graphs with many overlapping equivalences risk exponential blowup in size and matching time. Theoretical bounds from 2008 to 2022 emphasize these worst-case behaviors, while recent 2024 analyses on parameterized complexity show that for linear-matching rules, the overall saturation time is O(N \cdot I \cdot R) where N is the number of e-nodes, I is the number of iterations, and R is the number of rules, improving to sub-quadratic bounds under disjointness parameters.[29] For example, constructing an E-graph via equality saturation with 1000 rewrites on a program of size N \approx 2500 (as in eggcc benchmarks) typically completes in near-linear time relative to N, leveraging these parameterized bounds with I=2 iterations and modest rule counts.[30]
Limitations and Challenges
One major practical limitation of E-graphs is their scalability, particularly the risk of memory explosion due to the proliferation of e-nodes during saturation. As rewrite rules are applied, the e-graph can grow exponentially in size, making it impractical for very large expressions or complex programs.[31] To mitigate this, techniques such as rebuilding—where invariant maintenance like congruence is deferred and recomputed periodically—provide significant speedups, up to 87.85× for congruence closure, while bounded saturation or timeouts prevent unbounded growth.[18] Pruning strategies, including e-class analyses that simplify or discard redundant nodes, further address memory usage in production settings.[32]
Rule engineering poses significant challenges in deploying E-graphs effectively, as developing complete and non-conflicting rewrite sets requires substantial domain expertise. Rules may be subtly incorrect, overlook profitable transformations, or necessitate revalidation when underlying semantics change, such as in numerical domains like floating-point arithmetic where precision and rounding introduce non-trivial equivalences.[31] Incomplete rulesets can lead to suboptimal saturation, exacerbating the phase-ordering problem where the sequence of applications affects outcomes, though equality saturation mitigates this by exploring all possibilities exhaustively.[32]
Debugging E-graphs is complicated by their opaque representations, which compactly encode vast sets of equivalents but obscure the reasoning behind transformations. Inspecting the structure to trace rule applications or identify inefficiencies often requires custom tools, such as interactive visualizers that render e-graphs as graphs with e-classes and e-nodes for easier navigation. Recent developments, including JavaScript-based visualizers using Cytoscape.js, have emerged around 2024 to facilitate this, enabling users to explore and debug large e-graphs more intuitively.[33] E-class analyses also support debugging by verifying properties like constant values during saturation.[18]
Domain-specific issues arise when applying E-graphs beyond pure functional settings, particularly in handling side effects or imperative languages. Rewrites that alter side effects, such as mutable state updates, are harder to express and maintain soundly, as E-graphs assume contextual equivalence without inherent support for binding or mutation. In non-functional languages with loops or conditionals, additional mechanisms like partial evaluation graphs are needed to extend saturation, but these introduce overhead and limit generality.[34][32]
Open problems in E-graphs include efficient incremental updates to handle dynamic changes without full resaturation, which remains underdeveloped and workload-dependent. Recent 2025 research explores integrating machine learning for automated rule discovery, such as guided equality saturation using reinforcement learning to prioritize rewrites and generate domain-specific rules, addressing the manual engineering bottleneck.[35]
A notable example of saturation challenges occurs with cyclic rules that introduce infinite loops, such as repeated applications of identities like x \times 1 \equiv x without proper merging, leading to non-terminating growth despite cycles compactly representing infinite expressions. In such cases, equality saturation may fail to converge, requiring timeouts or depth bounds to halt, as formalized in semantic models allowing infinite E-graphs for non-terminating scenarios.[18][36]
Applications
Compiler and Program Optimization
E-graphs have emerged as a powerful tool in compiler and program optimization, primarily through the application of equality saturation, which builds an e-graph by exhaustively applying rewrite rules to generate equivalent program variants and then extracts the optimal one based on a cost model.[37] This approach enables systematic rewriting of intermediate representations (IRs) for loops, arithmetic expressions, and control flow structures, allowing compilers to explore a vast space of optimizations without manual ordering of passes.[8] In contrast to traditional peephole optimizers, e-graphs facilitate global reasoning over equivalences, leading to more comprehensive transformations.[38]
A prominent example is the egg library, implemented in Rust since 2020, which integrates e-graphs into compiler pipelines for equality saturation-based optimization of IRs.[8] Egg has been adopted for building custom optimizer passes, enabling automatic peephole optimizations and the discovery of novel instruction sequences that outperform hand-written rules.[38] For instance, in LLVM-based compilers, the eqsat dialect for MLIR, introduced in 2025, embeds e-graphs directly into the IR to support non-destructive rewriting passes, improving optimization of arithmetic and loop constructs in production workflows.[24] Similarly, Wasm-mutate, developed in 2022, leverages e-graphs to generate semantically equivalent variants of WebAssembly binaries, aiding optimization and fuzzing of compiler outputs for better code diversity and performance.[39]
In technology mapping, recent work from Cornell University in 2025 applies e-graphs to map Boolean functions to gate networks, particularly for FPGA LUT remapping, by saturating equivalences and extracting minimal-cost implementations that surpass traditional heuristic methods.[13][40] This technique discovers efficient circuit layouts through exhaustive equivalence exploration, reducing area and delay in hardware synthesis.[13]
Benefits of e-graphs in this domain include automated peephole optimization across larger scopes and the synthesis of previously unknown instruction sequences, as demonstrated in egg's applications where rewrites yield up to 2x speedups in numerical kernels by simplifying expressions like polynomial evaluations.[8][41] Case studies highlight integrations with MLIR for dialect-specific rewriting, achieving 15-30% performance gains in tensor computations through targeted arithmetic optimizations.[24] In numerical code, e-graph-based tools have produced substantial speedups in symbolic-numeric simulations by fusing operations and eliminating redundancies.[41]
Recent extensions in 2025 focus on GPU kernel optimization, with differentiable e-graph extraction methods like SmoothE enabling gradient-based cost modeling for parallel code generation, resulting in optimized kernels that reduce execution time by leveraging hardware-specific equivalences.[42]
Automated Theorem Proving and SMT Solvers
E-graphs play a central role in satisfiability modulo theories (SMT) solvers for handling congruence closure in the theory of equalities with uninterpreted functions (EUF). In this context, they efficiently represent and propagate equalities among terms, enabling the solver to maintain a compact structure of equivalent expressions. The Z3 SMT solver, introduced in 2008, utilizes E-graphs as its core data structure for congruence closure, where equalities from the SAT solver are propagated to infer additional congruences.[43] Similarly, the CVC4 solver employs E-graph-based procedures to compute congruence closure, supporting decision procedures for EUF logics.[44] This representation allows SMT solvers to manage uninterpreted functions by treating them as opaque symbols while enforcing structural congruence.
Beyond basic EUF, E-graphs facilitate advanced equality reasoning in SMT solvers, particularly during quantifier instantiation and lemma learning. Quantifier instantiation relies on E-matching techniques that operate over the E-graph to identify relevant triggers and generate ground instances, avoiding redundant computations.[6] In Z3, this E-matching process dynamically modifies the E-graph during backtracking search, enabling new instantiations based on updated congruences.[6] For lemma learning, E-graphs maintain equivalences that inform conflict-driven clause generation, ensuring that learned clauses respect the current set of equalities. This integration enhances the solver's ability to handle quantified formulas and theory-specific lemmas efficiently.
Recent extensions of E-graphs in automated theorem proving include diagrammatic approaches for visual equation manipulation, as explored by the Topos Institute in 2025. These methods model equations as e-diagrams—hypergraph-like structures where rewrites correspond to visual transformations—leveraging E-graphs to prove equivalences in categorical settings.[12][45] The benefits of E-graphs in these domains include scalable handling of large term sets through canonicalization and union-find structures, alongside tight integration with SAT solvers for hybrid decision procedures.[46] In verification case studies, E-graphs serve as backends for equivalence oracles in tools like egg, supporting hardware verification by generating proofs of semantic equivalence via rewrite sequences. SMT solvers with E-graph cores, such as Z3, are routinely integrated into interactive theorem provers like Coq and Isabelle for automated verification of logical properties.[47]
Emerging 2024-2025 research advances hybrid symbolic-numeric solving with E-graphs, combining discrete rewriting with continuous optimization. For instance, differentiable E-graph extraction enables gradient-based refinement of equivalences, aiding tasks like expression optimization in scientific computing.[48] These developments extend E-graphs' utility in theorem proving by bridging symbolic reasoning with numerical methods, improving scalability for complex satisfiability problems.[32]
Other Domains
E-graphs have been applied to program synthesis by leveraging saturated e-graphs to generate equivalent code snippets that satisfy specifications, enabling efficient search over large program spaces. Tools like egglog, introduced around 2023, extend equality saturation to support synthesis tasks by combining e-graph rewriting with datalog-style querying for relational reasoning over programs.[49] For instance, egglog facilitates the construction of synthesizers that enumerate functionally equivalent implementations, improving scalability for tasks such as superoptimization.[50]
In abstract interpretation and static analysis, e-graphs represent sets of equivalent values to approximate program behaviors, aiding in bug detection by capturing relational properties that traditional lattices might miss. A 2022 framework integrates e-graphs with abstract interpretation domains, where e-classes model abstract values and rewrites propagate equivalences across control flow, enhancing precision in tools like those for pointer analysis or taint tracking.[51] This combination allows for bidirectional refinement: abstract domains inform e-graph saturation, while e-graphs provide over-approximations of equality for scalable analysis.[52]
For fuzzing and testing, e-graphs enable equivalence-based mutation to generate semantically identical variants, supporting differential testing and compiler validation. The Wasm-mutate tool, developed in 2022, constructs e-graphs from WebAssembly binaries via data-flow analysis to produce diverse yet equivalent mutations, which have uncovered bugs in multiple Wasm compilers by amplifying subtle differences in optimization paths.[11] This approach ensures mutations preserve behavior while exploring edge cases, outperforming random mutation in coverage for resource-constrained environments.[53]
Beyond these, e-graphs contribute to machine learning model optimization by extracting optimal subgraphs under complex, differentiable cost models. In 2025, SmoothE introduced a probabilistic extraction algorithm that optimizes e-graphs on GPUs for ML compute graphs, reducing latency in frameworks like TensorFlow by rewriting tensor operations while respecting hardware constraints.[42] Similarly, equality saturation verifies semantic equivalence in large-scale models, enabling safe parallelism and fusion optimizations in distributed training pipelines.[54] In database systems, e-graphs power query optimization through rewrite rule application; for example, the egg library has been used to build SQL optimizers that enumerate equivalent plans via saturation, improving join ordering and subquery elimination in engines like those based on Cascades frameworks.[55] Recent work applies learned rewriting on e-graphs to generate optimal SQL execution plans, reducing query latency by up to 50% on TPC-H benchmarks.[56]
Case studies illustrate these applications in practice. The egg library, originating from 2020 research, has been adapted to Haskell via the hegg package (released in 2022 and updated through 2025), enabling equality saturation for functional program synthesis and optimization in a pure functional setting without external dependencies.[57] In scientific computing, e-graphs support algebraic simplification of symbolic expressions as of 2025, such as in computer algebra systems for optimizing numerical simulations by equating variant forms of differential equations, though adoption remains experimental.[58]
Looking ahead, e-graphs show promise for integration with quantum computing simulations, where equivalence reasoning could optimize circuit representations in graph-based models, though current efforts focus on preliminary extensions like hypergraphs for monoidal categories underlying quantum protocols.[59]