From 2d14f3397bc0dc6c85ce29933486aee121f8c445 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Sun, 24 Aug 2025 09:09:04 -0500 Subject: [PATCH 1/8] Union-split on `Expr`, `Symbol`, and `LineNumberNode` when hashing `Expr` --- base/hashing.jl | 8 ++++++-- base/multidimensional.jl | 37 ++++++++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/base/hashing.jl b/base/hashing.jl index 30a5e28447809..c4d1a94402a5c 100644 --- a/base/hashing.jl +++ b/base/hashing.jl @@ -236,17 +236,21 @@ end ## symbol & expression hashing ## if UInt === UInt64 # conservatively hash using == equality of all of the data, even though == often uses === internally - hash(x::Expr, h::UInt) = hash(x.args, hash(x.head, h ⊻ 0x83c7900696d26dc6)) hash(x::QuoteNode, h::UInt) = hash(x.value, h ⊻ 0x2c97bf8b3de87020) hash(x::PhiNode, h::UInt) = hash(x.edges, hash(x.values, h ⊻ 0x2c97bf8b3de87020)) hash(x::PhiCNode, h::UInt) = hash(x.values, h ⊻ 0x2c97bf8b3de87020) else - hash(x::Expr, h::UInt) = hash(x.args, hash(x.head, h ⊻ 0x469d72af)) hash(x::QuoteNode, h::UInt) = hash(x.value, h ⊻ 0x469d72af) hash(x::PhiNode, h::UInt) = hash(x.edges, hash(x.values, h ⊻ 0x469d72af)) hash(x::PhiCNode, h::UInt) = hash(x.values, h ⊻ 0x469d72af) end +function hash(x::Expr, h::UInt) + h = hash(x.head, h ⊻ (UInt === UInt64 ? 0x83c7900696d26dc6 : 0x469d72af)) + # Hint that `x.args::Vector{Any}` is mostly Expr, Symbol, and LineNumberNode. + hash_shaped(x.args, h ⊻ hash_abstractarray_seed, (Val{Expr}(), Val{Symbol}(), Val{LineNumberNode}())) +end + function hash(x::CodeInfo, h::UInt) h ⊻= UInt === UInt64 ? 0x2c97bf8b3de87020 : 0x469d72af for i in 1:nfields(x) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 9512d5d6dbe8b..8b29fdfdb3b32 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -2083,7 +2083,34 @@ function _hash_fib(A, h::UInt) return hash_uint(h) end -function hash_shaped(A, h::UInt) +""" + union_split(f, x, ts::Tuple{Vararg{Val}}, args...) + +call `f(x, args...)`, union-splitting on all the types specified by `ts` + +`union_split(f, x, (Val{T1}(), Val{T2}()), y, z)` is equivalent to + +``` +if x isa T1 + f(x, y, z) +elseif x isa T2 + f(x, y, z) +else + f(x, y, z) +end +``` +""" +function union_split(f::F, @nospecialize(x), ts::Tuple{Val{T}, Vararg{Any,N}}, args...) where {F, T, N} + if x isa T + f(x::T, args...) + else + union_split(f, x, Base.tail(ts), args...) + end +end +union_split(f::F, @nospecialize(x), ::Tuple{}, args...) where F = f(x, args...) + +function hash_shaped(A, h0::UInt, eltpye_hint=()) + h::UInt = h0 # Axes are themselves AbstractArrays, so hashing them directly would stack overflow # Instead hash the tuple of firsts and lasts along each dimension h = hash(map(first, axes(A)), h) @@ -2093,20 +2120,20 @@ function hash_shaped(A, h::UInt) if len < 8 # for the shortest arrays we chain directly for elt in A - h = hash(elt, h) + h = @inline union_split(hash, elt, eltpye_hint, h) end return h elseif len < 32768 # separate accumulator streams, unrolled - @nexprs 8 i -> p_i = h + @nexprs 8 i -> p_i::UInt = h n = 1 limit = len - 7 while n <= limit - @nexprs 8 i -> p_i = hash(A[n + i - 1], p_i) + @nexprs 8 i -> p_i = @inline union_split(hash, A[n + i - 1], eltpye_hint, p_i) n += 8 end while n <= len - p_1 = hash(A[n], p_1) + p_1 = @inline union_split(hash, A[n], eltpye_hint, p_1) n += 1 end # fold all streams back together From ca11116eeb3658ca4c522a598ee908780d909433 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Sun, 24 Aug 2025 13:45:22 -0500 Subject: [PATCH 2/8] Apply suggestions from code review --- base/multidimensional.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 8b29fdfdb3b32..d86611275706c 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -2100,16 +2100,16 @@ else end ``` """ -function union_split(f::F, @nospecialize(x), ts::Tuple{Val{T}, Vararg{Any,N}}, args...) where {F, T, N} +@inline function union_split(f::F, @nospecialize(x), ts::Tuple{Val{T}, Vararg{Any,N}}, args...) where {F, T, N} if x isa T f(x::T, args...) else union_split(f, x, Base.tail(ts), args...) end end -union_split(f::F, @nospecialize(x), ::Tuple{}, args...) where F = f(x, args...) +@inline union_split(f::F, @nospecialize(x), ::Tuple{}, args...) where F = f(x, args...) -function hash_shaped(A, h0::UInt, eltpye_hint=()) +function hash_shaped(A, h0::UInt, eltype_hint=()) h::UInt = h0 # Axes are themselves AbstractArrays, so hashing them directly would stack overflow # Instead hash the tuple of firsts and lasts along each dimension @@ -2120,7 +2120,7 @@ function hash_shaped(A, h0::UInt, eltpye_hint=()) if len < 8 # for the shortest arrays we chain directly for elt in A - h = @inline union_split(hash, elt, eltpye_hint, h) + h = union_split(hash, elt, eltype_hint, h) end return h elseif len < 32768 @@ -2129,11 +2129,11 @@ function hash_shaped(A, h0::UInt, eltpye_hint=()) n = 1 limit = len - 7 while n <= limit - @nexprs 8 i -> p_i = @inline union_split(hash, A[n + i - 1], eltpye_hint, p_i) + @nexprs 8 i -> p_i = union_split(hash, A[n + i - 1], eltype_hint, p_i) n += 8 end while n <= len - p_1 = @inline union_split(hash, A[n], eltpye_hint, p_1) + p_1 = union_split(hash, A[n], eltype_hint, p_1) n += 1 end # fold all streams back together From cd5bea8a0e6988067aa63d369cfe43864db42d55 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Mon, 25 Aug 2025 06:13:42 -0500 Subject: [PATCH 3/8] Remove unneccessary type annotation Co-authored-by: Neven Sajko <4944410+nsajko@users.noreply.github.com> --- base/multidimensional.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index d86611275706c..96295daa81e22 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -2102,7 +2102,7 @@ end """ @inline function union_split(f::F, @nospecialize(x), ts::Tuple{Val{T}, Vararg{Any,N}}, args...) where {F, T, N} if x isa T - f(x::T, args...) + f(x, args...) else union_split(f, x, Base.tail(ts), args...) end From d525f46af0d3570865b112134bf1202c14e7ed3a Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Mon, 25 Aug 2025 12:22:30 -0500 Subject: [PATCH 4/8] Don't nospecialize the base case --- base/multidimensional.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 96295daa81e22..89e7f53a02707 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -2107,7 +2107,7 @@ end union_split(f, x, Base.tail(ts), args...) end end -@inline union_split(f::F, @nospecialize(x), ::Tuple{}, args...) where F = f(x, args...) +@inline union_split(f::F, x, ::Tuple{}, args...) where F = f(x, args...) function hash_shaped(A, h0::UInt, eltype_hint=()) h::UInt = h0 From a4998a7a5a443d158a6e3230f34f49a5c5c75d9f Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Mon, 25 Aug 2025 12:23:30 -0500 Subject: [PATCH 5/8] Don't use unneccessary type parameter for function --- base/multidimensional.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 89e7f53a02707..6756d358e0cfd 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -2100,14 +2100,14 @@ else end ``` """ -@inline function union_split(f::F, @nospecialize(x), ts::Tuple{Val{T}, Vararg{Any,N}}, args...) where {F, T, N} +@inline function union_split(f, @nospecialize(x), ts::Tuple{Val{T}, Vararg{Any,N}}, args...) where {T, N} if x isa T f(x, args...) else union_split(f, x, Base.tail(ts), args...) end end -@inline union_split(f::F, x, ::Tuple{}, args...) where F = f(x, args...) +@inline union_split(x, ::Tuple{}, args...) = f(x, args...) function hash_shaped(A, h0::UInt, eltype_hint=()) h::UInt = h0 From e7b9408537f042903d61bd1819698e36c888b9b8 Mon Sep 17 00:00:00 2001 From: Andy Dienes <51664769+adienes@users.noreply.github.com> Date: Mon, 25 Aug 2025 13:51:40 -0400 Subject: [PATCH 6/8] Update base/multidimensional.jl --- base/multidimensional.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 6756d358e0cfd..57e7910c8b21e 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -2107,7 +2107,7 @@ end union_split(f, x, Base.tail(ts), args...) end end -@inline union_split(x, ::Tuple{}, args...) = f(x, args...) +@inline union_split(f, x, ::Tuple{}, args...) = f(x, args...) function hash_shaped(A, h0::UInt, eltype_hint=()) h::UInt = h0 From 9be750de8b9baa2c15f7a80634c04a733177ac5c Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 29 Aug 2025 10:12:42 -0500 Subject: [PATCH 7/8] Tighten dispatch Co-authored-by: Neven Sajko <4944410+nsajko@users.noreply.github.com> --- base/multidimensional.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 57e7910c8b21e..e46c8ae986c7f 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -2100,7 +2100,7 @@ else end ``` """ -@inline function union_split(f, @nospecialize(x), ts::Tuple{Val{T}, Vararg{Any,N}}, args...) where {T, N} +@inline function union_split(f, @nospecialize(x), ts::Tuple{Val{T}, Vararg{Val,N}}, args...) where {T, N} if x isa T f(x, args...) else From 8a95cf82d3dd860ecf07d598e619875425ae9586 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Sun, 7 Sep 2025 12:17:57 -0500 Subject: [PATCH 8/8] Force specialization on union_split with empty tuple Co-authored-by: Neven Sajko <4944410+nsajko@users.noreply.github.com> --- base/multidimensional.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index e46c8ae986c7f..b436fcff11b64 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -2107,7 +2107,7 @@ end union_split(f, x, Base.tail(ts), args...) end end -@inline union_split(f, x, ::Tuple{}, args...) = f(x, args...) +@inline union_split(f, x, ::Tuple{}, args::Vararg{Any, N}) where {N} = f(x, args...) function hash_shaped(A, h0::UInt, eltype_hint=()) h::UInt = h0