diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 7d54455b8a..2e2510dbea 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -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) diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index a9379732d0..f3b2c77b0a 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -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) ? { diff --git a/vlib/v/callgraph/callgraph.v b/vlib/v/callgraph/callgraph.v new file mode 100644 index 0000000000..e5c2b4b691 --- /dev/null +++ b/vlib/v/callgraph/callgraph.v @@ -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') +*/ diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index b8d65c1531..d217734769 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -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 diff --git a/vlib/v/gen/native/amd64.v b/vlib/v/gen/native/amd64.v index af1bf1d168..2e945b050e 100644 --- a/vlib/v/gen/native/amd64.v +++ b/vlib/v/gen/native/amd64.v @@ -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') diff --git a/vlib/v/markused/markused.v b/vlib/v/markused/markused.v index 0743eb3718..2d911394f6 100644 --- a/vlib/v/markused/markused.v +++ b/vlib/v/markused/markused.v @@ -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 { diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index e2c244f893..4031589ae9 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -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 }