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.