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
- 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: |

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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()))
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())

View File

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

View File

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

View File

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

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