I have a question about type instability when accessing the fields of an
abstract type
(in julia v0.6).
Suppose I have a type hierarchy, all of which share the same instance variable. I know that it is both type-unstable and not-guaranteed to be correct to access the field correctly, since someone could always define a new subtype that lacks the expected variable. However, even when wrapping the member access in a function, the access is still type unstable, and I can't figure out why.
Assume we have a simple type hierarchy:
julia> begin
abstract type AT end
mutable struct T1 <: AT
x::Int
end
mutable struct T2 <: AT
x::Int
end
end
Instead of accessing a.x
directly, we wrap it in a function barrier:
julia> getX(a::AT)::Int = a.x
>> getX (generic function with 1 method)
julia> @code_warntype getX(T1(1))
Variables:
#self# <optimized out>
a::T1
Body:
begin
return (Core.getfield)(a::T1, :x)::Int64
end::Int64
Note that the access through this method is type-stable, since it can infer the
type of a
to be T1
.
However, when I use getX
in a context where the compiler can't know the type
of the variable ahead-of-time, it's still type-unstable:
julia> foo() = getX(rand([T1(1),T2(2)]))
>> foo (generic function with 1 method)
julia> @code_warntype foo()
Variables:
#self# <optimized out>
T <optimized out>
Body:
begin
SSAValue(0) = (Core.tuple)($(Expr(:new, :(Main.T1), 1)), $(Expr(:new, :(Main.T2), 2)))::Tuple{T1,T2}
SSAValue(2) = $(Expr(:invoke, MethodInstance for rand(::Array{AT,1}), :(Main.rand), :($(Expr(:invoke, MethodInstance for copy!(::Array{AT,1}, ::Tuple{T1,T2}), :(Base.copy!), :($(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{AT,1}, svec(Any, Int64), Array{AT,1}, 0, 2, 0))), SSAValue(0))))))
return (Core.typeassert)((Base.convert)(Main.Int, (Core.getfield)(SSAValue(2), :x)::Any)::Any, Main.Int)::Int64
end::Int64
Note that it inlined the body of getX
, and replaced it with essentially
tmp.x::Int64
. This surprises me, since I was expecting getX
to dispatch to
one of the two instantiations of the same definition we saw above, where no assert
is necessary since the type is known.
I thought that this does make some sense if getX
is actually only defined
for the abstract base type AT
-- there would be no methods to dispatch to in
the way I'm imagining. So I tried redefining getX
such that it would generate
a specific method for each subtype as follows:
julia> getX(a::T where T<:AT)::Int = a.x
>> getX (generic function with 1 method)
But that is actually an identical definition, and nothing changed:
julia> methods(getX)
>> # 1 method for generic function "getX":
getX(a::AT) in Main at none:1
Any idea how I can get this to work?