Nice! I’m more than happy to provide a bit of feedback to help make this happen.
For point 1, the key is to lean on constant propagation instead of explicit Val
usage. The key test cases, then, are going to be introspecting functions that call eachslice
with a constant literal tuple. For example:
julia> f(;dims=()) = dims[1]+dims[2]+dims[3]
f (generic function with 1 method)
julia> g() = f(dims=(1,2,3))
g (generic function with 1 method)
julia> @code_llvm g()
; @ REPL[5]:1 within `g'
define i64 @julia_g_12214() {
top:
ret i64 6
}
For this to work, you need to ensure that f
inlines up to the point where you use dims
. Now combining this with a more complicated algorithm (point 2) is gonna be tricky. Encoding these literal constants as quickly as possible into a typed tuple is probably going to be your best bet. Unfortunately the naive things with ntuple
doesn’t quite do the trick:
julia> f(A;dims) = ntuple(i->i in dims ? (:) : 1, ndims(A))
f (generic function with 1 method)
julia> g(A) = f(A, dims=(2,3))
g (generic function with 1 method)
julia> @code_warntype g(rand(3,4,5))
Body::Tuple{Union{Colon, Int64},Colon,Union{Colon, Int64}}
It’s rather amazing how Julia is able to concretely identify the second element in that tuple, but it’s not quite able to follow in
the whole way along all the checks in the tuple. Even more amazing is that this is all happening with the totally generic in(x, itr)
definition. Ok, so perhaps that’s the key, let’s try adding some specialized in
methods for small tuples:
julia> Base.in(x, ::Tuple{}) = false
Base.in(x, t::Tuple{Any}) = x == t[1]
Base.in(x, t::NTuple{2,Any}) = x == t[1] || x == t[2]
Base.in(x, t::NTuple{3,Any}) = x == t[1] || x == t[2] || x == t[3]
Base.in(x, t::NTuple{4,Any}) = x == t[1] || x in Base.tail(t)
julia> @code_warntype g(rand(3,4,5))
Body::Tuple{Int64,Colon,Colon}
# ...
11 ─ return (1, Colon(), Colon())
Now you can encode your constant literals into a Tuple
of either Int
s or Colon
s… and then use that for your type-stable algorithm. To use this “template” tuple to replace an arbitrary tuple of indices, walk through them in sync:
replace_colons(template::Tuple{}, idxs::Tuple{}) = ()
replace_colons(template::Tuple{Int, Vararg{Any}}, idxs::Tuple{Any, Vararg{Any}}) = (idxs[1], replace_colons(tail(template), tail(idxs))...)
replace_colons(template::Tuple{Colon, Vararg{Any}}, idxs::Tuple{Any, Vararg{Any}}) = (:, replace_colons(tail(template), tail(idxs))...)