I have a function type_uint(val::Integer) ::Type{<:Unsigned} which returns the smallest UInt type that can still represent val without overflowing. I use this everywhere in my code, but only at top level.
For example, given l\times m and m\times n sparse matrices X and Y, the product will be l\times n, so the indices/positions will be of type tp=type_uint(l) and the values/weights will be of type tw=promote_type(twx,twy). I pass tp(l), tw(0) and the indices and values of X and Y to a function that does all the work:
_compute_product(l::tp, ::tw,
    Xcolptr::Vector{tPx}, Xrowval::Vector{tpx}, Xnzval::Vector{twx}, 
    Ycolptr::Vector{tPy}, Yrowval::Vector{tpy}, Ynzval::Vector{twy}) 
where {tp<:Integer, tw, tPx<:Integer, tpx<:Integer, twx, tPy<:Integer, tpy<:Integer, twy}
Of course, the latter calls a bunch of other functions (which are parametrized with constrained types like above) that run in hot loops, optionally in parallel.
My question: Obviously tp(l)::Any is type unstable. But when I pass it to later functions, its type is a concrete integer. Does type-instability stop at the first function, or does it propagate to other functions? Since later functions do not accept Any arguments, I thought I was safe from losing performance. Is my thinking correct? When I use @code_warntype, I get:
MethodInstance for *(::MatCSC{UInt24, UInt24, Float64}, ::MatCSC{UInt24, UInt24, Float64}, ::Type{mtd3}, ::Type{prl1})
  from *(X::MatCSC{tPx, tpx, twx}, Y::MatCSC{tPy, tpy, twy}, mtd::Type{<:SLATH.Mtd}, prl::Type{<:SLATH.Prl}) where {tPx, tpx, twx, tPy, tpy, twy} @ SLATH ~/venvs_julia/SLATH/src/SLATH.jl:2317
Static Parameters
  tPx = UInt24
  tpx = UInt24
  twx = Float64
  tPy = UInt24
  tpy = UInt24
  twy = Float64
Arguments
  #self#::Core.Const(*)
  X::MatCSC{UInt24, UInt24, Float64}
  Y::MatCSC{UInt24, UInt24, Float64}
  mtd::Core.Const(mtd3)
  prl::Core.Const(prl1)
Locals
  @_6::Int64
  @_7::Int64
  ww::Vector{Float64}
  pp::Vector
  I::Vector{UInt64}
  _0::Float64
  tp::Type
  tP::Type{UInt64}
  n::Int64
  _m::Int64
  m::Int64
  l::Int64
  @_18::MatCSC{UInt64, T, Float64} where T<:Integer
Body::MatCSC{UInt64, T, Float64} where T<:Integer
1 β        Core.NewvarNode(:(@_6))
β          Core.NewvarNode(:(ww))
β          Core.NewvarNode(:(pp))
β          Core.NewvarNode(:(I))
β          Core.NewvarNode(:(_0))
β          Core.NewvarNode(:(tp))
β          Core.NewvarNode(:(tP))
β   %8   = SLATH.MatCSC::Core.Const(MatCSC)
β   %9   = SLATH.size::Core.Const(size)
β   %10  = (%9)(X)::Tuple{Int64, Int64}
β   %11  = SLATH.size::Core.Const(size)
β   %12  = (%11)(Y)::Tuple{Int64, Int64}
β   %13  = Core._apply_iterate(Base.iterate, Core.tuple, %10, %12)::NTuple{4, Int64}
β   %14  = Base.indexed_iterate(%13, 1)::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(2)])
β          (l = Core.getfield(%14, 1))
β          (@_7 = Core.getfield(%14, 2))
β   %17  = @_7::Core.Const(2)
β   %18  = Base.indexed_iterate(%13, 2, %17)::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(3)])
β          (m = Core.getfield(%18, 1))
β          (@_7 = Core.getfield(%18, 2))
β   %21  = @_7::Core.Const(3)
β   %22  = Base.indexed_iterate(%13, 3, %21)::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(4)])
β          (_m = Core.getfield(%22, 1))
β          (@_7 = Core.getfield(%22, 2))
β   %25  = @_7::Core.Const(4)
β   %26  = Base.indexed_iterate(%13, 4, %25)::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(5)])
β          (n = Core.getfield(%26, 1))
β   %28  = SLATH.:(==)::Core.Const(==)
β   %29  = m::Int64
β   %30  = _m::Int64
β   %31  = (%28)(%29, %30)::Bool
βββ        goto #3 if not %31
2 β        goto #4
3 β %34  = Base.throw::Core.Const(throw)
β   %35  = Base.AssertionError::Core.Const(AssertionError)
β   %36  = Main.Base::Core.Const(Base)
β   %37  = Base.getproperty(%36, :inferencebarrier)::Any
β   %38  = Main.Base::Core.Const(Base)
β   %39  = Base.getproperty(%38, :string)::Any
β   %40  = (%37)(%39)::Any
β   %41  = m::Int64
β   %42  = _m::Int64
β   %43  = Base.string("X*Y is undefined, since size(X,2) = ", %41, " β  ", %42, " = size(Y,1).")::Any
β   %44  = (%40)(%43)::Any
β   %45  = (%35)(%44)::Any
βββ        (%34)(%45)
4 β        (tP = SLATH.UInt64)
β   %48  = l::Int64
β          (tp = SLATH.type_uint(%48))
β   %50  = SLATH.:*::Core.Const(*)
β   %51  = SLATH.zero::Core.Const(zero)
β   %52  = $(Expr(:static_parameter, 3))::Core.Const(Float64)
β   %53  = (%51)(%52)::Core.Const(0.0)
β   %54  = SLATH.zero::Core.Const(zero)
β   %55  = $(Expr(:static_parameter, 6))::Core.Const(Float64)
β   %56  = (%54)(%55)::Core.Const(0.0)
β          (_0 = (%50)(%53, %56))
β   %58  = SLATH.zeros::Core.Const(zeros)
β   %59  = tP::Core.Const(UInt64)
β   %60  = n::Int64
β   %61  = (%60 + 1)::Int64
β          (I = (%58)(%59, %61))
β   %63  = I::Vector{UInt64}
β          Base.setindex!(%63, 1, 1)
β   %65  = SLATH._mul_CS_CS!::Core.Const(SLATH._mul_CS_CS!)
β   %66  = Base.getproperty(X, :I)::Vector{UInt24}
β   %67  = Base.getproperty(X, :pp)::Vector{UInt24}
β   %68  = Base.getproperty(X, :ww)::Vector{Float64}
β   %69  = Base.getproperty(Y, :I)::Vector{UInt24}
β   %70  = Base.getproperty(Y, :pp)::Vector{UInt24}
β   %71  = Base.getproperty(Y, :ww)::Vector{Float64}
β   %72  = I::Vector{UInt64}
β   %73  = tp::Type
β   %74  = (%73)(0)::Any
β   %75  = _0::Core.Const(0.0)
β   %76  = l::Int64
β   %77  = m::Int64
β   %78  = n::Int64
β   %79  = (%65)(mtd, prl, %66, %67, %68, %69, %70, %71, %72, %74, %75, %76, %77, %78)::Tuple{Vector, Vector{Float64}}
β   %80  = Base.indexed_iterate(%79, 1)::Core.PartialStruct(Tuple{Vector, Int64}, Any[Vector, Core.Const(2)])
β          (pp = Core.getfield(%80, 1))
β          (@_6 = Core.getfield(%80, 2))
β   %83  = @_6::Core.Const(2)
β   %84  = Base.indexed_iterate(%79, 2, %83)::Core.PartialStruct(Tuple{Vector{Float64}, Int64}, Any[Vector{Float64}, Core.Const(3)])
β          (ww = Core.getfield(%84, 1))
β   %86  = SLATH.MatCSC::Core.Const(MatCSC)
β   %87  = tP::Core.Const(UInt64)
β   %88  = tp::Type
β   %89  = SLATH.typeof::Core.Const(typeof)
β   %90  = _0::Core.Const(0.0)
β   %91  = (%89)(%90)::Core.Const(Float64)
β   %92  = Core.apply_type(%86, %87, %88, %91)::Type{MatCSC{UInt64, T, Float64}} where T
β   %93  = tp::Type
β   %94  = l::Int64
β   %95  = Base.getindex(%93, %94)::Vector
β   %96  = I::Vector{UInt64}
β   %97  = pp::Vector
β   %98  = ww::Vector{Float64}
β   %99  = (%92)(%95, %96, %97, %98)::MatCSC{UInt64, T, Float64} where T<:Integer
β          (@_18 = %99)
β   %101 = @_18::MatCSC{UInt64, T, Float64} where T<:Integer
β   %102 = (%101 isa %8)::Core.Const(true)
βββ        goto #6 if not %102
5 β        goto #7
6 β        Core.Const(:(@_18))
β          Core.Const(:(Base.convert(%8, %105)))
βββ        Core.Const(:(@_18 = Core.typeassert(%106, %8)))
7 β %108 = @_18::MatCSC{UInt64, T, Float64} where T<:Integer
βββ        return %108
Is this healthy?
Off-topic, a second question that I have, how can I force julia to specialize in the following case:
function Base.hcat(XX::MatCSC...)  @assert length(XX)>=3; ...general implementation ... end
function Base.hcat(X::MatCSC{tP,tp,tw}) where {...}  return X end
function Base.hcat(X::MatCSC{tPx,tpx,twx}, Y::MatCSC{tPy,tpy,twy}) where {...}  ...more efficient implementation ... end;
When calling on 1 or 2 arguments, I get the assertion error, even though the 1- and 2- case is defined later and is more specific (parametrized by types).