all: ast walker for marking unused fns
parent
119dfc0bb0
commit
1084b43ffb
|
@ -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: |
|
||||
|
|
16
doc/docs.md
16
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue