v: support -show-depgraph in addition to -show-callgraph

pull/10986/head
Delyan Angelov 2021-07-28 16:41:32 +03:00
parent e3cf95b058
commit d25bd95a0e
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
6 changed files with 159 additions and 59 deletions

View File

@ -8,9 +8,10 @@ import v.ast
import v.vmod import v.vmod
import v.checker import v.checker
import v.parser import v.parser
import v.depgraph
import v.markused import v.markused
import v.depgraph
import v.callgraph import v.callgraph
import v.dotgraph
pub struct Builder { pub struct Builder {
pub: pub:
@ -52,6 +53,9 @@ pub fn new_builder(pref &pref.Preferences) Builder {
} }
} }
util.timing_set_should_print(pref.show_timings || pref.is_verbose) util.timing_set_should_print(pref.show_timings || pref.is_verbose)
if pref.show_callgraph || pref.show_depgraph {
dotgraph.start_digraph()
}
return Builder{ return Builder{
pref: pref pref: pref
table: table table: table
@ -180,6 +184,9 @@ pub fn (mut b Builder) resolve_deps() {
eprintln(deps_resolved.display()) eprintln(deps_resolved.display())
eprintln('------------------------------------------') eprintln('------------------------------------------')
} }
if b.pref.show_depgraph {
depgraph.show(deps_resolved, b.pref.path)
}
cycles := deps_resolved.display_cycles() cycles := deps_resolved.display_cycles()
if cycles.len > 1 { if cycles.len > 1 {
verror('error: import cycle detected between the following modules: \n' + cycles) verror('error: import cycle detected between the following modules: \n' + cycles)

View File

@ -3,7 +3,7 @@ module callgraph
import v.ast import v.ast
import v.ast.walker import v.ast.walker
import v.pref import v.pref
import strings import v.dotgraph
// callgraph.show walks the AST, starting at main() and prints a DOT output describing the calls // callgraph.show walks the AST, starting at main() and prints a DOT output describing the calls
// that function make transitively // that function make transitively
@ -11,18 +11,14 @@ pub fn show(mut table ast.Table, pref &pref.Preferences, ast_files []&ast.File)
mut mapper := &Mapper{ mut mapper := &Mapper{
pref: pref pref: pref
table: table table: table
dg: dotgraph.new('CallGraph', 'CallGraph for $pref.path', 'green')
} }
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"]; // Node14 [shape="box",label="PrivateBase",URL="$classPrivateBase.html"];
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"]; // Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
for afile in ast_files { for afile in ast_files {
walker.walk(mapper, afile) walker.walk(mapper, afile)
} }
mapper.sb.writeln('}') mapper.dg.finish()
println(mapper.sb.str())
} }
[heap] [heap]
@ -37,7 +33,7 @@ mut:
caller_name string caller_name string
dot_caller_name string dot_caller_name string
is_caller_used bool is_caller_used bool
sb strings.Builder = strings.new_builder(1024) dg dotgraph.DotGraph
} }
fn (mut m Mapper) dot_normalise_node_name(name string) string { fn (mut m Mapper) dot_normalise_node_name(name string) string {
@ -104,11 +100,10 @@ fn (mut m Mapper) visit(node &ast.Node) ? {
m.caller_name = m.fn_name(node.name, node.receiver.typ, node.is_method) 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) m.dot_caller_name = m.dot_fn_name(node.name, node.receiver.typ, node.is_method)
if m.is_caller_used { if m.is_caller_used {
if m.caller_name == 'main.main' { m.dg.new_node(m.caller_name,
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];') node_name: m.dot_caller_name
} else { should_highlight: m.caller_name == 'main.main'
m.sb.writeln('\t$m.dot_caller_name [shape="box",label="$m.caller_name"];') )
}
} }
} }
else {} else {}
@ -121,11 +116,9 @@ fn (mut m Mapper) visit(node &ast.Node) ? {
dot_called_name := m.dot_fn_name(node.name, node.receiver_type, dot_called_name := m.dot_fn_name(node.name, node.receiver_type,
node.is_method) node.is_method)
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"]; // Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
if m.caller_name == 'main.main' { m.dg.new_edge(m.dot_caller_name, dot_called_name,
m.sb.writeln('\t$m.dot_caller_name -> $dot_called_name [color="blue"];') should_highlight: m.caller_name == 'main.main'
} else { )
m.sb.writeln('\t$m.dot_caller_name -> $dot_called_name')
}
} }
} }
else {} else {}
@ -134,13 +127,3 @@ fn (mut m Mapper) visit(node &ast.Node) ? {
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

@ -5,6 +5,8 @@
// this implementation is specifically suited to ordering dependencies // this implementation is specifically suited to ordering dependencies
module depgraph module depgraph
import v.dotgraph
struct DepGraphNode { struct DepGraphNode {
pub mut: pub mut:
name string name string
@ -197,3 +199,20 @@ fn (mut nn NodeNames) is_part_of_cycle(name string, already_seen []string) (bool
nn.is_cycle[name] = false nn.is_cycle[name] = false
return false, new_already_seen return false, new_already_seen
} }
pub fn show(graph &DepGraph, path string) {
mut dg := dotgraph.new('ModGraph', 'ModGraph for $path', 'blue')
mbuiltin := 'builtin'
for node in graph.nodes {
is_main := node.name == 'main'
dg.new_node(node.name, should_highlight: is_main)
mut deps := node.deps.clone()
if node.name != mbuiltin && mbuiltin !in deps {
deps << mbuiltin
}
for dep in deps {
dg.new_edge(node.name, dep, should_highlight: is_main)
}
}
dg.finish()
}

View File

@ -0,0 +1,8 @@
module dotgraph
pub fn start_digraph() {
println('digraph G {')
C.atexit(fn () {
println('}')
})
}

View File

@ -0,0 +1,81 @@
module dotgraph
import strings
[heap]
struct DotGraph {
mut:
sb strings.Builder
}
pub fn new(name string, label string, color string) &DotGraph {
mut res := &DotGraph{
sb: strings.new_builder(1024)
}
res.writeln(' subgraph cluster_$name {')
res.writeln('\tedge [fontname="Helvetica",fontsize="10",labelfontname="Helvetica",labelfontsize="10",style="solid",color="black"];')
res.writeln('\tnode [fontname="Helvetica",fontsize="10",style="filled",fontcolor="black",fillcolor="white",color="black",shape="box"];')
res.writeln('\trankdir="LR";')
res.writeln('\tcolor="$color";')
res.writeln('\tlabel="$label";')
// Node14 [shape="box",label="PrivateBase",URL="$classPrivateBase.html"];
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
return res
}
pub fn (mut d DotGraph) writeln(line string) {
d.sb.writeln(line)
}
pub fn (mut d DotGraph) finish() {
d.sb.writeln(' }')
println(d.sb.str())
}
//
pub struct NewNodeConfig {
node_name string
should_highlight bool
tooltip string
ctx voidptr = voidptr(0)
name2node_fn FnLabel2NodeName = node_name
}
pub fn (mut d DotGraph) new_node(nlabel string, cfg NewNodeConfig) {
mut nname := cfg.name2node_fn(nlabel, cfg.ctx)
if cfg.node_name != '' {
nname = cfg.node_name
}
if cfg.should_highlight {
d.writeln('\t$nname [label="$nlabel",color="blue",height=0.2,width=0.4,fillcolor="#00FF00",tooltip="$cfg.tooltip",shape=oval];')
} else {
d.writeln('\t$nname [shape="box",label="$nlabel"];')
}
}
//
pub struct NewEdgeConfig {
should_highlight bool
ctx voidptr = voidptr(0)
name2node_fn FnLabel2NodeName = node_name
}
pub fn (mut d DotGraph) new_edge(source string, target string, cfg NewEdgeConfig) {
nsource := cfg.name2node_fn(source, cfg.ctx)
ntarget := cfg.name2node_fn(target, cfg.ctx)
if cfg.should_highlight {
d.writeln('\t$nsource -> $ntarget [color="blue"];')
} else {
d.writeln('\t$nsource -> $ntarget;')
}
}
//
pub type FnLabel2NodeName = fn (string, voidptr) string
pub fn node_name(name string, context voidptr) string {
return name.replace('.', '_')
}

View File

@ -112,21 +112,20 @@ pub mut:
obfuscate bool // `v -obf program.v`, renames functions to "f_XXX" obfuscate bool // `v -obf program.v`, renames functions to "f_XXX"
is_repl bool is_repl bool
is_run bool is_run bool
is_debug bool // turned on by -g or -cg, it tells v to pass -g to the C backend compiler.
is_vlines bool // turned on by -g (it slows down .tmp.c generation slightly).
// NB: passing -cg instead of -g will set is_vlines to false and is_debug to true, thus making v generate cleaner C files,
// which are sometimes easier to debug / inspect manually than the .tmp.c files by plain -g (when/if v line number generation breaks).
sanitize bool // use Clang's new "-fsanitize" option sanitize bool // use Clang's new "-fsanitize" option
is_debug bool // false by default, turned on by -g or -cg, it tells v to pass -g to the C backend compiler.
sourcemap bool // JS Backend: -sourcemap will create a source map - default false sourcemap bool // JS Backend: -sourcemap will create a source map - default false
sourcemap_inline bool = true // JS Backend: -sourcemap-inline will embed the source map in the generated JaaScript file - currently default true only implemented sourcemap_inline bool = true // JS Backend: -sourcemap-inline will embed the source map in the generated JaaScript file - currently default true only implemented
sourcemap_src_included bool // JS Backend: -sourcemap-src-included includes V source code in source map - default false sourcemap_src_included bool // JS Backend: -sourcemap-src-included includes V source code in source map - default false
is_vlines bool // turned on by -g, false by default (it slows down .tmp.c generation slightly).
show_cc bool // -showcc, print cc command show_cc bool // -showcc, print cc command
show_c_output bool // -show-c-output, print all cc output even if the code was compiled correctly show_c_output bool // -show-c-output, print all cc output even if the code was compiled correctly
show_callgraph bool // -show-callgraph, print the program callgraph, in a Graphviz DOT format to stdout show_callgraph bool // -show-callgraph, print the program callgraph, in a Graphviz DOT format to stdout
// NB: passing -cg instead of -g will set is_vlines to false and is_debug to true, thus making v generate cleaner C files, show_depgraph bool // -show-depgraph, print the program module dependency graph, in a Graphviz DOT format to stdout
// which are sometimes easier to debug / inspect manually than the .tmp.c files by plain -g (when/if v line number generation breaks). dump_c_flags string // `-dump-c-flags file.txt` - let V store all C flags, passed to the backend C compiler in `file.txt`, one C flag/value per line.
// use cached modules to speed up compilation. use_cache bool // when set, use cached modules to speed up subsequent compilations, at the cost of slower initial ones (while the modules are cached)
dump_c_flags string // `-dump-c-flags file.txt` - let V store all C flags, passed to the backend C compiler
// in `file.txt`, one C flag/value per line.
use_cache bool // = true
retry_compilation bool = true // retry the compilation with another C compiler, if tcc fails. retry_compilation bool = true // retry the compilation with another C compiler, if tcc fails.
is_stats bool // `v -stats file_test.v` will produce more detailed statistics for the tests that were run is_stats bool // `v -stats file_test.v` will produce more detailed statistics for the tests that were run
// TODO Convert this into a []string // TODO Convert this into a []string
@ -141,7 +140,7 @@ pub mut:
building_v bool building_v bool
autofree bool // `v -manualfree` => false, `v -autofree` => true; false by default for now. autofree bool // `v -manualfree` => false, `v -autofree` => true; false by default for now.
// Disabling `free()` insertion results in better performance in some applications (e.g. compilers) // Disabling `free()` insertion results in better performance in some applications (e.g. compilers)
compress bool compress bool // when set, use `upx` to compress the generated executable
// skip_builtin bool // Skips re-compilation of the builtin module // skip_builtin bool // Skips re-compilation of the builtin module
// to increase compilation time. // to increase compilation time.
// This is on by default, since a vast majority of users do not // This is on by default, since a vast majority of users do not
@ -444,6 +443,9 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences
'-show-callgraph' { '-show-callgraph' {
res.show_callgraph = true res.show_callgraph = true
} }
'-show-depgraph' {
res.show_depgraph = true
}
'-dump-c-flags' { '-dump-c-flags' {
res.dump_c_flags = cmdline.option(current_args, arg, '-') res.dump_c_flags = cmdline.option(current_args, arg, '-')
i++ i++