v.builder: implement -show-callgraph
parent
c8e671d88c
commit
45a15755b8
|
@ -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)
|
||||||
|
|
|
@ -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) ? {
|
||||||
|
|
|
@ -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 {
|
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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue