Reputation: 886
I am playing around with Julia's macros. One thing I am particularly curious about is the ability to extract a function's reachable call graph without having to fully compile the code. By 'reachable call graph', I mean all of the functions found inside the body of a function that are potentially reachable, and the functions in those functions, and so on.
For example:
do_something(x::Int) = println(x*2)
do_something(x::String) = println(x)
function foo(a, b)::Bool
do_something_with(a)
do_something_with(b)
return true
end
# Ideally something like this would be accessible from Base
functions_in_codebase = [:do_something, :foo]
# Not correct, but gets the idea across
macro access_call_graph(f)
tokens = f.text.tokens
graph = [f]
for t in tokens
go_deeper = t in functions_in_codebase
!go_deeper && append!(graph, access_call_graph(get_function_for(t))...)
end
return graph
end
access_call_graph(foo)
# Should get something like the following, nesting notwithstanding:
# foo
# do_something, do_something
This is pretty janky, but being able to access the call graph at parse-time, even if only in terms of potentially reachable functions, would be extremely useful for what I'm trying to do. If I have to fully compile the code in order for something like this to work, that would largely defeat the benefit.
Is something like this possible?
Upvotes: 3
Views: 97
Reputation: 42214
@code_lowered
could be useful:
julia> code = @code_lowered foo(1,"2")
CodeInfo(
1 ─ %1 = Main.Bool
│ Main.do_something_with(a)
│ Main.do_something_with(b)
│ %4 = Base.convert(%1, true)
│ %5 = Core.typeassert(%4, %1)
└── return %5
)
To get function calls you could do:
julia> code.first.code
5-element Vector{Any}:
:(Main.do_something_with)
:((%1)(_2))
:(Main.do_something_with)
:((%3)(_3))
:(return true)
Upvotes: 3