v.builder: implement -show-callgraph
parent
c8e671d88c
commit
45a15755b8
|
@ -17,6 +17,22 @@ pub fn (node &FnDecl) modname() string {
|
|||
return pamod
|
||||
}
|
||||
|
||||
// fkey returns a unique name of the function/method.
|
||||
// it is used in table.used_fns and v.markused.
|
||||
pub fn (node &FnDecl) fkey() string {
|
||||
if node.is_method {
|
||||
return '${int(node.receiver.typ)}.$node.name'
|
||||
}
|
||||
return node.name
|
||||
}
|
||||
|
||||
pub fn (node &CallExpr) fkey() string {
|
||||
if node.is_method {
|
||||
return '${int(node.receiver_type)}.$node.name'
|
||||
}
|
||||
return node.name
|
||||
}
|
||||
|
||||
// These methods are used only by vfmt, vdoc, and for debugging.
|
||||
pub fn (node &FnDecl) stringify(t &Table, cur_mod string, m2a map[string]string) string {
|
||||
mut f := strings.new_builder(30)
|
||||
|
|
|
@ -10,6 +10,7 @@ import v.checker
|
|||
import v.parser
|
||||
import v.depgraph
|
||||
import v.markused
|
||||
import v.callgraph
|
||||
|
||||
pub struct Builder {
|
||||
pub:
|
||||
|
@ -86,6 +87,9 @@ pub fn (mut b Builder) middle_stages() ? {
|
|||
if b.pref.skip_unused {
|
||||
markused.mark_used(mut b.table, b.pref, b.parsed_files)
|
||||
}
|
||||
if b.pref.show_callgraph {
|
||||
callgraph.show(mut b.table, b.pref, b.parsed_files)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut b Builder) front_and_middle_stages(v_files []string) ? {
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
module callgraph
|
||||
|
||||
import v.ast
|
||||
import v.ast.walker
|
||||
import v.pref
|
||||
import strings
|
||||
|
||||
// callgraph.show walks the AST, starting at main() and prints a DOT output describing the calls
|
||||
// that function make transitively
|
||||
pub fn show(mut table ast.Table, pref &pref.Preferences, ast_files []&ast.File) {
|
||||
mut mapper := &Mapper{
|
||||
pref: pref
|
||||
table: table
|
||||
}
|
||||
mapper.sb.writeln('digraph G {')
|
||||
mapper.sb.writeln('\tedge [fontname="Helvetica",fontsize="10",labelfontname="Helvetica",labelfontsize="10",style="solid",color="black"];')
|
||||
mapper.sb.writeln('\tnode [fontname="Helvetica",fontsize="10",style="filled",fontcolor="black",fillcolor="white",color="black",shape="box"];')
|
||||
mapper.sb.writeln('\trankdir="LR";')
|
||||
// Node14 [shape="box",label="PrivateBase",URL="$classPrivateBase.html"];
|
||||
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
|
||||
for afile in ast_files {
|
||||
walker.walk(mapper, afile)
|
||||
}
|
||||
mapper.sb.writeln('}')
|
||||
println(mapper.sb.str())
|
||||
}
|
||||
|
||||
[heap]
|
||||
struct Mapper {
|
||||
pos int
|
||||
mut:
|
||||
pref &pref.Preferences
|
||||
table &ast.Table
|
||||
file &ast.File = 0
|
||||
node &ast.Node = 0
|
||||
fn_decl &ast.FnDecl = 0
|
||||
caller_name string
|
||||
dot_caller_name string
|
||||
is_caller_used bool
|
||||
sb strings.Builder = strings.new_builder(1024)
|
||||
}
|
||||
|
||||
fn (mut m Mapper) dot_normalise_node_name(name string) string {
|
||||
res := name.replace_each([
|
||||
'.',
|
||||
'_',
|
||||
'==',
|
||||
'op_eq',
|
||||
'>=',
|
||||
'op_greater_eq',
|
||||
'<=',
|
||||
'op_lesser_eq',
|
||||
'>',
|
||||
'op_greater',
|
||||
'<',
|
||||
'op_lesser',
|
||||
'+',
|
||||
'op_plus',
|
||||
'-',
|
||||
'op_minus',
|
||||
'/',
|
||||
'op_divide',
|
||||
'*',
|
||||
'op_multiply',
|
||||
'^',
|
||||
'op_xor',
|
||||
'|',
|
||||
'op_or',
|
||||
'&',
|
||||
'op_and',
|
||||
])
|
||||
return res
|
||||
}
|
||||
|
||||
fn (mut m Mapper) fn_name(fname string, receiver_type ast.Type, is_method bool) string {
|
||||
if !is_method {
|
||||
return fname
|
||||
}
|
||||
rec_sym := m.table.get_type_symbol(receiver_type)
|
||||
return '${rec_sym.name}.$fname'
|
||||
}
|
||||
|
||||
fn (mut m Mapper) dot_fn_name(fname string, recv_type ast.Type, is_method bool) string {
|
||||
if is_method {
|
||||
return 'Node_method_' + int(recv_type).str() + '_' + m.dot_normalise_node_name(fname)
|
||||
}
|
||||
return 'Node_fn_' + m.dot_normalise_node_name(fname)
|
||||
}
|
||||
|
||||
fn (mut m Mapper) visit(node &ast.Node) ? {
|
||||
m.node = unsafe { node }
|
||||
match node {
|
||||
ast.File {
|
||||
m.file = unsafe { &node }
|
||||
}
|
||||
ast.Stmt {
|
||||
match node {
|
||||
ast.FnDecl {
|
||||
m.is_caller_used = true
|
||||
if m.pref.skip_unused {
|
||||
m.is_caller_used = m.table.used_fns[node.fkey()]
|
||||
}
|
||||
m.fn_decl = unsafe { &node }
|
||||
m.caller_name = m.fn_name(node.name, node.receiver.typ, node.is_method)
|
||||
m.dot_caller_name = m.dot_fn_name(node.name, node.receiver.typ, node.is_method)
|
||||
if m.is_caller_used {
|
||||
if m.caller_name == 'main.main' {
|
||||
m.sb.writeln('\t$m.dot_caller_name [label="fn main()",color="blue",height=0.2,width=0.4,fillcolor="#00FF00",tooltip="The main program entry point.",shape=oval];')
|
||||
} else {
|
||||
m.sb.writeln('\t$m.dot_caller_name [shape="box",label="$m.caller_name"];')
|
||||
}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
ast.Expr {
|
||||
match node {
|
||||
ast.CallExpr {
|
||||
if m.is_caller_used {
|
||||
dot_called_name := m.dot_fn_name(node.name, node.receiver_type,
|
||||
node.is_method)
|
||||
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
|
||||
if m.caller_name == 'main.main' {
|
||||
m.sb.writeln('\t$m.dot_caller_name -> $dot_called_name [color="blue"];')
|
||||
} else {
|
||||
m.sb.writeln('\t$m.dot_caller_name -> $dot_called_name')
|
||||
}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
mut fpath := ''
|
||||
if m.file != 0 {
|
||||
fpath = m.file.path
|
||||
}
|
||||
node_pos := node.position()
|
||||
fpos := '$fpath:$node_pos.line_nr:$node_pos.col:'
|
||||
println('$fpos $node.type_name() | $node')
|
||||
*/
|
|
@ -9,7 +9,7 @@ import v.util
|
|||
fn (mut g Gen) is_used_by_main(node ast.FnDecl) bool {
|
||||
mut is_used_by_main := true
|
||||
if g.pref.skip_unused {
|
||||
fkey := if node.is_method { '${int(node.receiver.typ)}.$node.name' } else { node.name }
|
||||
fkey := node.fkey()
|
||||
is_used_by_main = g.table.used_fns[fkey]
|
||||
$if trace_skip_unused_fns ? {
|
||||
println('> is_used_by_main: $is_used_by_main | node.name: $node.name | fkey: $fkey | node.is_method: $node.is_method')
|
||||
|
@ -21,8 +21,7 @@ fn (mut g Gen) is_used_by_main(node ast.FnDecl) bool {
|
|||
}
|
||||
} else {
|
||||
$if trace_skip_unused_fns_in_c_code ? {
|
||||
fkey := if node.is_method { '${int(node.receiver.typ)}.$node.name' } else { node.name }
|
||||
g.writeln('// trace_skip_unused_fns_in_c_code, $node.name, fkey: $fkey')
|
||||
g.writeln('// trace_skip_unused_fns_in_c_code, $node.name, fkey: $node.fkey()')
|
||||
}
|
||||
}
|
||||
return is_used_by_main
|
||||
|
|
|
@ -721,7 +721,7 @@ pub fn (mut g Gen) call_fn(node ast.CallExpr) {
|
|||
if !n.contains('.') {
|
||||
n = 'main.$n'
|
||||
}
|
||||
println('call fn ($n)')
|
||||
eprintln('call fn ($n)')
|
||||
addr := g.fn_addr[n]
|
||||
if addr == 0 {
|
||||
verror('fn addr of `$name` = 0')
|
||||
|
|
|
@ -351,11 +351,7 @@ fn all_fn_and_const(ast_files []&ast.File) (map[string]ast.FnDecl, map[string]as
|
|||
for node in file.stmts {
|
||||
match node {
|
||||
ast.FnDecl {
|
||||
fkey := if node.is_method {
|
||||
'${int(node.receiver.typ)}.$node.name'
|
||||
} else {
|
||||
node.name
|
||||
}
|
||||
fkey := node.fkey()
|
||||
all_fns[fkey] = node
|
||||
}
|
||||
ast.ConstDecl {
|
||||
|
|
|
@ -377,7 +377,7 @@ pub fn (mut w Walker) fn_decl(mut node ast.FnDecl) {
|
|||
if node.language == .c {
|
||||
return
|
||||
}
|
||||
fkey := if node.is_method { '${int(node.receiver.typ)}.$node.name' } else { node.name }
|
||||
fkey := node.fkey()
|
||||
if w.used_fns[fkey] {
|
||||
// This function is already known to be called, meaning it has been processed already.
|
||||
// Save CPU time and do nothing.
|
||||
|
@ -398,11 +398,7 @@ pub fn (mut w Walker) call_expr(mut node ast.CallExpr) {
|
|||
w.expr(node.left)
|
||||
w.or_block(node.or_block)
|
||||
//
|
||||
fn_name := if node.is_method {
|
||||
int(node.receiver_type).str() + '.' + node.name
|
||||
} else {
|
||||
node.name
|
||||
}
|
||||
fn_name := node.fkey()
|
||||
if w.used_fns[fn_name] {
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue