Way to plot graph of codebase? (i.e. function circles and dependency arrows)

Yeah, TreeView isn’t quite what you want, but it’s the closest thing I could think of at the time.

Maybe just using the functionality in Base.Profile to build the call graph by sampling and visualizing it using ProfileView is closer to what you need.

But I was thinking a bit more about how to construct the call graph without sampling. I guess one thing you would need in order to get an actual call graph for a given function call is the ability to ‘dig into’ functions. So given e.g.

function f(x, y)
    a = x * x
    a + g(y * y)
end

somehow get the expression

quote 
    a = x * x
    return a + (Main.g)(y * y)
end

In general, the expression to return will of course depend on the types of the input arguments, because function f might have several methods. Here is my attempt at accomplishing this:

function replace_slot_numbers_with_names!(expr::Expr, l::LambdaInfo)
    for (i, arg) in enumerate(expr.args)
        if isa(arg, SlotNumber)
            expr.args[i] = l.slotnames[arg.id]
        elseif isa(arg, Expr)
            replace_slot_numbers_with_names!(arg, l)
        end
    end
    expr
end

function function_body_expr(f, types)
    l = first(code_lowered(f, types))
    ast = Base.uncompressed_ast(l)
    body = Expr(:block)
    body.args = ast[2 : end]
    body.typ = l.rettype
    expr = Base.remove_linenums!(replace_slot_numbers_with_names!(body, l))
end

Usage example:

function_body_expr(f, (Int, Int))

which returns exactly the desired expression above. Note: unfortunately, TreeView / its dependency TikzGraphs can’t quite handle this expression due to some LaTeX issues. I worked around these issues by changing this line to

return latex_escape(string(sym))

after which

using TreeView
TreeView.walk_tree(function_body_expr(f, (Int, Int)))

produces

Now this is just a piece of the puzzle:

  • you’d need to do this recursively, e.g. ‘digging into’ g in the same way, while keeping track of the types of the arguments with which each function is called (i.e., which method is called). Maybe code_typed is more useful than code_lowered. Presumably, you’d do this up to some desired depth, or maybe recursing only into functions from a certain module. You’d also need to handle cycles.
  • the graph you get out of TreeView isn’t exactly what you want; you only care about methods (things like a, x, and = should not appear), and the depth of a node in the tree should be the recursion depth (so all of the methods in the example I gave should actually be at the same depth in the tree, and the methods that g calls should be nested under g).

So there’s still quite a lot of work to be done, but maybe this will get you (or somebody else) started. I think it would be very nice to be able to visualize call graphs without sampling.

4 Likes