Finding uses of a method?

Is there a way to interrogate the Julia runtime and determine places where a method has been called from?

(Context: Emacs development environment for Julia. This is for implementing search by cross-references.)

You could potentially use Casette.jl to store the stacktrace every time a particular method is called. Apart from this, I do not think any such functionality exists.

For code that has been executed, you can look at the backedges:

julia> foo(::Integer) = 1
foo (generic function with 1 method)

julia> foo(::AbstractFloat) = 2
foo (generic function with 2 methods)

julia> m = @which foo(1)
foo(::Integer) in Main at REPL[1]:1

julia> m.specializations     # no specializations (yet)

julia> bar(x) = foo(x)
bar (generic function with 1 method)

julia> bar(1)    # execute it, which forces compilation which requires specialization
1

julia> m.specializations    # now there are specializations (linked list, see the `next` field also)
Core.TypeMapEntry(nothing, Tuple{typeof(foo),Int64}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for foo(::Int64), true, true, false)

julia> mi = m.specializations.func        # the MethodInstance for the first specialization
MethodInstance for foo(::Int64)

julia> mi.backedges    # here's the list of MethodInstances that require this specialization
1-element Array{Any,1}:
 MethodInstance for bar(::Int64)

For code that hasn’t been executed, it’s difficult to answer even in principle because the same bar might call a different method of foo given different input types. You could search for all calls that have the proper number of arguments by iterating through all names in a module, for the functions then iterating through each method, call Base.uncompressed_ast on the method and then iterate through the code lines to look for the calls. This would give you a superset of things that call the given method from that module.

You could be a bit more selective by calling code_llvm on each method with its signature-restricted types; that would force inference and generation of backedges for the broadest possible types supported by each method. But I doubt you’d want to wait for the amount of computation that would require—I would guess that running that on Base would take many minutes (inference is slow, whereas accessing the lowered code via Base.uncompressed_ast is pretty fast).

9 Likes

Fantastic info. Thank you very much!