From 40fff7b56ac17e96802c7836b6cd901c017d9160 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Fri, 5 Feb 2021 10:42:52 +0200 Subject: [PATCH] v.pref: support `v -skip-unused run examples/hello_world.v` --- vlib/v/checker/checker.v | 24 +---- vlib/v/checker/mark_used.v | 92 +++++++++++++++++++ .../mark_used_walker}/walker.v | 9 +- vlib/v/gen/c/fn.v | 13 ++- vlib/v/pref/pref.v | 4 + vlib/v/table/table.v | 1 + 6 files changed, 109 insertions(+), 34 deletions(-) create mode 100644 vlib/v/checker/mark_used.v rename vlib/v/{walker => checker/mark_used_walker}/walker.v (95%) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 021bdee055..8b61b43339 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -12,8 +12,6 @@ import v.pref import v.util import v.errors import v.pkgconfig -import v.walker -import time const ( max_nr_errors = 300 @@ -229,26 +227,8 @@ pub fn (mut c Checker) check_files(ast_files []ast.File) { } else if !has_main_fn { c.error('function `main` must be declared in the main module', token.Position{}) } - // Walk the tree starting at main() and mark all used fns. - if c.pref.experimental { - println('walking the ast') - // c.is_recursive = true - // c.fn_decl(mut c.table2.main_fn_decl_node) - t := time.ticks() - mut walker := walker.Walker{ - files: ast_files - } - for stmt in c.main_fn_decl_node.stmts { - walker.stmt(stmt) - } - // walker.fn_decl(mut c.table2.main_fn_decl_node) - println('time = ${time.ticks() - t}ms, nr used fns=$walker.used_fns.len') - - for key, _ in walker.used_fns { - println(key) - } - // println(walker.used_fns) - // c.walk(ast_files) + if c.pref.skip_unused { + c.mark_used(ast_files) } } diff --git a/vlib/v/checker/mark_used.v b/vlib/v/checker/mark_used.v new file mode 100644 index 0000000000..50ac93f73d --- /dev/null +++ b/vlib/v/checker/mark_used.v @@ -0,0 +1,92 @@ +module checker + +import v.ast +import v.table +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) { + // println('walking the ast') + // c.is_recursive = true + // c.fn_decl(mut c.table2.main_fn_decl_node) + + // c.timing_measure(@FN) + mut walker := mark_used_walker.Walker{ + files: ast_files + } + + // TODO: walking over the AST using roots != main.main, like `panic` + // for example, will potentially eliminate many cases where manual + // whitelisting is needed. + // TODO: use the full name of the functions in the map. For now the + // receiver type is skipped, so we can not distinguish between + // array.trim and string.trim etc. + for stmt in c.main_fn_decl_node.stmts { + walker.stmt(stmt) + } + // walker.fn_decl(mut c.table2.main_fn_decl_node) + // println('time = ${time.ticks() - t}ms, nr used fns=$walker.used_fns.len') + + /* + for key, _ in walker.used_fns { + println(key) + } + */ + c.table.used_fns = walker.used_fns + + // Whitelist some functions that are used by cgen directly: + c.table.used_fns['tos'] = true + c.table.used_fns['tos2'] = true + c.table.used_fns['v_panic'] = true + c.table.used_fns['panic'] = true + c.table.used_fns['eprintln'] = true + c.table.used_fns['print_backtrace_skipping_top_frames'] = true + c.table.used_fns['print_backtrace_skipping_top_frames_linux'] = true + c.table.used_fns['new_array_from_c_array'] = true + c.table.used_fns['__new_array_with_default'] = true + c.table.used_fns['__new_array'] = true + c.table.used_fns['vcalloc'] = true + c.table.used_fns['set_unsafe'] = true + c.table.used_fns['get_unsafe'] = true + c.table.used_fns['push'] = true + c.table.used_fns['ensure_cap'] = true + c.table.used_fns['v_realloc'] = true + c.table.used_fns['all_before'] = true + c.table.used_fns['all_after'] = true + c.table.used_fns['add'] = true + c.table.used_fns['isnil'] = true + c.table.used_fns['vstrlen'] = true + c.table.used_fns['trim_space'] = true + c.table.used_fns['malloc'] = true + c.table.used_fns['trim'] = true + c.table.used_fns['at'] = true + c.table.used_fns['replace'] = true + c.table.used_fns['index_'] = true + c.table.used_fns['index_after'] = true + c.table.used_fns['index_kmp'] = true + c.table.used_fns['substr'] = true + c.table.used_fns['clone'] = true + c.table.used_fns['free'] = true + c.table.used_fns['has_index'] = true + c.table.used_fns['key'] = true + c.table.used_fns['set'] = true + c.table.used_fns['get'] = true + c.table.used_fns['new_node'] = true + c.table.used_fns['eq'] = true + c.table.used_fns['ne'] = true + c.table.used_fns['lt'] = true + c.table.used_fns['gt'] = true + c.table.used_fns['le'] = true + c.table.used_fns['ge'] = true + c.table.used_fns['split_child'] = true + c.table.used_fns['bytes'] = true + c.table.used_fns['utf8_char_len'] = true + c.table.used_fns['utf8_str_visible_length'] = true + c.table.used_fns['main.main'] = true + c.table.used_fns['builtin_init'] = true + c.table.used_fns['memdup'] = true + // c.timing_measure(@FN) + + // println(walker.used_fns) + // c.walk(ast_files) +} diff --git a/vlib/v/walker/walker.v b/vlib/v/checker/mark_used_walker/walker.v similarity index 95% rename from vlib/v/walker/walker.v rename to vlib/v/checker/mark_used_walker/walker.v index 86f4eb2d44..14d7b58fd9 100644 --- a/vlib/v/walker/walker.v +++ b/vlib/v/checker/mark_used_walker/walker.v @@ -1,9 +1,8 @@ // Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license that can be found in the LICENSE file. -module walker +module mark_used_walker -// This module walks the entire program starting at fn main and marks used (called) -// functions. +// This module walks the entire program starting at fn main and marks used (called) functions. // Unused functions can be safely skipped by the backends to save CPU time and space. import v.ast @@ -91,7 +90,7 @@ pub fn (mut w Walker) fn_decl(mut node ast.FnDecl) { if node.language == .c { return } - println('fn decl $fn_name') + // println('fn decl $fn_name') w.used_fns[fn_name] = true for stmt in node.stmts { w.stmt(stmt) @@ -102,7 +101,7 @@ pub fn (mut w Walker) fn_decl(mut node ast.FnDecl) { pub fn (mut w Walker) call_expr(mut node ast.CallExpr) { fn_name := if node.is_method { node.receiver_type.str() + '.' + node.name } else { node.name } // fn_name := node.name - println('call_expr $fn_name') + // println('call_expr $fn_name') // if node.is_method { // println('M $node.name $node.receiver_type') //} diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 0d4beea635..dae80cf0c2 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -27,13 +27,12 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl, skip bool) { } } */ - if g.pref.experimental { - if f := g.table.find_fn(node.name) { - println('> usages: ${f.usages:-10} | node.name: $node.name') - if f.usages == 0 { - g.writeln('// fn $node.name UNUSED') - return - } + if g.pref.skip_unused { + is_used_by_main := g.table.used_fns[ node.name ] + // println('> is_used_by_main: $is_used_by_main | node.name: $node.name') + if !is_used_by_main { + g.writeln('// fn $node.name UNUSED') + return } } diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index cbc22d4263..27ee7d959d 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -136,6 +136,7 @@ pub mut: is_vweb bool // skip _ var warning in templates only_check_syntax bool // when true, just parse the files, then stop, before running checker experimental bool // enable experimental features + skip_unused bool // skip generating C code for functions, that are not used show_timings bool // show how much time each compiler stage took is_ios_simulator bool is_apk bool // build as Android .apk format @@ -221,6 +222,9 @@ pub fn parse_args(args []string) (&Preferences, string) { res.autofree = false res.build_options << arg } + '-skip-unused' { + res.skip_unused = true + } '-compress' { res.compress = true } diff --git a/vlib/v/table/table.v b/vlib/v/table/table.v index f9253f186f..a9a6bc2134 100644 --- a/vlib/v/table/table.v +++ b/vlib/v/table/table.v @@ -19,6 +19,7 @@ pub mut: fn_gen_types map[string][][]Type // for generic functions 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; } pub struct Fn {