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.checker
import v.parser
import v.depgraph
import v.markused
import v.depgraph
import v.callgraph
import v.dotgraph
pub struct Builder {
pub:
@ -52,6 +53,9 @@ pub fn new_builder(pref &pref.Preferences) Builder {
}
}
util.timing_set_should_print(pref.show_timings || pref.is_verbose)
if pref.show_callgraph || pref.show_depgraph {
dotgraph.start_digraph()
}
return Builder{
pref: pref
table: table
@ -180,6 +184,9 @@ pub fn (mut b Builder) resolve_deps() {
eprintln(deps_resolved.display())
eprintln('------------------------------------------')
}
if b.pref.show_depgraph {
depgraph.show(deps_resolved, b.pref.path)
}
cycles := deps_resolved.display_cycles()
if cycles.len > 1 {
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.walker
import v.pref
import strings
import v.dotgraph
// callgraph.show walks the AST, starting at main() and prints a DOT output describing the calls
// 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{
pref: pref
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"];
// 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())
mapper.dg.finish()
}
[heap]
@ -37,7 +33,7 @@ mut:
caller_name string
dot_caller_name string
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 {
@ -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.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"];')
}
m.dg.new_node(m.caller_name,
node_name: m.dot_caller_name
should_highlight: m.caller_name == 'main.main'
)
}
}
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,
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')
}
m.dg.new_edge(m.dot_caller_name, dot_called_name,
should_highlight: m.caller_name == 'main.main'
)
}
}
else {}
@ -134,13 +127,3 @@ fn (mut m Mapper) visit(node &ast.Node) ? {
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
module depgraph
import v.dotgraph
struct DepGraphNode {
pub mut:
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
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

@ -98,37 +98,36 @@ pub mut:
// verbosity VerboseLevel
is_verbose bool
// nofmt bool // disable vfmt
is_test bool // `v test string_test.v`
is_script bool // single file mode (`v program.v`), main function can be skipped
is_vsh bool // v script (`file.vsh`) file, the `os` module should be made global
is_livemain bool // main program that contains live/hot code
is_liveshared bool // a shared library, that will be used in a -live main program
is_shared bool // an ordinary shared library, -shared, no matter if it is live or not
is_prof bool // benchmark every function
profile_file string // the profile results will be stored inside profile_file
profile_no_inline bool // when true, [inline] functions would not be profiled
translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc
is_prod bool // use "-O2"
obfuscate bool // `v -obf program.v`, renames functions to "f_XXX"
is_repl bool
is_run bool
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_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
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_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
is_test bool // `v test string_test.v`
is_script bool // single file mode (`v program.v`), main function can be skipped
is_vsh bool // v script (`file.vsh`) file, the `os` module should be made global
is_livemain bool // main program that contains live/hot code
is_liveshared bool // a shared library, that will be used in a -live main program
is_shared bool // an ordinary shared library, -shared, no matter if it is live or not
is_prof bool // benchmark every function
profile_file string // the profile results will be stored inside profile_file
profile_no_inline bool // when true, [inline] functions would not be profiled
translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc
is_prod bool // use "-O2"
obfuscate bool // `v -obf program.v`, renames functions to "f_XXX"
is_repl 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).
// use cached modules to speed up compilation.
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.
is_stats bool // `v -stats file_test.v` will produce more detailed statistics for the tests that were run
sanitize bool // use Clang's new "-fsanitize" option
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_src_included bool // JS Backend: -sourcemap-src-included includes V source code in source map - default false
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_callgraph bool // -show-callgraph, print the program callgraph, in a Graphviz DOT format to stdout
show_depgraph bool // -show-depgraph, print the program module dependency graph, in a Graphviz DOT format to stdout
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 // when set, use cached modules to speed up subsequent compilations, at the cost of slower initial ones (while the modules are cached)
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
// TODO Convert this into a []string
cflags string // Additional options which will be passed to the C compiler.
// For example, passing -cflags -Os will cause the C compiler to optimize the generated binaries for size.
@ -141,7 +140,7 @@ pub mut:
building_v bool
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)
compress bool
compress bool // when set, use `upx` to compress the generated executable
// skip_builtin bool // Skips re-compilation of the builtin module
// to increase compilation time.
// 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' {
res.show_callgraph = true
}
'-show-depgraph' {
res.show_depgraph = true
}
'-dump-c-flags' {
res.dump_c_flags = cmdline.option(current_args, arg, '-')
i++