Types in `:foreigncall` nodes

Consider:

julia> @code_warntype Base.unsafe_convert(Ptr{Float64}, randn(10))
MethodInstance for Base.unsafe_convert(::Type{Ptr{Float64}}, ::Vector{Float64})
  from unsafe_convert(::Type{Ptr{T}}, a::Array{T}) where T @ Base ~/.julia/juliaup/julia-1.9.2+0.x64.apple.darwin14/share/julia/base/pointer.jl:65
Static Parameters
  T = Float64
Arguments
  #self#::Core.Const(Base.unsafe_convert)
  _::Core.Const(Ptr{Float64})
  a::Vector{Float64}
Body::Ptr{Float64}
1 ─ %1 = $(Expr(:foreigncall, :(:jl_array_ptr), Ptr{T}, svec(Any), 0, :(:ccall), :(a)))::Ptr{Float64}
└──      return %1

I’m specifically interested in the return type Ptr{T}. The presence of this as a single argument to the :foreigncall node surprised me, as my (clearly incomplete) mental model of lowered code would have expected something like the following to have been produced during lowering:

%1 = $(Expr(:static_parameter, 1)
%2 = Core.apply_type(Ptr, %1)
%3 = $(Expr(:foreigncall, :(:jl_array_ptr), %2, svec(Any), 0, :(:ccall), :(a)))

Could someone explain why this is lowered in this way, rather than expanded out?

For context, I’m interested because I’m trying to understand how Umlaut.jl might handle :foreigncall nodes, how JuliaInterpreter.jl currently handles them, and what the compiler currently does.

It’s a terrible special case hack where foreigncall is allowed to have something that’s not a type but references the typevars of the method signature by identity. I’ve been wanting to disallow it for the longest time.

3 Likes

Good to know – thanks for the context. Were this to change at some point, would you anticipate literally just disallowing this special case and forcing the compiler to expand everything out (akin to what I thought would have happened in this situation), or are there other considerations?