|
1 | 1 | # Functional implementation of https://egraphs-good.github.io/ |
2 | 2 | # https://dl.acm.org/doi/10.1145/3434304 |
3 | 3 |
|
4 | | -############################################## |
5 | | -# Interface to implement for custom analyses # |
6 | | -############################################## |
| 4 | +# ============================================================== |
| 5 | +# Interface to implement for custom analyses |
| 6 | +# ============================================================== |
7 | 7 |
|
8 | 8 | """ |
9 | 9 | modify!(eclass::EClass{Analysis}) |
@@ -31,9 +31,9 @@ Given an e-node `n`, `make` should return the corresponding analysis value. |
31 | 31 | """ |
32 | 32 | function make end |
33 | 33 |
|
34 | | -############ |
35 | | -# EClasses # |
36 | | -############ |
| 34 | +# ============================================================== |
| 35 | +# EClasses |
| 36 | +# ============================================================== |
37 | 37 |
|
38 | 38 | """ |
39 | 39 | EClass{D} |
@@ -132,13 +132,17 @@ not necessarily very informative, but you can access the terms of each e-node |
132 | 132 | via `Metatheory.to_expr`. |
133 | 133 |
|
134 | 134 | See the [egg paper](https://dl.acm.org/doi/pdf/10.1145/3434304) |
135 | | -for implementation details. |
| 135 | +for implementation details. Of special notice is the e-graph invariants, |
| 136 | +and when they do or do not hold. One of the main innovations of `egg` was to |
| 137 | +"batch" the maintenance of the e-graph invariants. We use the `clean` field |
| 138 | +on this struct to keep track of whether there is pending work to do in order |
| 139 | +to re-establish the e-graph invariants. |
136 | 140 | """ |
137 | 141 | mutable struct EGraph{ExpressionType,Analysis} |
138 | 142 | """ |
139 | 143 | stores the equality relations over e-class ids |
140 | 144 |
|
141 | | - The `(potentially non-root id) --> (root id)` mapping. |
| 145 | + More specifically, the `(potentially non-root id) --> (root id)` mapping. |
142 | 146 | """ |
143 | 147 | uf::UnionFind |
144 | 148 |
|
@@ -170,12 +174,27 @@ mutable struct EGraph{ExpressionType,Analysis} |
170 | 174 | pending::Vector{Pair{VecExpr,Id}} |
171 | 175 |
|
172 | 176 | """ |
| 177 | + When an e-node is added to an e-graph for the first time, we add analysis data to the |
| 178 | + newly-created e-class by calling [`make`]() on the head of the e-node and the analysis |
| 179 | + data for the arguments to that e-node. However, the analysis data for the arguments to |
| 180 | + that e-node could get updated at some point, as e-classes are merged. |
| 181 | +
|
| 182 | + This is a queue for e-nodes which have had the analysis of some of their arguments |
| 183 | + updated, but have not updated the analysis of their parent e-class yet. |
173 | 184 | """ |
174 | 185 | analysis_pending::UniqueQueue{Pair{VecExpr,Id}} |
| 186 | + |
| 187 | + """ |
| 188 | + The Id of the e-class that we have built this e-graph to simplify. |
| 189 | + """ |
175 | 190 | root::Id |
| 191 | + |
176 | 192 | "a cache mapping signatures (function symbols and their arity) to e-classes that contain e-nodes with that function symbol." |
177 | 193 | classes_by_op::Dict{IdKey,Vector{Id}} |
| 194 | + |
| 195 | + "do we need to do extra work in order to re-establish the e-graph invariants" |
178 | 196 | clean::Bool |
| 197 | + |
179 | 198 | "If we use global buffers we may need to lock. Defaults to false." |
180 | 199 | needslock::Bool |
181 | 200 | lock::ReentrantLock |
@@ -220,6 +239,8 @@ EGraph(e; kwargs...) = EGraph{typeof(e),Nothing}(e; kwargs...) |
220 | 239 | @inline get_constant(@nospecialize(g::EGraph), hash::UInt64) = g.constants[hash] |
221 | 240 | @inline has_constant(@nospecialize(g::EGraph), hash::UInt64)::Bool = haskey(g.constants, hash) |
222 | 241 |
|
| 242 | +# Why does one of these use `get!` and the other use `setindex!`? |
| 243 | + |
223 | 244 | @inline function add_constant!(@nospecialize(g::EGraph), @nospecialize(c))::Id |
224 | 245 | h = hash(c) |
225 | 246 | get!(g.constants, h, c) |
@@ -286,13 +307,17 @@ Returns the canonical e-class id for a given e-class. |
286 | 307 | # new_n |
287 | 308 | # end |
288 | 309 |
|
| 310 | +""" |
| 311 | +Make sure all of the arguments of `n` point to root nodes in the unionfind |
| 312 | +data structure for `g`. |
| 313 | +""" |
289 | 314 | function canonicalize!(g::EGraph, n::VecExpr) |
290 | | - v_isexpr(n) || @goto ret |
291 | | - for i in (VECEXPR_META_LENGTH + 1):length(n) |
292 | | - @inbounds n[i] = find(g, n[i]) |
| 315 | + if v_isexpr(n) |
| 316 | + for i in (VECEXPR_META_LENGTH + 1):length(n) |
| 317 | + @inbounds n[i] = find(g, n[i]) |
| 318 | + end |
| 319 | + v_unset_hash!(n) |
293 | 320 | end |
294 | | - v_unset_hash!(n) |
295 | | - @label ret |
296 | 321 | v_hash!(n) |
297 | 322 | n |
298 | 323 | end |
@@ -391,8 +416,9 @@ function addexpr!(g::EGraph, se)::Id |
391 | 416 | end |
392 | 417 |
|
393 | 418 | """ |
394 | | -Given an [`EGraph`](@ref) and two e-class ids, set |
395 | | -the two e-classes as equal. |
| 419 | +Given an [`EGraph`](@ref) and two e-class ids, merge the two corresponding e-classes. |
| 420 | +
|
| 421 | +This includes merging the analysis data of the e-classes. |
396 | 422 | """ |
397 | 423 | function Base.union!( |
398 | 424 | g::EGraph{ExpressionType,AnalysisType}, |
@@ -435,6 +461,9 @@ function Base.union!( |
435 | 461 | return true |
436 | 462 | end |
437 | 463 |
|
| 464 | +""" |
| 465 | +Returns whether all of `ids...` are the same e-class in `g`. |
| 466 | +""" |
438 | 467 | function in_same_class(g::EGraph, ids::Id...)::Bool |
439 | 468 | nids = length(ids) |
440 | 469 | nids == 1 && return true |
|
563 | 592 |
|
564 | 593 | # Thanks to Max Willsey and Yihong Zhang |
565 | 594 |
|
566 | | - |
| 595 | +""" |
| 596 | +Look up a grounded pattern. |
| 597 | +""" |
567 | 598 | function lookup_pat(g::EGraph{ExpressionType}, p::PatExpr)::Id where {ExpressionType} |
568 | 599 | @assert isground(p) |
569 | 600 |
|
|
0 commit comments