diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8b1a1ad56..a7be8fd29d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -254,7 +254,7 @@ jobs: ## sudo apt-get install --quiet -y libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libsdl2-image-dev - name: Build V run: make -j4 && ./v -cc gcc -cg -cflags -Werror -o v cmd/v - - name: Valgrind + - name: Valgrind v.c run: valgrind --error-exitcode=1 ./v -o v.c cmd/v - name: Run sanitizers run: | diff --git a/doc/docs.md b/doc/docs.md index 7187fdfc7c..881d84cc82 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -757,7 +757,7 @@ println(array_2) // [0, 1, 3, 5, 4] ### Fixed size arrays -V also supports arrays with fixed size. Unlike ordinary arrays, their +V also supports arrays with fixed size. Unlike ordinary arrays, their length is constant. You cannot append elements to them, nor shrink them. You can only modify their elements in place. @@ -1661,11 +1661,16 @@ fn sqr(n int) int { return n * n } +fn cube(n int) int { + return n * n * n +} + fn run(value int, op fn (int) int) int { return op(value) } fn main() { + // Functions can be passed to other functions println(run(5, sqr)) // "25" // Anonymous functions can be declared inside other functions: double_fn := fn (n int) int { @@ -1676,6 +1681,11 @@ fn main() { res := run(5, fn (n int) int { return n + n }) + // You can even have an array/map of functions: + fns := [sqr, cube] + println(fns[0](10)) // "100" + fns_map := { 'sqr': sqr, 'cube': cube} + println(fns_map['cube'](2)) // "8" } ``` @@ -3867,8 +3877,8 @@ if x { } my_label: ``` -`goto` should be avoided when `for` can be used instead. In particular, -[labelled break](#labelled-break--continue) can be used to break out of +`goto` should be avoided when `for` can be used instead. In particular, +[labelled break](#labelled-break--continue) can be used to break out of a nested loop. # Appendices diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 3ce8b17505..cafb872151 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -329,6 +329,7 @@ pub: generic_params []GenericParam is_direct_arr bool // direct array access attrs []table.Attr + skip_gen bool // this function doesn't need to be generated (for example [if foo]) pub mut: stmts []Stmt return_type table.Type @@ -1518,3 +1519,9 @@ pub fn ex2fe(x Expr) table.FExpr { unsafe { C.memcpy(&res, &x, sizeof(table.FExpr)) } return res } + +// experimental ast.Table +pub struct Table { + // pub mut: + // main_fn_decl_node FnDecl +} diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index 8c8b5d9b4a..9fd9cfdd16 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -27,6 +27,7 @@ pub mut: parsed_files []ast.File cached_msvc MsvcResult table &table.Table + table2 &ast.Table timers &util.Timers = util.new_timers(false) ccoptions CcompilerOptions } @@ -36,6 +37,7 @@ pub fn new_builder(pref &pref.Preferences) Builder { compiled_dir := if os.is_dir(rdir) { rdir } else { os.dir(rdir) } mut table := table.new_table() table.is_fmt = false + table2 := &ast.Table{} if pref.use_color == .always { util.emanager.set_support_color(true) } @@ -53,7 +55,8 @@ pub fn new_builder(pref &pref.Preferences) Builder { return Builder{ pref: pref table: table - checker: checker.new_checker(table, pref) + table2: table2 + checker: checker.new_checker(table, table2, pref) global_scope: &ast.Scope{ parent: 0 } @@ -235,7 +238,7 @@ fn module_path(mod string) string { return mod.replace('.', os.path_separator) } -// TODO: try to merge this & util.module functions to create a +// TODO: try to merge this & util.module functions to create a // reliable multi use function. see comments in util/module.v pub fn (b &Builder) find_module_path(mod string, fpath string) ?string { // support @VROOT/v.mod relative paths: diff --git a/vlib/v/builder/c.v b/vlib/v/builder/c.v index 8e26bb8d9c..2518c3ea79 100644 --- a/vlib/v/builder/c.v +++ b/vlib/v/builder/c.v @@ -22,7 +22,7 @@ pub fn (mut b Builder) gen_c(v_files []string) string { b.print_warnings_and_errors() // TODO: move gen.cgen() to c.gen() b.timing_start('C GEN') - res := c.gen(b.parsed_files, b.table, b.pref) + res := c.gen(b.parsed_files, b.table, b.table2, b.pref) b.timing_measure('C GEN') // println('cgen done') // println(res) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 402daf733f..9596d4fb63 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -12,6 +12,8 @@ import v.pref import v.util import v.errors import v.pkgconfig +import v.walker +import time const ( max_nr_errors = 300 @@ -36,6 +38,7 @@ pub struct Checker { pref &pref.Preferences // Preferences shared from V struct pub mut: table &table.Table + table2 &ast.Table file &ast.File = 0 nr_errors int nr_warnings int @@ -63,6 +66,7 @@ pub mut: skip_flags bool // should `#flag` and `#include` be skipped cur_generic_types []table.Type mut: + files []ast.File expr_level int // to avoid infinite recursion segfaults due to compiler bugs inside_sql bool // to handle sql table fields pseudo variables cur_orm_ts table.TypeSymbol @@ -76,15 +80,18 @@ mut: timers &util.Timers = util.new_timers(false) comptime_fields_type map[string]table.Type fn_scope &ast.Scope = voidptr(0) + used_fns map[string]bool // used_fns['println'] == true + main_fn_decl_node ast.FnDecl } -pub fn new_checker(table &table.Table, pref &pref.Preferences) Checker { +pub fn new_checker(table &table.Table, table2 &ast.Table, pref &pref.Preferences) Checker { mut timers_should_print := false $if time_checking ? { timers_should_print = true } return Checker{ table: table + table2: table2 pref: pref cur_fn: 0 timers: util.new_timers(timers_should_print) @@ -140,6 +147,7 @@ pub fn (mut c Checker) check2(ast_file &ast.File) []errors.Error { } pub fn (mut c Checker) check_files(ast_files []ast.File) { + // c.files = ast_files mut has_main_mod_file := false mut has_main_fn := false mut files_from_main_module := []&ast.File{} @@ -223,6 +231,27 @@ 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) + } } // do checks specific to files in main module @@ -1303,7 +1332,7 @@ fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ table.Type, call_e ast.Ident { if arg_expr.kind == .function { func := c.table.find_fn(arg_expr.name) or { - c.error('$arg_expr.name is not exist', arg_expr.pos) + c.error('$arg_expr.name does not exist', arg_expr.pos) return } if func.params.len > 1 { @@ -1772,6 +1801,7 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { call_expr.name = name_prefixed found = true f = f1 + c.table.fns[name_prefixed].is_used = true } } if !found && call_expr.left is ast.IndexExpr { @@ -1805,6 +1835,7 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { if f1 := c.table.find_fn(fn_name) { found = true f = f1 + c.table.fns[fn_name].is_used = true } } if c.pref.is_script && !found { @@ -1816,6 +1847,7 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { call_expr.name = os_name found = true f = f1 + c.table.fns[os_name].is_used = true } } // check for arg (var) of fn type @@ -3827,7 +3859,7 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) table.Type { ...pref_ is_vweb: true } - mut c2 := new_checker(c.table, pref2) + mut c2 := new_checker(c.table, c.table2, pref2) c2.check(node.vweb_tmpl) mut i := 0 // tmp counter var for skipping first three tmpl vars for k, _ in c2.file.scope.children[0].objects { @@ -5655,9 +5687,12 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { if node.language == .v && !c.is_builtin_mod { c.check_valid_snake_case(node.name, 'function name', node.pos) } + if node.name == 'main.main' { + c.main_fn_decl_node = node + } if node.return_type != table.void_type { for attr in node.attrs { - if attr.is_ctdefine { + if attr.is_comptime_define { c.error('only functions that do NOT return values can have `[if $attr.name]` tags', node.pos) break diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 93f0da6437..b118aab103 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -32,6 +32,7 @@ struct Gen { module_built string mut: table &table.Table + table2 &ast.Table out strings.Builder cheaders strings.Builder includes strings.Builder // all C #includes required by V modules @@ -150,9 +151,10 @@ mut: force_main_console bool // true when [console] used on fn main() as_cast_type_names map[string]string // table for type name lookup in runtime (for __as_cast) obf_table map[string]string + // main_fn_decl_node ast.FnDecl } -pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string { +pub fn gen(files []ast.File, table &table.Table, table2 &ast.Table, pref &pref.Preferences) string { // println('start cgen2') mut module_built := '' if pref.build_mode == .build_module { @@ -191,6 +193,7 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string enum_typedefs: strings.new_builder(100) sql_buf: strings.new_builder(100) table: table + table2: table2 pref: pref fn_decl: 0 is_autofree: true diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index be6910b4c7..cbef70c4ed 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -15,6 +15,23 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl, skip bool) { // || node.no_body { return } + // Skip [if xxx] if xxx is not defined + for attr in node.attrs { + if !attr.is_comptime_define { + continue + } + if attr.name !in g.pref.compile_defines_all { + // println('skipping [if]') + return + } + } + if f := g.table.find_fn(node.name) { + if !f.is_used { + g.writeln('// fn $node.name UNUSED') + // return + } + } + g.returned_var_name = '' // old_g_autofree := g.is_autofree diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 73bebe724e..a261e40542 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -828,7 +828,7 @@ fn (mut p Parser) attributes() { p.error_with_pos('duplicate attribute `$attr.name`', start_pos.extend(p.prev_tok.position())) return } - if attr.is_ctdefine { + if attr.is_comptime_define { if has_ctdefine { p.error_with_pos('only one `[if flag]` may be applied at a time `$attr.name`', start_pos.extend(p.prev_tok.position())) @@ -863,10 +863,9 @@ fn (mut p Parser) parse_attr() table.Attr { pos: apos.extend(p.tok.position()) } } - mut is_ctdefine := false - if p.tok.kind == .key_if { + is_comptime_define := p.tok.kind == .key_if + if is_comptime_define { p.next() - is_ctdefine = true } mut name := '' mut arg := '' @@ -899,7 +898,7 @@ fn (mut p Parser) parse_attr() table.Attr { return table.Attr{ name: name is_string: is_string - is_ctdefine: is_ctdefine + is_comptime_define: is_comptime_define arg: arg is_string_arg: is_string_arg pos: apos.extend(p.tok.position()) diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 8a5af4d7fd..cbc22d4263 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -3,6 +3,7 @@ // that can be found in the LICENSE file. module pref +// import v.ast // TODO this results in a compiler bug import os.cmdline import os import v.vcache diff --git a/vlib/v/table/attr.v b/vlib/v/table/attr.v index 1aa540e874..b66572e2e4 100644 --- a/vlib/v/table/attr.v +++ b/vlib/v/table/attr.v @@ -8,18 +8,18 @@ import v.token // e.g. `[unsafe]` pub struct Attr { pub: - name string // [name] - is_string bool // ['name'] - is_ctdefine bool // [if name] - arg string // [name: arg] - is_string_arg bool // [name: 'arg'] - pos token.Position + name string // [name] + is_string bool // ['name'] + is_comptime_define bool // [if name] + arg string // [name: arg] + is_string_arg bool // [name: 'arg'] + pos token.Position } // no square brackets pub fn (attr Attr) str() string { mut s := '' - if attr.is_ctdefine { + if attr.is_comptime_define { s += 'if ' } if attr.is_string { diff --git a/vlib/v/table/table.v b/vlib/v/table/table.v index 885a7a2036..915218938b 100644 --- a/vlib/v/table/table.v +++ b/vlib/v/table/table.v @@ -34,11 +34,12 @@ pub: is_placeholder bool no_body bool mod string - ctdefine string // compile time define. myflag, when [if myflag] tag + ctdefine string // compile time define. "myflag", when [if myflag] tag attrs []Attr pub mut: name string source_fn voidptr // set in the checker, while processing fn declarations + is_used bool } fn (f &Fn) method_equals(o &Fn) bool { diff --git a/vlib/v/walker/walker.v b/vlib/v/walker/walker.v new file mode 100644 index 0000000000..86f4eb2d44 --- /dev/null +++ b/vlib/v/walker/walker.v @@ -0,0 +1,129 @@ +// 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 + +// 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 + +pub struct Walker { +pub mut: + used_fns map[string]bool // used_fns['println'] == true +mut: + files []ast.File +} + +/* +fn (mut w Walker) walk_files(ast_files []ast.File) { + t := time.ticks() +*/ + +pub fn (mut w Walker) stmt(node ast.Stmt) { + match mut node { + ast.AssignStmt { + for l in node.left { + w.expr(l) + } + for r in node.right { + w.expr(r) + } + } + ast.ExprStmt { + w.expr(node.expr) + } + // ast.FnDecl { + // w.fn_decl(mut node) + //} + ast.ForStmt { + w.expr(node.cond) + for stmt in node.stmts { + w.stmt(stmt) + } + } + else {} + } +} + +fn (mut w Walker) expr(node ast.Expr) { + match mut node { + ast.CallExpr { + w.call_expr(mut node) + } + ast.GoExpr { + w.expr(node.go_stmt.call_expr) + } + ast.IndexExpr { + w.expr(node.left) + w.expr(node.index) + } + ast.IfExpr { + for b in node.branches { + w.expr(b.cond) + for stmt in b.stmts { + w.stmt(stmt) + } + } + } + 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) + } + } + } + else {} + } +} + +/* +pub fn (mut w Walker) fn_decl(mut node ast.FnDecl) { + fn_name := if node.is_method { node.receiver.typ.str() + '.' + node.name } else { node.name } + if w.used_fns[fn_name] { + // 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 + } + println('fn decl $fn_name') + w.used_fns[fn_name] = true + for stmt in node.stmts { + w.stmt(stmt) + } +} +*/ + +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') + // 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 + } + } + } + } +}