all: ast walker for marking unused fns

pull/8574/head
Alexander Medvednikov 2021-02-05 08:05:13 +01:00
parent 119dfc0bb0
commit 1084b43ffb
13 changed files with 230 additions and 25 deletions

View File

@ -254,7 +254,7 @@ jobs:
## sudo apt-get install --quiet -y libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libsdl2-image-dev ## sudo apt-get install --quiet -y libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libsdl2-image-dev
- name: Build V - name: Build V
run: make -j4 && ./v -cc gcc -cg -cflags -Werror -o v cmd/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 run: valgrind --error-exitcode=1 ./v -o v.c cmd/v
- name: Run sanitizers - name: Run sanitizers
run: | run: |

View File

@ -757,7 +757,7 @@ println(array_2) // [0, 1, 3, 5, 4]
### Fixed size arrays ### 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. length is constant. You cannot append elements to them, nor shrink them.
You can only modify their elements in place. You can only modify their elements in place.
@ -1661,11 +1661,16 @@ fn sqr(n int) int {
return n * n return n * n
} }
fn cube(n int) int {
return n * n * n
}
fn run(value int, op fn (int) int) int { fn run(value int, op fn (int) int) int {
return op(value) return op(value)
} }
fn main() { fn main() {
// Functions can be passed to other functions
println(run(5, sqr)) // "25" println(run(5, sqr)) // "25"
// Anonymous functions can be declared inside other functions: // Anonymous functions can be declared inside other functions:
double_fn := fn (n int) int { double_fn := fn (n int) int {
@ -1676,6 +1681,11 @@ fn main() {
res := run(5, fn (n int) int { res := run(5, fn (n int) int {
return n + n 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: my_label:
``` ```
`goto` should be avoided when `for` can be used instead. In particular, `goto` should be avoided when `for` can be used instead. In particular,
[labelled break](#labelled-break--continue) can be used to break out of [labelled break](#labelled-break--continue) can be used to break out of
a nested loop. a nested loop.
# Appendices # Appendices

View File

@ -329,6 +329,7 @@ pub:
generic_params []GenericParam generic_params []GenericParam
is_direct_arr bool // direct array access is_direct_arr bool // direct array access
attrs []table.Attr attrs []table.Attr
skip_gen bool // this function doesn't need to be generated (for example [if foo])
pub mut: pub mut:
stmts []Stmt stmts []Stmt
return_type table.Type return_type table.Type
@ -1518,3 +1519,9 @@ pub fn ex2fe(x Expr) table.FExpr {
unsafe { C.memcpy(&res, &x, sizeof(table.FExpr)) } unsafe { C.memcpy(&res, &x, sizeof(table.FExpr)) }
return res return res
} }
// experimental ast.Table
pub struct Table {
// pub mut:
// main_fn_decl_node FnDecl
}

View File

@ -27,6 +27,7 @@ pub mut:
parsed_files []ast.File parsed_files []ast.File
cached_msvc MsvcResult cached_msvc MsvcResult
table &table.Table table &table.Table
table2 &ast.Table
timers &util.Timers = util.new_timers(false) timers &util.Timers = util.new_timers(false)
ccoptions CcompilerOptions 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) } compiled_dir := if os.is_dir(rdir) { rdir } else { os.dir(rdir) }
mut table := table.new_table() mut table := table.new_table()
table.is_fmt = false table.is_fmt = false
table2 := &ast.Table{}
if pref.use_color == .always { if pref.use_color == .always {
util.emanager.set_support_color(true) util.emanager.set_support_color(true)
} }
@ -53,7 +55,8 @@ pub fn new_builder(pref &pref.Preferences) Builder {
return Builder{ return Builder{
pref: pref pref: pref
table: table table: table
checker: checker.new_checker(table, pref) table2: table2
checker: checker.new_checker(table, table2, pref)
global_scope: &ast.Scope{ global_scope: &ast.Scope{
parent: 0 parent: 0
} }
@ -235,7 +238,7 @@ fn module_path(mod string) string {
return mod.replace('.', os.path_separator) 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 // reliable multi use function. see comments in util/module.v
pub fn (b &Builder) find_module_path(mod string, fpath string) ?string { pub fn (b &Builder) find_module_path(mod string, fpath string) ?string {
// support @VROOT/v.mod relative paths: // support @VROOT/v.mod relative paths:

View File

@ -22,7 +22,7 @@ pub fn (mut b Builder) gen_c(v_files []string) string {
b.print_warnings_and_errors() b.print_warnings_and_errors()
// TODO: move gen.cgen() to c.gen() // TODO: move gen.cgen() to c.gen()
b.timing_start('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') b.timing_measure('C GEN')
// println('cgen done') // println('cgen done')
// println(res) // println(res)

View File

@ -12,6 +12,8 @@ import v.pref
import v.util import v.util
import v.errors import v.errors
import v.pkgconfig import v.pkgconfig
import v.walker
import time
const ( const (
max_nr_errors = 300 max_nr_errors = 300
@ -36,6 +38,7 @@ pub struct Checker {
pref &pref.Preferences // Preferences shared from V struct pref &pref.Preferences // Preferences shared from V struct
pub mut: pub mut:
table &table.Table table &table.Table
table2 &ast.Table
file &ast.File = 0 file &ast.File = 0
nr_errors int nr_errors int
nr_warnings int nr_warnings int
@ -63,6 +66,7 @@ pub mut:
skip_flags bool // should `#flag` and `#include` be skipped skip_flags bool // should `#flag` and `#include` be skipped
cur_generic_types []table.Type cur_generic_types []table.Type
mut: mut:
files []ast.File
expr_level int // to avoid infinite recursion segfaults due to compiler bugs expr_level int // to avoid infinite recursion segfaults due to compiler bugs
inside_sql bool // to handle sql table fields pseudo variables inside_sql bool // to handle sql table fields pseudo variables
cur_orm_ts table.TypeSymbol cur_orm_ts table.TypeSymbol
@ -76,15 +80,18 @@ mut:
timers &util.Timers = util.new_timers(false) timers &util.Timers = util.new_timers(false)
comptime_fields_type map[string]table.Type comptime_fields_type map[string]table.Type
fn_scope &ast.Scope = voidptr(0) 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 mut timers_should_print := false
$if time_checking ? { $if time_checking ? {
timers_should_print = true timers_should_print = true
} }
return Checker{ return Checker{
table: table table: table
table2: table2
pref: pref pref: pref
cur_fn: 0 cur_fn: 0
timers: util.new_timers(timers_should_print) 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) { pub fn (mut c Checker) check_files(ast_files []ast.File) {
// c.files = ast_files
mut has_main_mod_file := false mut has_main_mod_file := false
mut has_main_fn := false mut has_main_fn := false
mut files_from_main_module := []&ast.File{} 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 { } else if !has_main_fn {
c.error('function `main` must be declared in the main module', token.Position{}) 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 // 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 { ast.Ident {
if arg_expr.kind == .function { if arg_expr.kind == .function {
func := c.table.find_fn(arg_expr.name) or { 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 return
} }
if func.params.len > 1 { 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 call_expr.name = name_prefixed
found = true found = true
f = f1 f = f1
c.table.fns[name_prefixed].is_used = true
} }
} }
if !found && call_expr.left is ast.IndexExpr { 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) { if f1 := c.table.find_fn(fn_name) {
found = true found = true
f = f1 f = f1
c.table.fns[fn_name].is_used = true
} }
} }
if c.pref.is_script && !found { 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 call_expr.name = os_name
found = true found = true
f = f1 f = f1
c.table.fns[os_name].is_used = true
} }
} }
// check for arg (var) of fn type // check for arg (var) of fn type
@ -3827,7 +3859,7 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) table.Type {
...pref_ ...pref_
is_vweb: true is_vweb: true
} }
mut c2 := new_checker(c.table, pref2) mut c2 := new_checker(c.table, c.table2, pref2)
c2.check(node.vweb_tmpl) c2.check(node.vweb_tmpl)
mut i := 0 // tmp counter var for skipping first three tmpl vars mut i := 0 // tmp counter var for skipping first three tmpl vars
for k, _ in c2.file.scope.children[0].objects { 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 { if node.language == .v && !c.is_builtin_mod {
c.check_valid_snake_case(node.name, 'function name', node.pos) 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 { if node.return_type != table.void_type {
for attr in node.attrs { 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', c.error('only functions that do NOT return values can have `[if $attr.name]` tags',
node.pos) node.pos)
break break

View File

@ -32,6 +32,7 @@ struct Gen {
module_built string module_built string
mut: mut:
table &table.Table table &table.Table
table2 &ast.Table
out strings.Builder out strings.Builder
cheaders strings.Builder cheaders strings.Builder
includes strings.Builder // all C #includes required by V modules 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() 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) as_cast_type_names map[string]string // table for type name lookup in runtime (for __as_cast)
obf_table map[string]string 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') // println('start cgen2')
mut module_built := '' mut module_built := ''
if pref.build_mode == .build_module { 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) enum_typedefs: strings.new_builder(100)
sql_buf: strings.new_builder(100) sql_buf: strings.new_builder(100)
table: table table: table
table2: table2
pref: pref pref: pref
fn_decl: 0 fn_decl: 0
is_autofree: true is_autofree: true

View File

@ -15,6 +15,23 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl, skip bool) {
// || node.no_body { // || node.no_body {
return 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 = '' g.returned_var_name = ''
// //
old_g_autofree := g.is_autofree old_g_autofree := g.is_autofree

View File

@ -828,7 +828,7 @@ fn (mut p Parser) attributes() {
p.error_with_pos('duplicate attribute `$attr.name`', start_pos.extend(p.prev_tok.position())) p.error_with_pos('duplicate attribute `$attr.name`', start_pos.extend(p.prev_tok.position()))
return return
} }
if attr.is_ctdefine { if attr.is_comptime_define {
if has_ctdefine { if has_ctdefine {
p.error_with_pos('only one `[if flag]` may be applied at a time `$attr.name`', p.error_with_pos('only one `[if flag]` may be applied at a time `$attr.name`',
start_pos.extend(p.prev_tok.position())) 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()) pos: apos.extend(p.tok.position())
} }
} }
mut is_ctdefine := false is_comptime_define := p.tok.kind == .key_if
if p.tok.kind == .key_if { if is_comptime_define {
p.next() p.next()
is_ctdefine = true
} }
mut name := '' mut name := ''
mut arg := '' mut arg := ''
@ -899,7 +898,7 @@ fn (mut p Parser) parse_attr() table.Attr {
return table.Attr{ return table.Attr{
name: name name: name
is_string: is_string is_string: is_string
is_ctdefine: is_ctdefine is_comptime_define: is_comptime_define
arg: arg arg: arg
is_string_arg: is_string_arg is_string_arg: is_string_arg
pos: apos.extend(p.tok.position()) pos: apos.extend(p.tok.position())

View File

@ -3,6 +3,7 @@
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
module pref module pref
// import v.ast // TODO this results in a compiler bug
import os.cmdline import os.cmdline
import os import os
import v.vcache import v.vcache

View File

@ -8,18 +8,18 @@ import v.token
// e.g. `[unsafe]` // e.g. `[unsafe]`
pub struct Attr { pub struct Attr {
pub: pub:
name string // [name] name string // [name]
is_string bool // ['name'] is_string bool // ['name']
is_ctdefine bool // [if name] is_comptime_define bool // [if name]
arg string // [name: arg] arg string // [name: arg]
is_string_arg bool // [name: 'arg'] is_string_arg bool // [name: 'arg']
pos token.Position pos token.Position
} }
// no square brackets // no square brackets
pub fn (attr Attr) str() string { pub fn (attr Attr) str() string {
mut s := '' mut s := ''
if attr.is_ctdefine { if attr.is_comptime_define {
s += 'if ' s += 'if '
} }
if attr.is_string { if attr.is_string {

View File

@ -34,11 +34,12 @@ pub:
is_placeholder bool is_placeholder bool
no_body bool no_body bool
mod string mod string
ctdefine string // compile time define. myflag, when [if myflag] tag ctdefine string // compile time define. "myflag", when [if myflag] tag
attrs []Attr attrs []Attr
pub mut: pub mut:
name string name string
source_fn voidptr // set in the checker, while processing fn declarations source_fn voidptr // set in the checker, while processing fn declarations
is_used bool
} }
fn (f &Fn) method_equals(o &Fn) bool { fn (f &Fn) method_equals(o &Fn) bool {

View File

@ -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
}
}
}
}
}