Getting function's module by a MethodInstance

I’m looping over method instances in a module using @tim.holy’s marvelous MethodAnalysis.jl. I need to run @code_typed for those instances to extract some type information. For that I first resolve the function using getfield on MethodInstance.def.module and MethodInstance.def.name. The issue is that a method can be defined in other modules than the original function, so MethodInstance.def.module is not quite right.

Is there a way to get the module where the function was introduced using MethodInstance interface?

I often do it that way, too. However, I’ve also noticed an alternative, which I sometimes remember to use:

julia> m = @which sum((1,2))
sum(x::Tuple{Any, Vararg{Any, N} where N}) in Base at tuple.jl:474

julia> ft = Base.unwrap_unionall(m.sig).parameters[1]
typeof(sum)

julia> ft.instance
sum (generic function with 14 methods)

I don’t remember whether the last line is always safe; maybe give it a try and let us know of any corrections you discover?

EDIT: and I’d be happy to have something like this wrapped up nicely and added to MethodAnalysis, if you want to submit a PR.

3 Likes

@tim.holy neat! There’s one problem with instance (as I figured running your approach on a bunch of instances): it’s not defined for constructors. E.g.

julia> h() = 1
julia> (@which h()).sig.parameters[1].instance                                 
h (generic function with 1 method)

julia> struct S end;
julia> (@which S()).sig.parameters[1].instance                                 
ERROR: UndefRefError: access to undefined reference                            
Stacktrace:                                                                    
 [1] getproperty(::Type{T} where T, ::Symbol) at ./Base.jl:28                  
 [2] top-level scope at REPL[95]:1   

(And the error looks a bit bizzare.) So, instead of typeof(f) we have Type{SomeType} at this spot.

This is not too hard to work around, though, assuming constructor’s function defined in the same module as the corresponding type. So, currently, I have the following to convert a method instance to a function:

instance_to_function(mi :: MethodInstance) = begin
    sig = Base.unwrap_unionall(mi.specTypes).types
    if sig[1] <: Function # typeof(func)
        sig[1].instance
    else                        # constructor
        getfield(
            parentmodule(sig[1].parameters[1]),
            mi.def.name)
    end
end

I’m yet to see how it works on a larger data set (it could reveal some more problems). But already this is good progress, thanks a lot!

Nice catch! If m is a Method, does

typ = m.sig.parameters[1]
return typ <: Type ? typ.parameters[1] : typ.instance

always work?

Almost: typ can be a UnionAll, so after adding Base.unwrap_unionall to it it seems to work.

I also found that working with constructors (typ <: Type) is not very useful because of things like:

(::Type{T})(x::Number) where {T<:AbstractChar} = T(UInt32(x))

(base/char.jl#L48)

My understanding is that without the actual value of T not much I can do with the signature I have (e.g. I use code_typed on the result but it fails for such signature). So I only analyze the case of typ <: Function right now.

I’ve played with this a bit in the meantime too. (Your question was useful since it made me think about this a bit more.) Here’s how SnoopCompile now deals with this:

https://github.com/timholy/SnoopCompile.jl/blob/484231c34cb43f392f1d1aeeb0baad672c47b27a/src/parcel_snoopi_deep.jl#L1582-L1608

ft2f (“function type to function”) is only used to print stuff to the REPL, so all that was needed was a good fallback for the case of a closure. And yes, just as you say I do have to Base.unwrap_unionall it before calling ft2f.

1 Like