v.builder: implement -show-callgraph

pull/10922/head
Delyan Angelov 2021-07-24 10:25:16 +03:00
parent c8e671d88c
commit 45a15755b8
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
7 changed files with 172 additions and 15 deletions

View File

@ -17,6 +17,22 @@ pub fn (node &FnDecl) modname() string {
return pamod 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. // 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 { pub fn (node &FnDecl) stringify(t &Table, cur_mod string, m2a map[string]string) string {
mut f := strings.new_builder(30) mut f := strings.new_builder(30)

View File

@ -10,6 +10,7 @@ import v.checker
import v.parser import v.parser
import v.depgraph import v.depgraph
import v.markused import v.markused
import v.callgraph
pub struct Builder { pub struct Builder {
pub: pub:
@ -86,6 +87,9 @@ pub fn (mut b Builder) middle_stages() ? {
if b.pref.skip_unused { if b.pref.skip_unused {
markused.mark_used(mut b.table, b.pref, b.parsed_files) 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) ? { pub fn (mut b Builder) front_and_middle_stages(v_files []string) ? {

View File

@ -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')
*/

View File

@ -9,7 +9,7 @@ import v.util
fn (mut g Gen) is_used_by_main(node ast.FnDecl) bool { fn (mut g Gen) is_used_by_main(node ast.FnDecl) bool {
mut is_used_by_main := true mut is_used_by_main := true
if g.pref.skip_unused { 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] is_used_by_main = g.table.used_fns[fkey]
$if trace_skip_unused_fns ? { $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') 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 { } else {
$if trace_skip_unused_fns_in_c_code ? { $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: $node.fkey()')
g.writeln('// trace_skip_unused_fns_in_c_code, $node.name, fkey: $fkey')
} }
} }
return is_used_by_main return is_used_by_main

View File

@ -721,7 +721,7 @@ pub fn (mut g Gen) call_fn(node ast.CallExpr) {
if !n.contains('.') { if !n.contains('.') {
n = 'main.$n' n = 'main.$n'
} }
println('call fn ($n)') eprintln('call fn ($n)')
addr := g.fn_addr[n] addr := g.fn_addr[n]
if addr == 0 { if addr == 0 {
verror('fn addr of `$name` = 0') verror('fn addr of `$name` = 0')

View File

@ -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 { for node in file.stmts {
match node { match node {
ast.FnDecl { ast.FnDecl {
fkey := if node.is_method { fkey := node.fkey()
'${int(node.receiver.typ)}.$node.name'
} else {
node.name
}
all_fns[fkey] = node all_fns[fkey] = node
} }
ast.ConstDecl { ast.ConstDecl {

View File

@ -377,7 +377,7 @@ pub fn (mut w Walker) fn_decl(mut node ast.FnDecl) {
if node.language == .c { if node.language == .c {
return return
} }
fkey := if node.is_method { '${int(node.receiver.typ)}.$node.name' } else { node.name } fkey := node.fkey()
if w.used_fns[fkey] { if w.used_fns[fkey] {
// This function is already known to be called, meaning it has been processed already. // This function is already known to be called, meaning it has been processed already.
// Save CPU time and do nothing. // 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.expr(node.left)
w.or_block(node.or_block) w.or_block(node.or_block)
// //
fn_name := if node.is_method { fn_name := node.fkey()
int(node.receiver_type).str() + '.' + node.name
} else {
node.name
}
if w.used_fns[fn_name] { if w.used_fns[fn_name] {
return return
} }