diff --git a/vlib/v/compiler_errors_test.v b/vlib/v/compiler_errors_test.v index 55f6f72f41..2159903a70 100644 --- a/vlib/v/compiler_errors_test.v +++ b/vlib/v/compiler_errors_test.v @@ -77,6 +77,7 @@ fn test_all() { global_run_dir := '$checker_dir/globals_run' run_dir := '$checker_dir/run' skip_unused_dir := 'vlib/v/tests/skip_unused' + trace_calls_dir := 'vlib/v/tests/trace_calls' // checker_tests := get_tests_in_dir(checker_dir, false) parser_tests := get_tests_in_dir(parser_dir, false) @@ -86,6 +87,7 @@ fn test_all() { module_tests := get_tests_in_dir(module_dir, true) run_tests := get_tests_in_dir(run_dir, false) skip_unused_dir_tests := get_tests_in_dir(skip_unused_dir, false) + trace_calls_dir_tests := get_tests_in_dir(trace_calls_dir, false) mut tasks := Tasks{ vexe: vexe label: 'all tests' @@ -113,6 +115,15 @@ fn test_all() { skip_unused_tasks.add('', skip_unused_dir, '-d no_backtrace -skip-unused run', '.skip_unused.run.out', skip_unused_dir_tests, false) skip_unused_tasks.run() + // + mut trace_calls_tasks := Tasks{ + vexe: vexe + parallel_jobs: 1 + label: '-trace-calls tests' + } + trace_calls_tasks.add('', trace_calls_dir, '-trace-calls run', '.run.out', trace_calls_dir_tests, + false) + trace_calls_tasks.run() } // if github_job == 'ubuntu-tcc' { diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 5b2440957a..5e78944dd8 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -158,7 +158,8 @@ 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 // when set, use `upx` to compress the generated executable + trace_calls bool // -trace-calls true = the transformer stage will generate and inject print calls for tracing function calls + compress bool // when set, use `upx` to compress the generated executable // generating_vh bool no_builtin bool // Skip adding the `builtin` module implicitly. The generated C code may not compile. enable_globals bool // allow __global for low level code @@ -429,6 +430,9 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin res.autofree = true res.build_options << arg } + '-trace-calls' { + res.trace_calls = true + } '-manualfree' { res.autofree = false res.build_options << arg diff --git a/vlib/v/tests/trace_calls/simple.run.out b/vlib/v/tests/trace_calls/simple.run.out new file mode 100644 index 0000000000..d0bec344ca --- /dev/null +++ b/vlib/v/tests/trace_calls/simple.run.out @@ -0,0 +1,6 @@ +> trace fn main() +> trace fn foo() +> trace fn bar() +> trace fn baz() +> trace fn (b Boo) call_1() +end diff --git a/vlib/v/tests/trace_calls/simple.vv b/vlib/v/tests/trace_calls/simple.vv new file mode 100644 index 0000000000..46b96fc5b3 --- /dev/null +++ b/vlib/v/tests/trace_calls/simple.vv @@ -0,0 +1,25 @@ +module main + +fn C.test() + +struct Boo {} + +fn (b Boo) call_1() {} + +fn foo() { + bar() +} + +fn bar() { + baz() +} + +fn baz() { + boo := Boo{} + boo.call_1() +} + +fn main() { + foo() + eprintln('end') +} diff --git a/vlib/v/tests/trace_calls/with_modules.run.out b/vlib/v/tests/trace_calls/with_modules.run.out new file mode 100644 index 0000000000..1c88b09c9c --- /dev/null +++ b/vlib/v/tests/trace_calls/with_modules.run.out @@ -0,0 +1,8 @@ +> trace fn init_os_args(argc int, argv &&u8) []string +> trace pub fn getwd() string +> trace fn main() +> trace fn f1() +> trace fn f2() +> trace fn f3() +> trace pub fn join_path(base string, dirs ...string) string +end diff --git a/vlib/v/tests/trace_calls/with_modules.vv b/vlib/v/tests/trace_calls/with_modules.vv new file mode 100644 index 0000000000..e01acbd9b9 --- /dev/null +++ b/vlib/v/tests/trace_calls/with_modules.vv @@ -0,0 +1,20 @@ +module main + +import os + +fn f1() { + f2() +} + +fn f2() { + f3() +} + +fn f3() { + _ := os.join_path('1', '2', '3') +} + +fn main() { + f1() + eprintln('end') +} diff --git a/vlib/v/transformer/transformer.v b/vlib/v/transformer/transformer.v index ccd576f0ff..73951bf5ba 100644 --- a/vlib/v/transformer/transformer.v +++ b/vlib/v/transformer/transformer.v @@ -226,6 +226,7 @@ pub fn (mut t Transformer) stmt(mut node ast.Stmt) ast.Stmt { } } ast.FnDecl { + t.fn_decl(mut node) t.index.indent(true) for mut stmt in node.stmts { stmt = t.stmt(mut stmt) @@ -1034,3 +1035,51 @@ pub fn (mut t Transformer) sql_expr(mut node ast.SqlExpr) ast.Expr { } return node } + +// fn_decl mutates `node`. +// if `pref.trace_calls` is true ast Nodes for `eprintln(...)` is prepended to the `FnDecl`'s +// stmts list to let the gen backend generate the target specific code for the print. +pub fn (mut t Transformer) fn_decl(mut node ast.FnDecl) { + if t.pref.trace_calls { + // Skip `C.fn()` and all of builtin + // builtin could probably be traced also but would need + // special cases for, at least, println/eprintln + if node.no_body || node.is_builtin { + return + } + call_expr := t.gen_trace_print_call_expr(node) + expr_stmt := ast.ExprStmt{ + expr: call_expr + } + node.stmts.prepend(expr_stmt) + } +} + +// gen_trace_print_expr_stmt generates an ast.CallExpr representation of a +// `eprint(...)` V code statement. +fn (t Transformer) gen_trace_print_call_expr(node ast.FnDecl) ast.CallExpr { + print_str := '> trace ' + node.stringify(t.table, node.mod, map[string]string{}) + + call_arg := ast.CallArg{ + expr: ast.StringLiteral{ + val: print_str + } + typ: ast.string_type_idx + } + args := [call_arg] + + fn_name := 'eprintln' + call_expr := ast.CallExpr{ + name: fn_name + args: args + mod: node.mod + pos: node.pos + language: node.language + scope: node.scope + comments: [ast.Comment{ + text: 'fn $node.short_name trace call' + }] + } + + return call_expr +}