From d77bb2f606936c5d69f9362cb40ddfc66f48958a Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 7 Feb 2021 01:01:37 +0200 Subject: [PATCH] all: improve -skip-unused, track consts, walk all AST nodes, support tests --- vlib/builtin/float.v | 2 +- vlib/v/checker/mark_used.v | 132 ++++--- vlib/v/checker/mark_used_walker/walker.v | 343 +++++++++++++++--- vlib/v/gen/c/cgen.v | 9 + vlib/v/gen/c/fn.v | 2 +- vlib/v/table/table.v | 1 + .../skip_unused/assert_passes_test.run.out | 0 .../assert_passes_test.skip_unused.run.out | 0 .../v/tests/skip_unused/assert_passes_test.vv | 3 + .../skip_unused/assert_works_test.run.out | 5 + .../assert_works_test.skip_unused.run.out | 5 + vlib/v/tests/skip_unused/assert_works_test.vv | 3 + 12 files changed, 410 insertions(+), 95 deletions(-) create mode 100644 vlib/v/tests/skip_unused/assert_passes_test.run.out create mode 100644 vlib/v/tests/skip_unused/assert_passes_test.skip_unused.run.out create mode 100644 vlib/v/tests/skip_unused/assert_passes_test.vv create mode 100644 vlib/v/tests/skip_unused/assert_works_test.run.out create mode 100644 vlib/v/tests/skip_unused/assert_works_test.skip_unused.run.out create mode 100644 vlib/v/tests/skip_unused/assert_works_test.vv diff --git a/vlib/builtin/float.v b/vlib/builtin/float.v index 602f91ecd1..4d336b8a85 100644 --- a/vlib/builtin/float.v +++ b/vlib/builtin/float.v @@ -26,7 +26,7 @@ pub fn (d float_literal) str() string { return f64(d).str() } -// strsci returns the `f64` as a `string` in scientific notation with `digit_num` deciamals displayed, max 17 digits. +// strsci returns the `f64` as a `string` in scientific notation with `digit_num` decimals displayed, max 17 digits. // Example: assert f64(1.234).strsci(3) == '1.234e+00' [inline] pub fn (x f64) strsci(digit_num int) string { diff --git a/vlib/v/checker/mark_used.v b/vlib/v/checker/mark_used.v index 3b3268101c..3b19859cc6 100644 --- a/vlib/v/checker/mark_used.v +++ b/vlib/v/checker/mark_used.v @@ -8,46 +8,47 @@ import v.checker.mark_used_walker // mark_used walks the AST, starting at main() and marks all used fns transitively fn (mut c Checker) mark_used(ast_files []ast.File) { util.timing_start(@METHOD) - mut walker := mark_used_walker.Walker{ - files: ast_files - } - - mut allfns := map[string]ast.FnDecl{} + util.timing_start('all_fn_and_const') + mut all_fns := map[string]ast.FnDecl{} + mut all_consts := map[string]ast.ConstField{} for i in 0 .. ast_files.len { file := unsafe { &ast_files[i] } for node in file.stmts { - if node is ast.FnDecl { - fkey := if node.is_method { - '${int(node.receiver.typ)}.$node.name' - } else { - node.name + match node { + ast.FnDecl { + fkey := if node.is_method { + '${int(node.receiver.typ)}.$node.name' + } else { + node.name + } + all_fns[fkey] = node } - allfns[fkey] = node + ast.ConstDecl { + for cfield in node.fields { + ckey := cfield.name + all_consts[ckey] = cfield + } + } + else {} } } } - all_fn_root_names := [ + util.timing_measure('all_fn_and_const') + + mut all_fn_root_names := [ 'main.main', '__new_array', '__new_array_with_default', 'new_array_from_c_array', - 'panic', 'memdup', 'vstrlen', - 'tos2', 'tos', + 'tos2', + 'tos3', 'isnil', - 'utf8_char_len', + /* utf8_str_visible_length is used by c/str.v */ 'utf8_str_visible_length', 'builtin_init', - 'print_backtrace_skipping_top_frames', - 'print_backtrace_skipping_top_frames_mac', - 'print_backtrace_skipping_top_frames_linux', - 'print_backtrace_skipping_top_frames_freebsd', - 'print_backtrace_skipping_top_frames_windows', - 'print_backtrace_skipping_top_frames_mingw', - 'print_backtrace_skipping_top_frames_msvc', - 'print_backtrace_skipping_top_frames_tcc', /* byteptr and charptr */ '3.vstring', '3.vstring_with_len', @@ -55,8 +56,6 @@ fn (mut c Checker) mark_used(ast_files []ast.File) { '4.vstring_with_len', /* string. methods */ '18.add', - '18.all_after', - '18.all_before', '18.trim_space', '18.replace', '18.clone', @@ -81,9 +80,13 @@ fn (mut c Checker) mark_used(ast_files []ast.File) { '19.add', /* other array methods */ '21.get', + '21.set', '21.get_unsafe', + '21.set_unsafe', + '21.slice', + '21.slice2', '59.get', - '65557.free', + '59.set', '65557.push', '65557.set', '65557.set_unsafe', @@ -91,28 +94,75 @@ fn (mut c Checker) mark_used(ast_files []ast.File) { 'os.getwd', 'os.init_os_args', ] - // println( allfns.keys() ) - for fn_name in all_fn_root_names { - walker.fn_decl(mut allfns[fn_name]) + + // implicit string builders are generated in auto_eq_methods.v + mut sb_mut_type := '' + if sbfn := c.table.find_fn('strings.new_builder') { + sb_mut_type = sbfn.return_type.set_nr_muls(1).str() + '.' } - $if trace_skip_unused ? { + for k, _ in all_fns { + if k.ends_with('.init') { + all_fn_root_names << k + } + if k.ends_with('.free') { + all_fn_root_names << k + } + if c.pref.is_test && (k.starts_with('test_') || k.contains('.test_')) { + all_fn_root_names << k + } + if sb_mut_type != '' && k.starts_with(sb_mut_type) { + all_fn_root_names << k + } + } + if c.pref.is_debug { + all_fn_root_names << 'panic_debug' + } + if c.pref.is_test { + all_fn_root_names << 'main.cb_assertion_ok' + all_fn_root_names << 'main.cb_assertion_failed' + if benched_tests_sym := c.table.find_type('main.BenchedTests') { + bts_type := benched_tests_sym.methods[0].params[0].typ + all_fn_root_names << '${bts_type}.testing_step_start' + all_fn_root_names << '${bts_type}.testing_step_end' + all_fn_root_names << '${bts_type}.end_testing' + all_fn_root_names << 'main.start_testing' + } + } + + mut walker := mark_used_walker.Walker{ + files: ast_files + all_fns: all_fns + all_consts: all_consts + } + // println( all_fns.keys() ) + walker.mark_root_fns(all_fn_root_names) + + if walker.n_asserts > 0 { + walker.fn_decl(mut all_fns['__print_assert_failure']) + } + if walker.n_maps > 0 { + for k, mut mfn in all_fns { + if k == 'new_map_2' || k.starts_with('map_') || k.ends_with('set_1') + || k.ends_with('exists_1')|| k.ends_with('get_1') { + walker.fn_decl(mut mfn) + } + } + } + + $if trace_skip_unused_fn_names ? { for key, _ in walker.used_fns { println('> used fn key: $key') } } c.table.used_fns = walker.used_fns - // - c.table.used_fns['term.can_show_color_on_stdin'] = true - c.table.used_fns['term.can_show_color_on_stdout'] = true - c.table.used_fns['term.can_show_color_on_stderr'] = true - // - c.table.used_fns['main.can_use_relative_paths'] = true - // - // eprintln('>>> c.table.used_fns: $c.table.used_fns') - util.timing_measure(@METHOD) + c.table.used_consts = walker.used_consts - // println(walker.used_fns) - // c.walk(ast_files) + $if trace_skip_unused ? { + eprintln('>> c.table.used_fns: $c.table.used_fns.keys()') + eprintln('>> c.table.used_consts: $c.table.used_consts.keys()') + eprintln('>> walker.n_maps: $walker.n_maps') + } + util.timing_measure(@METHOD) } diff --git a/vlib/v/checker/mark_used_walker/walker.v b/vlib/v/checker/mark_used_walker/walker.v index 24af677e43..b73b92def6 100644 --- a/vlib/v/checker/mark_used_walker/walker.v +++ b/vlib/v/checker/mark_used_walker/walker.v @@ -8,25 +8,61 @@ import v.ast pub struct Walker { pub mut: - used_fns map[string]bool // used_fns['println'] == true + used_fns map[string]bool // used_fns['println'] == true + used_consts map[string]bool // used_consts['os.args'] == true + n_maps int + n_asserts int mut: - files []ast.File + files []ast.File + all_fns map[string]ast.FnDecl + all_consts map[string]ast.ConstField } -/* -fn (mut w Walker) walk_files(ast_files []ast.File) { - t := time.ticks() -*/ +pub fn (mut w Walker) mark_fn_as_used(fkey string) { + $if trace_skip_unused_marked ? { + eprintln(' fn > |$fkey|') + } + w.used_fns[fkey] = true +} + +pub fn (mut w Walker) mark_const_as_used(ckey string) { + $if trace_skip_unused_marked ? { + eprintln(' const > |$ckey|') + } + w.used_consts[ckey] = true + cfield := w.all_consts[ckey] or { return } + w.expr(cfield.expr) +} + +pub fn (mut w Walker) mark_root_fns(all_fn_root_names []string) { + for fn_name in all_fn_root_names { + if fn_name !in w.used_fns { + $if trace_skip_unused_roots ? { + println('>>>> $fn_name uses: ') + } + w.fn_decl(mut w.all_fns[fn_name]) + } + } +} pub fn (mut w Walker) stmt(node ast.Stmt) { match mut node { + ast.AssertStmt { + w.expr(node.expr) + w.n_asserts++ + } ast.AssignStmt { - for l in node.left { - w.expr(l) - } - for r in node.right { - w.expr(r) - } + w.exprs(node.left) + w.exprs(node.right) + } + ast.Block { + w.stmts(node.stmts) + } + ast.CompFor { + w.stmts(node.stmts) + } + ast.ConstDecl { + w.const_fields(node.fields) } ast.ExprStmt { w.expr(node.expr) @@ -34,92 +70,295 @@ pub fn (mut w Walker) stmt(node ast.Stmt) { ast.FnDecl { w.fn_decl(mut node) } + ast.ForCStmt { + w.expr(node.cond) + w.stmt(node.inc) + w.stmts(node.stmts) + } + ast.ForInStmt { + w.expr(node.cond) + w.expr(node.high) + w.stmts(node.stmts) + } ast.ForStmt { w.expr(node.cond) - for stmt in node.stmts { - w.stmt(stmt) + w.stmts(node.stmts) + } + ast.GoStmt { + w.expr(node.call_expr) + } + ast.Return { + w.exprs(node.exprs) + } + ast.SqlStmt { + w.expr(node.db_expr) + w.expr(node.where_expr) + w.exprs(node.update_exprs) + } + ast.StructDecl { + w.struct_fields(node.fields) + } + ast.DeferStmt { + w.stmts(node.stmts) + } + ast.GlobalDecl { + for gf in node.fields { + if gf.has_expr { + w.expr(gf.expr) + } } } - else {} + ast.BranchStmt {} + ast.EnumDecl {} + ast.GotoLabel {} + ast.GotoStmt {} + ast.HashStmt {} + ast.Import {} + ast.InterfaceDecl {} + ast.Module {} + ast.TypeDecl {} + } +} + +fn (mut w Walker) defer_stmts(stmts []ast.DeferStmt) { + for stmt in stmts { + w.stmts(stmt.stmts) + } +} + +fn (mut w Walker) stmts(stmts []ast.Stmt) { + for stmt in stmts { + w.stmt(stmt) + } +} + +fn (mut w Walker) exprs(exprs []ast.Expr) { + for expr in exprs { + w.expr(expr) } } fn (mut w Walker) expr(node ast.Expr) { match mut node { + ast.AnonFn { + w.fn_decl(mut node.decl) + } + ast.Assoc { + w.exprs(node.exprs) + } + ast.ArrayInit { + w.expr(node.len_expr) + w.expr(node.cap_expr) + w.expr(node.default_expr) + w.exprs(node.exprs) + } + ast.ArrayDecompose { + w.expr(node.expr) + } ast.CallExpr { w.call_expr(mut node) } + ast.CastExpr { + w.expr(node.expr) + w.expr(node.arg) + } + ast.ChanInit { + w.expr(node.cap_expr) + } + ast.ConcatExpr { + w.exprs(node.vals) + } + ast.ComptimeSelector { + w.expr(node.left) + w.expr(node.field_expr) + } + ast.ComptimeCall { + w.expr(node.left) + if node.is_vweb { + w.stmts(node.vweb_tmpl.stmts) + } + } ast.GoExpr { w.expr(node.go_stmt.call_expr) } ast.IndexExpr { w.expr(node.left) w.expr(node.index) + w.or_block(node.or_expr) + } + ast.InfixExpr { + w.expr(node.left) + w.expr(node.right) + w.or_block(node.or_block) + } + ast.IfGuardExpr { + w.expr(node.expr) } ast.IfExpr { + w.expr(node.left) for b in node.branches { w.expr(b.cond) - for stmt in b.stmts { - w.stmt(stmt) - } + w.stmts(b.stmts) } } + ast.Ident { + if node.kind == .constant { + w.mark_const_as_used(node.name) + } + } + ast.Likely { + w.expr(node.expr) + } + ast.MapInit { + w.exprs(node.keys) + w.exprs(node.vals) + w.n_maps++ + } ast.MatchExpr { w.expr(node.cond) for b in node.branches { - for expr in b.exprs { - w.expr(expr) - } - for stmt in b.stmts { - w.stmt(stmt) - } + w.exprs(b.exprs) + w.stmts(b.stmts) } } - else {} + ast.None { + w.mark_fn_as_used('opt_none') + } + ast.ParExpr { + w.expr(node.expr) + } + ast.PrefixExpr { + w.expr(node.right) + } + ast.PostfixExpr { + w.expr(node.expr) + } + ast.RangeExpr { + if node.has_low { + w.expr(node.low) + } + if node.has_high { + w.expr(node.high) + } + } + ast.SizeOf { + w.expr(node.expr) + } + ast.StringInterLiteral { + w.exprs(node.exprs) + } + ast.SelectorExpr { + w.expr(node.expr) + } + ast.SqlExpr { + w.expr(node.db_expr) + w.expr(node.offset_expr) + w.expr(node.order_expr) + w.expr(node.limit_expr) + w.expr(node.where_expr) + } + ast.StructInit { + // eprintln('>>>> ast.StructInit: $node') + w.expr(node.update_expr) + for sif in node.fields { + w.expr(sif.expr) + } + for sie in node.embeds { + w.expr(sie.expr) + } + } + ast.TypeOf { + w.expr(node.expr) + } + /// + ast.AsCast { + w.expr(node.expr) + } + ast.AtExpr {} + ast.BoolLiteral {} + ast.FloatLiteral {} + ast.CharLiteral {} + ast.IntegerLiteral {} + ast.StringLiteral {} + ast.CTempVar { + w.expr(node.orig) + } + ast.Comment {} + ast.EnumVal {} + ast.LockExpr { + w.stmts(node.stmts) + } + ast.OffsetOf {} + ast.OrExpr { + w.or_block(node) + } + ast.SelectExpr { + for branch in node.branches { + w.stmt(branch.stmt) + w.stmts(branch.stmts) + } + } + ast.Type {} + ast.UnsafeExpr { + w.expr(node.expr) + } } } 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 } 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. return } - if node.language == .c { - return - } - w.used_fns[fkey] = true - for stmt in node.stmts { - w.stmt(stmt) - } + w.mark_fn_as_used(fkey) + w.stmts(node.stmts) + w.defer_stmts(node.defer_stmts) } pub fn (mut w Walker) call_expr(mut node ast.CallExpr) { + if node.language == .c { + return + } + w.expr(node.left) + for arg in node.args { + w.expr(arg.expr) + } + w.or_block(node.or_block) + // fn_name := if node.is_method { node.receiver_type.str() + '.' + node.name } else { node.name } - // fn_name := node.name - // println('call_expr $fn_name') - // if node.is_method { - // println('M $node.name $node.receiver_type') - //} if w.used_fns[fn_name] { return } - // w.used_fns[fn_name] = true - // Find the FnDecl for this CallExpr, mark the function as used, and process - // all its statements. - loop: for file in w.files { - for stmt in file.stmts { - if stmt is ast.FnDecl { - if stmt.name == node.name - && (!node.is_method || (node.receiver_type == stmt.receiver.typ)) { - w.used_fns[fn_name] = true - for fn_stmt in stmt.stmts { - w.stmt(fn_stmt) - } - break loop - } - } + stmt := w.all_fns[fn_name] or { return } + if stmt.name == node.name { + if !node.is_method || (node.receiver_type == stmt.receiver.typ) { + w.mark_fn_as_used(fn_name) + w.stmts(stmt.stmts) } } } + +pub fn (mut w Walker) struct_fields(sfields []ast.StructField) { + for sf in sfields { + if sf.has_default_expr { + w.expr(sf.default_expr) + } + } +} + +pub fn (mut w Walker) const_fields(cfields []ast.ConstField) { + for cf in cfields { + w.expr(cf.expr) + } +} + +pub fn (mut w Walker) or_block(node ast.OrExpr) { + if node.kind == .block { + w.stmts(node.stmts) + } +} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index fc97fff218..4912172e0f 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -4725,6 +4725,15 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) { g.inside_const = false } for field in node.fields { + if g.pref.skip_unused { + if field.name !in g.table.used_consts { + $if trace_skip_unused_consts ? { + eprintln('>> skipping unused const name: $field.name') + } + continue + } + } + name := c_name(field.name) // TODO hack. Cut the generated value and paste it into definitions. pos := g.out.len diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 4162fb5d0a..88766e4014 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -30,7 +30,7 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl, skip bool) { if g.pref.skip_unused { fkey := if node.is_method { '${int(node.receiver.typ)}.$node.name' } else { node.name } is_used_by_main := g.table.used_fns[fkey] - $if trace_skip_unused ? { + $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') } if !is_used_by_main { diff --git a/vlib/v/table/table.v b/vlib/v/table/table.v index a9a6bc2134..3bfe4e5382 100644 --- a/vlib/v/table/table.v +++ b/vlib/v/table/table.v @@ -20,6 +20,7 @@ pub mut: cmod_prefix string // needed for table.type_to_str(Type) while vfmt; contains `os.` is_fmt bool used_fns map[string]bool // filled in by the checker, when pref.skip_unused = true; + used_consts map[string]bool // filled in by the checker, when pref.skip_unused = true; } pub struct Fn { diff --git a/vlib/v/tests/skip_unused/assert_passes_test.run.out b/vlib/v/tests/skip_unused/assert_passes_test.run.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vlib/v/tests/skip_unused/assert_passes_test.skip_unused.run.out b/vlib/v/tests/skip_unused/assert_passes_test.skip_unused.run.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vlib/v/tests/skip_unused/assert_passes_test.vv b/vlib/v/tests/skip_unused/assert_passes_test.vv new file mode 100644 index 0000000000..3e6017a2fa --- /dev/null +++ b/vlib/v/tests/skip_unused/assert_passes_test.vv @@ -0,0 +1,3 @@ +fn test_abc() { + assert 'abc' == 'abc' +} diff --git a/vlib/v/tests/skip_unused/assert_works_test.run.out b/vlib/v/tests/skip_unused/assert_works_test.run.out new file mode 100644 index 0000000000..bf4097ed62 --- /dev/null +++ b/vlib/v/tests/skip_unused/assert_works_test.run.out @@ -0,0 +1,5 @@ +vlib/v/tests/skip_unused/assert_works_test.vv:2: fn test_abc + > assert 'abc' == 'xyz' + Left value: abc + Right value: xyz + diff --git a/vlib/v/tests/skip_unused/assert_works_test.skip_unused.run.out b/vlib/v/tests/skip_unused/assert_works_test.skip_unused.run.out new file mode 100644 index 0000000000..bf4097ed62 --- /dev/null +++ b/vlib/v/tests/skip_unused/assert_works_test.skip_unused.run.out @@ -0,0 +1,5 @@ +vlib/v/tests/skip_unused/assert_works_test.vv:2: fn test_abc + > assert 'abc' == 'xyz' + Left value: abc + Right value: xyz + diff --git a/vlib/v/tests/skip_unused/assert_works_test.vv b/vlib/v/tests/skip_unused/assert_works_test.vv new file mode 100644 index 0000000000..8f7bc38eab --- /dev/null +++ b/vlib/v/tests/skip_unused/assert_works_test.vv @@ -0,0 +1,3 @@ +fn test_abc() { + assert 'abc' == 'xyz' +}