From b4df50bb19877e00c358530aee3759d15d1ea09d Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Fri, 23 Aug 2024 10:51:16 +0200 Subject: [PATCH 01/16] Implement the correct hash function. --- src/vecexpr.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vecexpr.jl b/src/vecexpr.jl index 1e8a83ba..c18059f9 100644 --- a/src/vecexpr.jl +++ b/src/vecexpr.jl @@ -80,7 +80,7 @@ end """The hash of the e-node.""" @inline v_hash(n::VecExpr)::Id = @inbounds n.data[1] -Base.hash(n::VecExpr) = v_hash(n) # IdKey not necessary here +Base.hash(n::VecExpr, h::UInt) = hash(v_hash(n), h) # IdKey not necessary here Base.:(==)(a::VecExpr, b::VecExpr) = (@view a.data[2:end]) == (@view b.data[2:end]) """Set e-node hash to zero.""" From f987d6650284b9117e6f43d99cf7247656ee2993 Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Fri, 23 Aug 2024 10:52:38 +0200 Subject: [PATCH 02/16] Fix upwards update of semantic analysis values. --- src/EGraphs/egraph.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EGraphs/egraph.jl b/src/EGraphs/egraph.jl index f645e44a..23073362 100644 --- a/src/EGraphs/egraph.jl +++ b/src/EGraphs/egraph.jl @@ -72,7 +72,7 @@ end function merge_analysis_data!(a::EClass{D}, b::EClass{D})::Tuple{Bool,Bool,Union{D,Nothing}} where {D} if !isnothing(a.data) && !isnothing(b.data) new_a_data = join(a.data, b.data) - (a.data == new_a_data, b.data == new_a_data, new_a_data) + (a.data != new_a_data, b.data != new_a_data, new_a_data) elseif isnothing(a.data) && !isnothing(b.data) # a merged, b not merged (true, false, b.data) From 313c5e88866a70aea2088998b574e6b3481381b4 Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Fri, 23 Aug 2024 10:53:47 +0200 Subject: [PATCH 03/16] Remove unused code --- src/EGraphs/egraph.jl | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/EGraphs/egraph.jl b/src/EGraphs/egraph.jl index 23073362..f7f9ed45 100644 --- a/src/EGraphs/egraph.jl +++ b/src/EGraphs/egraph.jl @@ -223,22 +223,6 @@ Returns the canonical e-class id for a given e-class. @inline Base.getindex(g::EGraph, i::Id) = g.classes[IdKey(find(g, i))] -# function canonicalize(g::EGraph, n::VecExpr)::VecExpr -# if !v_isexpr(n) -# v_hash!(n) -# return n -# end -# l = v_arity(n) -# new_n = v_new(l) -# v_set_flag!(new_n, v_flags(n)) -# v_set_head!(new_n, v_head(n)) -# for i in v_children_range(n) -# @inbounds new_n[i] = find(g, n[i]) -# end -# v_hash!(new_n) -# new_n -# end - function canonicalize!(g::EGraph, n::VecExpr) v_isexpr(n) || @goto ret for i in (VECEXPR_META_LENGTH + 1):length(n) @@ -252,7 +236,6 @@ end function lookup(g::EGraph, n::VecExpr)::Id canonicalize!(g, n) - h = IdKey(v_hash(n)) haskey(g.memo, n) ? find(g, g.memo[n]) : 0 end From f14229d59e754ceb36b39298c2611de79a8e9e10 Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Fri, 23 Aug 2024 10:55:36 +0200 Subject: [PATCH 04/16] Fix check_memo() --- src/EGraphs/egraph.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/EGraphs/egraph.jl b/src/EGraphs/egraph.jl index f7f9ed45..d7610c33 100644 --- a/src/EGraphs/egraph.jl +++ b/src/EGraphs/egraph.jl @@ -237,7 +237,8 @@ end function lookup(g::EGraph, n::VecExpr)::Id canonicalize!(g, n) - haskey(g.memo, n) ? find(g, g.memo[n]) : 0 + id = get(g.memo, n, zero(Id)) + iszero(id) ? id : find(g, id) end @@ -256,7 +257,8 @@ Inserts an e-node in an [`EGraph`](@ref) function add!(g::EGraph{ExpressionType,Analysis}, n::VecExpr, should_copy::Bool)::Id where {ExpressionType,Analysis} canonicalize!(g, n) - haskey(g.memo, n) && return g.memo[n] + id = get(g.memo, n, zero(Id)) + iszero(id) || return id if should_copy n = copy(n) @@ -456,9 +458,8 @@ function check_memo(g::EGraph)::Bool for (id, class) in g.classes @assert id.val == class.id for node in class.nodes - if haskey(test_memo, node) - old_id = test_memo[node] - test_memo[node] = id.val + old_id = get!(test_memo, node, id.val) + if old_id != id.val @assert find(g, old_id) == find(g, id.val) "Unexpected equivalence $node $(g[find(g, id.val)].nodes) $(g[find(g, old_id)].nodes)" end end From 6b6522ec5e0fb25a699ffee5c36a9abfc4c822b3 Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Fri, 23 Aug 2024 11:12:55 +0200 Subject: [PATCH 05/16] Fix an issue where enodes are not added to memo. --- src/EGraphs/egraph.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/EGraphs/egraph.jl b/src/EGraphs/egraph.jl index d7610c33..08b74a8c 100644 --- a/src/EGraphs/egraph.jl +++ b/src/EGraphs/egraph.jl @@ -416,9 +416,8 @@ function process_unions!(g::EGraph{ExpressionType,AnalysisType})::Int where {Exp while !isempty(g.pending) (node::VecExpr, eclass_id::Id) = pop!(g.pending) canonicalize!(g, node) - if haskey(g.memo, node) - old_class_id = g.memo[node] - g.memo[node] = eclass_id + old_class_id = get!(g.memo, node, eclass_id) + if old_class_id != eclass_id did_something = union!(g, old_class_id, eclass_id) # TODO unique! can node dedup be moved here? compare performance # did_something && unique!(g[eclass_id].nodes) From 84d2e30a834ae451f671be6c1e6193380f4a33e6 Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Fri, 23 Aug 2024 11:22:02 +0200 Subject: [PATCH 06/16] Simplify code --- src/EGraphs/egraph.jl | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/EGraphs/egraph.jl b/src/EGraphs/egraph.jl index 08b74a8c..e73f21f7 100644 --- a/src/EGraphs/egraph.jl +++ b/src/EGraphs/egraph.jl @@ -304,28 +304,22 @@ function addexpr!(g::EGraph, se)::Id se isa EClass && return se.id e = preprocess(se) - n = if isexpr(e) - args = iscall(e) ? arguments(e) : children(e) - ar = length(args) - n = v_new(ar) - v_set_flag!(n, VECEXPR_FLAG_ISTREE) - iscall(e) && v_set_flag!(n, VECEXPR_FLAG_ISCALL) - - h = iscall(e) ? operation(e) : head(e) - v_set_head!(n, add_constant!(g, h)) - - # get the signature from op and arity - v_set_signature!(n, hash(maybe_quote_operation(h), hash(ar))) - - for i in v_children_range(n) - @inbounds n[i] = addexpr!(g, args[i - VECEXPR_META_LENGTH]) - end - n - else # constant enode - VecExpr(Id[Id(0), Id(0), Id(0), add_constant!(g, e)]) + isexpr(e) || return add!(g, VecExpr(Id[Id(0), Id(0), Id(0), add_constant!(g, e)]), false) + + args = iscall(e) ? arguments(e) : children(e) + ar = length(args) + n = v_new(ar) + v_set_flag!(n, VECEXPR_FLAG_ISTREE) + iscall(e) && v_set_flag!(n, VECEXPR_FLAG_ISCALL) + h = iscall(e) ? operation(e) : head(e) + v_set_head!(n, add_constant!(g, h)) + # get the signature from op and arity + v_set_signature!(n, hash(maybe_quote_operation(h), hash(ar))) + for i in v_children_range(n) + @inbounds n[i] = addexpr!(g, args[i - VECEXPR_META_LENGTH]) end - id = add!(g, n, false) - return id + + add!(g, n, false) end """ @@ -490,7 +484,7 @@ for more details. function rebuild!(g::EGraph) n_unions = process_unions!(g) trimmed_nodes = rebuild_classes!(g) - # @assert check_memo(g) + @assert check_memo(g) # @assert check_analysis(g) g.clean = true From 7adb07ec5ba55a738ff1d1d0ccaa58e5d28cb034 Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Fri, 23 Aug 2024 12:33:49 +0200 Subject: [PATCH 07/16] Add code to make sure we do not canonicalize vecexpr in memo --- src/EGraphs/egraph.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/EGraphs/egraph.jl b/src/EGraphs/egraph.jl index e73f21f7..b83319f6 100644 --- a/src/EGraphs/egraph.jl +++ b/src/EGraphs/egraph.jl @@ -224,6 +224,8 @@ Returns the canonical e-class id for a given e-class. @inline Base.getindex(g::EGraph, i::Id) = g.classes[IdKey(find(g, i))] function canonicalize!(g::EGraph, n::VecExpr) + orig = copy(n) + inmemo = any(entry -> objectid(entry) == objectid(n), keys(g.memo)) v_isexpr(n) || @goto ret for i in (VECEXPR_META_LENGTH + 1):length(n) @inbounds n[i] = find(g, n[i]) @@ -231,6 +233,7 @@ function canonicalize!(g::EGraph, n::VecExpr) v_unset_hash!(n) @label ret v_hash!(n) + @assert orig == n || !inmemo n end From 9b3d0e488074842b1399a9e745de23cfcc461322 Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Fri, 23 Aug 2024 12:36:23 +0200 Subject: [PATCH 08/16] Make sure nodes in dictionaries (memo and pending) are cloned --- src/EGraphs/egraph.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/EGraphs/egraph.jl b/src/EGraphs/egraph.jl index b83319f6..77daea30 100644 --- a/src/EGraphs/egraph.jl +++ b/src/EGraphs/egraph.jl @@ -271,17 +271,17 @@ function add!(g::EGraph{ExpressionType,Analysis}, n::VecExpr, should_copy::Bool) if v_isexpr(n) for c_id in v_children(n) - addparent!(g.classes[IdKey(c_id)], n, id) + addparent!(g.classes[IdKey(c_id)], copy(n), id) end end - g.memo[n] = id + g.memo[copy(n)] = id add_class_by_op(g, n, id) eclass = EClass{Analysis}(id, VecExpr[n], Pair{VecExpr,Id}[], make(g, n)) g.classes[IdKey(id)] = eclass modify!(g, eclass) - push!(g.pending, n => id) + push!(g.pending, copy(n) => id) return id end @@ -412,6 +412,7 @@ function process_unions!(g::EGraph{ExpressionType,AnalysisType})::Int where {Exp while !isempty(g.pending) || !isempty(g.analysis_pending) while !isempty(g.pending) (node::VecExpr, eclass_id::Id) = pop!(g.pending) + node = copy(node) canonicalize!(g, node) old_class_id = get!(g.memo, node, eclass_id) if old_class_id != eclass_id From 6c7a56aa096e3bddeb1129e4983873d348d45cce Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Fri, 23 Aug 2024 12:42:01 +0200 Subject: [PATCH 09/16] Use in!() instead of in() for UniqueQueue push!() --- src/EGraphs/uniquequeue.jl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/EGraphs/uniquequeue.jl b/src/EGraphs/uniquequeue.jl index aade15d6..ca68c336 100644 --- a/src/EGraphs/uniquequeue.jl +++ b/src/EGraphs/uniquequeue.jl @@ -12,8 +12,17 @@ end UniqueQueue{T}() where {T} = UniqueQueue{T}(Set{T}(), T[]) function Base.push!(uq::UniqueQueue{T}, x::T) where {T} - if !(x in uq.set) - push!(uq.set, x) + # checks if x is contained in s and adds x if it is not, using a single hash call and lookup + # available from Julia 1.11 + function in!(x::T, s::Set) + idx, sh = Base.ht_keyindex2_shorthash!(s.dict, x) + idx > 0 && return true + _setindex!(s.dict, nothing, x, -idx, sh) + + false + end + + if !in!(x, uq.set) push!(uq.vec, x) end end From a4fe7a2c27e252034980f3c1dd7d3ad9d0c026a2 Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Fri, 23 Aug 2024 12:45:58 +0200 Subject: [PATCH 10/16] Comment runtime assertions --- src/EGraphs/egraph.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EGraphs/egraph.jl b/src/EGraphs/egraph.jl index 77daea30..9f7fae89 100644 --- a/src/EGraphs/egraph.jl +++ b/src/EGraphs/egraph.jl @@ -224,8 +224,8 @@ Returns the canonical e-class id for a given e-class. @inline Base.getindex(g::EGraph, i::Id) = g.classes[IdKey(find(g, i))] function canonicalize!(g::EGraph, n::VecExpr) - orig = copy(n) - inmemo = any(entry -> objectid(entry) == objectid(n), keys(g.memo)) + # orig = copy(n) + # inmemo = any(entry -> objectid(entry) == objectid(n), keys(g.memo)) v_isexpr(n) || @goto ret for i in (VECEXPR_META_LENGTH + 1):length(n) @inbounds n[i] = find(g, n[i]) @@ -233,7 +233,7 @@ function canonicalize!(g::EGraph, n::VecExpr) v_unset_hash!(n) @label ret v_hash!(n) - @assert orig == n || !inmemo + # @assert orig == n || !inmemo n end @@ -488,7 +488,7 @@ for more details. function rebuild!(g::EGraph) n_unions = process_unions!(g) trimmed_nodes = rebuild_classes!(g) - @assert check_memo(g) + # @assert check_memo(g) # @assert check_analysis(g) g.clean = true From d916a0d3379f9fc8cd98536b0e5891096a34d82b Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Sat, 24 Aug 2024 11:05:04 +0200 Subject: [PATCH 11/16] Fix function call in in!() --- src/EGraphs/uniquequeue.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EGraphs/uniquequeue.jl b/src/EGraphs/uniquequeue.jl index ca68c336..f5181ba5 100644 --- a/src/EGraphs/uniquequeue.jl +++ b/src/EGraphs/uniquequeue.jl @@ -17,7 +17,7 @@ function Base.push!(uq::UniqueQueue{T}, x::T) where {T} function in!(x::T, s::Set) idx, sh = Base.ht_keyindex2_shorthash!(s.dict, x) idx > 0 && return true - _setindex!(s.dict, nothing, x, -idx, sh) + Base._setindex!(s.dict, nothing, x, -idx, sh) false end From d5dce5d0de29cf97da7533d059883dd0b51bb5d2 Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Sat, 24 Aug 2024 11:27:42 +0200 Subject: [PATCH 12/16] Remove unnecessary copies of VecExpr --- src/EGraphs/egraph.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EGraphs/egraph.jl b/src/EGraphs/egraph.jl index 6898687b..2de89a83 100644 --- a/src/EGraphs/egraph.jl +++ b/src/EGraphs/egraph.jl @@ -272,17 +272,17 @@ function add!(g::EGraph{ExpressionType,Analysis}, n::VecExpr, should_copy::Bool) if v_isexpr(n) for c_id in v_children(n) - addparent!(g.classes[IdKey(c_id)], copy(n), id) + addparent!(g.classes[IdKey(c_id)], n, id) end end - g.memo[copy(n)] = id + g.memo[n] = id add_class_by_op(g, n, id) - eclass = EClass{Analysis}(id, VecExpr[n], Pair{VecExpr,Id}[], make(g, n)) + eclass = EClass{Analysis}(id, VecExpr[copy(n)], Pair{VecExpr,Id}[], make(g, n)) g.classes[IdKey(id)] = eclass modify!(g, eclass) - push!(g.pending, copy(n) => id) + push!(g.pending, n => id) return id end From fbdfc73f455b74835724e947881e6fa11a07ca14 Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Sat, 24 Aug 2024 11:28:05 +0200 Subject: [PATCH 13/16] Improve add_class_by_op to use a single dictionary lookup --- src/EGraphs/egraph.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/EGraphs/egraph.jl b/src/EGraphs/egraph.jl index 2de89a83..2d833210 100644 --- a/src/EGraphs/egraph.jl +++ b/src/EGraphs/egraph.jl @@ -248,11 +248,8 @@ end function add_class_by_op(g::EGraph, n, eclass_id) key = IdKey(v_signature(n)) - if haskey(g.classes_by_op, key) - push!(g.classes_by_op[key], eclass_id) - else - g.classes_by_op[key] = [eclass_id] - end + vec = get!(g.classes_by_op, key, Vector{Id}()) + push!(vec, eclass_id) end """ From f74d552d55b4b316266eb65aeb179c1e7da7714e Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Sat, 24 Aug 2024 20:40:06 +0200 Subject: [PATCH 14/16] Removed old commented code. --- src/EGraphs/egraph.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/EGraphs/egraph.jl b/src/EGraphs/egraph.jl index 2d833210..5937ddad 100644 --- a/src/EGraphs/egraph.jl +++ b/src/EGraphs/egraph.jl @@ -225,8 +225,6 @@ Returns the canonical e-class id for a given e-class. @inline Base.getindex(g::EGraph, i::Id) = g.classes[IdKey(find(g, i))] function canonicalize!(g::EGraph, n::VecExpr) - # orig = copy(n) - # inmemo = any(entry -> objectid(entry) == objectid(n), keys(g.memo)) v_isexpr(n) || @goto ret for i in (VECEXPR_META_LENGTH + 1):length(n) @inbounds n[i] = find(g, n[i]) @@ -234,7 +232,6 @@ function canonicalize!(g::EGraph, n::VecExpr) v_unset_hash!(n) @label ret v_hash!(n) - # @assert orig == n || !inmemo n end From a3ebcb6e4f443946c82a07e6b6c1cc68197c4272 Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Sat, 24 Aug 2024 20:45:46 +0200 Subject: [PATCH 15/16] Move in!() outside of push!() --- src/EGraphs/uniquequeue.jl | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/EGraphs/uniquequeue.jl b/src/EGraphs/uniquequeue.jl index f5181ba5..7c080156 100644 --- a/src/EGraphs/uniquequeue.jl +++ b/src/EGraphs/uniquequeue.jl @@ -12,16 +12,6 @@ end UniqueQueue{T}() where {T} = UniqueQueue{T}(Set{T}(), T[]) function Base.push!(uq::UniqueQueue{T}, x::T) where {T} - # checks if x is contained in s and adds x if it is not, using a single hash call and lookup - # available from Julia 1.11 - function in!(x::T, s::Set) - idx, sh = Base.ht_keyindex2_shorthash!(s.dict, x) - idx > 0 && return true - Base._setindex!(s.dict, nothing, x, -idx, sh) - - false - end - if !in!(x, uq.set) push!(uq.vec, x) end @@ -39,4 +29,16 @@ function Base.pop!(uq::UniqueQueue{T}) where {T} v end -Base.isempty(uq::UniqueQueue) = isempty(uq.vec) \ No newline at end of file +Base.isempty(uq::UniqueQueue) = isempty(uq.vec) + +# Helper for push!() +# checks if x is contained in s and adds x if it is not, using a single hash call and lookup +# available from Julia 1.11 +function in!(x::T, s::Set{T}) where {T} + idx, sh = Base.ht_keyindex2_shorthash!(s.dict, x) + idx > 0 && return true + Base._setindex!(s.dict, nothing, x, -idx, sh) + + false +end + From 66ea780fe36a5d821b02a18d92ff32aa19701cca Mon Sep 17 00:00:00 2001 From: Gabriel Kronberger Date: Sun, 25 Aug 2024 11:23:43 +0200 Subject: [PATCH 16/16] Revert change in UniqueQueue as it does not work with Julia v1.8 --- src/EGraphs/uniquequeue.jl | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/EGraphs/uniquequeue.jl b/src/EGraphs/uniquequeue.jl index 7c080156..512bb61a 100644 --- a/src/EGraphs/uniquequeue.jl +++ b/src/EGraphs/uniquequeue.jl @@ -12,7 +12,8 @@ end UniqueQueue{T}() where {T} = UniqueQueue{T}(Set{T}(), T[]) function Base.push!(uq::UniqueQueue{T}, x::T) where {T} - if !in!(x, uq.set) + if !(x in uq.set) + push!(uq.set, x) push!(uq.vec, x) end end @@ -30,15 +31,3 @@ function Base.pop!(uq::UniqueQueue{T}) where {T} end Base.isempty(uq::UniqueQueue) = isempty(uq.vec) - -# Helper for push!() -# checks if x is contained in s and adds x if it is not, using a single hash call and lookup -# available from Julia 1.11 -function in!(x::T, s::Set{T}) where {T} - idx, sh = Base.ht_keyindex2_shorthash!(s.dict, x) - idx > 0 && return true - Base._setindex!(s.dict, nothing, x, -idx, sh) - - false -end -