`unsafe_pointer_to_objref(::Ptr{T})` is not type stable

Example code:

mutable struct Foo{T}
    v::Vector{T}
    n::Int
    # possibly lots more fields, which I don't want
    # to have to copy each time with unsafe_{load,store!}
end

function demo()
    a = Foo{Int64}([1], 2)
    pa = pointer_from_objref(a)  # gives me an untyped Ptr{Nothing}
    # pa passed on to a C library ...
    # ... pa received back through a C callback function
    tpa = convert(Ptr{Foo{Int64}}, pa) # C callback function restores type
    # tpa[].n += 1 is not allowed
    na = unsafe_pointer_to_objref(tpa)
    na.n += 1
end
demo() == 3 && @code_warntype demo()

@code_warntype demo() shows this function to be type unstable, returning ::Any instead of ::Int64:

Variables
  #self#::Core.Compiler.Const(demo, false)
  a::Foo{Int64}
  pa::Ptr{Nothing}
  tpa::Ptr{Foo{Int64}}
  na::Any

Body::Any
1 ─ %1  = Core.apply_type(Main.Foo, Main.Int64)::Core.Compiler.Const(Foo{Int64}, false)
│   %2  = Base.vect(1)::Array{Int64,1}
│         (a = (%1)(%2, 2))
│         (pa = Main.pointer_from_objref(a))
│   %5  = Core.apply_type(Main.Foo, Main.Int64)::Core.Compiler.Const(Foo{Int64}, false)
│   %6  = Core.apply_type(Main.Ptr, %5)::Core.Compiler.Const(Ptr{Foo{Int64}}, false)
│         (tpa = Main.convert(%6, pa))
│         (na = Main.unsafe_pointer_to_objref(tpa))
│   %9  = Base.getproperty(na, :n)::Any
│   %10 = (%9 + 1)::Any
│         Base.setproperty!(na, :n, %10)
└──       return %10

with ::Any highlighted in red. This seems due to the definition in Base of

unsafe_pointer_to_objref(x::Ptr) = ccall(:jl_value_ptr, Any, (Ptr{Cvoid},), x)

Is is not possible for Base to implement a method

unsafe_pointer_to_objref(x::Ptr{T})::T

for which the compiler can infer the return type being T?

When you are passing pointers through C, you should just use the correct return type, e.g. Ref{Foo{Float64}} instead of using unsafe_pointer_to_objref.

5 Likes