1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

gen: implement a [manualfree] tag, for functions, that want to do their own memory management

This commit is contained in:
Delyan Angelov 2021-01-08 14:56:55 +02:00
parent 06bcd404b0
commit 083dc23db8
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
10 changed files with 77 additions and 53 deletions

View File

@ -7,6 +7,7 @@
- `byte.str()` has been fixed and works like with all other numbers. `byte.ascii_str()` has been added.
- Smart cast in for loops: `for mut x is string {}`.
- `[noinit]` struct attribute to disallow direct struct initialization with `Foo{}`.
- `[manualfree]` attribute for functions, that want to do their own memory management.
## V 0.2.1
*30 Dec 2020*

View File

@ -67,3 +67,10 @@ These build flags are enabled on `build` and `run` as long as the backend is set
-keepc
Do not remove the temporary .tmp.c and .tmp.c.rsp files. Also do not use a random prefix for them, so they would be fixed and predictable.
-autofree
Free memory used in functions automatically.
-manualfree
Do not free memory used in functions (the developer has to put x.free() and unsafe{free(x)} calls manually in this mode).
Some short lived applications, like compilers and other CLI tools are more performant without autofree.

View File

@ -2517,7 +2517,8 @@ Python, Go, or Java, except there's no heavy GC tracing everything or expensive
each object.
For developers willing to have more low level control, autofree can be disabled with
`-noautofree`.
`-manualfree`, or by adding a `[manualfree]` on each function that wants manage its
memory manually.
Note: right now autofree is hidden behind the -autofree flag. It will be enabled by
default in V 0.3.

View File

@ -296,6 +296,7 @@ pub:
is_pub bool
is_variadic bool
is_anon bool
is_manualfree bool // true, when [manualfree] is used on a fn
receiver Field
receiver_pos token.Position // `(u User)` in `fn (u User) name()` position
is_method bool

View File

@ -3,7 +3,6 @@
module gen
import v.table
import v.pref
import v.util
fn (mut g Gen) gen_str_default(sym table.TypeSymbol, styp string, str_fn_name string) {
@ -230,7 +229,7 @@ fn (mut g Gen) gen_str_for_array(info table.Array, styp string, str_fn_name stri
}
}
g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, x);')
if g.pref.autofree && typ != table.bool_type {
if g.is_autofree && typ != table.bool_type {
// no need to free "true"/"false" literals
g.auto_str_funcs.writeln('\t\tstring_free(&x);')
}

View File

@ -78,7 +78,7 @@ mut:
stmt_path_pos []int // positions of each statement start, for inserting C statements before the current statement
skip_stmt_pos bool // for handling if expressions + autofree (since both prepend C statements)
right_is_opt bool
autofree bool
is_autofree bool // false, inside the bodies of fns marked with [manualfree], otherwise === g.pref.autofree
indent int
empty_line bool
is_test bool
@ -186,7 +186,7 @@ pub fn cgen(files []ast.File, table &table.Table, pref &pref.Preferences) string
table: table
pref: pref
fn_decl: 0
autofree: true
is_autofree: true
indent: -1
module_built: module_built
timers: util.new_timers(timers_should_print)
@ -216,9 +216,9 @@ pub fn cgen(files []ast.File, table &table.Table, pref &pref.Preferences) string
if g.file.path == '' || !g.pref.autofree {
// cgen test or building V
// println('autofree=false')
g.autofree = false
g.is_autofree = false
} else {
g.autofree = true
g.is_autofree = true
autofree_used = true
}
// anon fn may include assert and thus this needs
@ -232,7 +232,7 @@ pub fn cgen(files []ast.File, table &table.Table, pref &pref.Preferences) string
}
g.timers.start('cgen common')
if autofree_used {
g.autofree = true // so that void _vcleanup is generated
g.is_autofree = true // so that void _vcleanup is generated
}
// to make sure type idx's are the same in cached mods
if g.pref.build_mode == .build_module {
@ -799,7 +799,7 @@ fn (mut g Gen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) {
g.write('')
g.write(')')
}
if g.pref.autofree && !g.inside_vweb_tmpl && stmts.len > 0 {
if g.is_autofree && !g.inside_vweb_tmpl && stmts.len > 0 {
// use the first stmt to get the scope
stmt := stmts[0]
// stmt := stmts[stmts.len-1]
@ -878,7 +878,7 @@ fn (mut g Gen) stmt(node ast.Stmt) {
}
} else {
// continue or break
if g.pref.autofree && !g.is_builtin_mod {
if g.is_autofree && !g.is_builtin_mod {
g.writeln('// free before continue/break')
g.autofree_scope_vars_stop(node.pos.pos - 1, node.pos.line_nr, true,
g.branch_parent_pos)
@ -935,7 +935,7 @@ fn (mut g Gen) stmt(node ast.Stmt) {
}
ast.ExprStmt {
g.write_v_source_line_info(node.pos)
// af := g.pref.autofree && node.expr is ast.CallExpr && !g.is_builtin_mod
// af := g.autofree && node.expr is ast.CallExpr && !g.is_builtin_mod
// if af {
// g.autofree_call_pregen(node.expr as ast.CallExpr)
// }
@ -1129,9 +1129,9 @@ fn (mut g Gen) stmt(node ast.Stmt) {
}
ast.Return {
g.write_defer_stmts_when_needed()
// af := g.pref.autofree && node.exprs.len > 0 && node.exprs[0] is ast.CallExpr && !g.is_builtin_mod
// af := g.autofree && node.exprs.len > 0 && node.exprs[0] is ast.CallExpr && !g.is_builtin_mod
/*
af := g.pref.autofree && !g.is_builtin_mod
af := g.autofree && !g.is_builtin_mod
if false && af {
g.writeln('// ast.Return free')
g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true)
@ -1172,7 +1172,7 @@ fn (mut g Gen) stmt(node ast.Stmt) {
}
// If we have temporary string exprs to free after this statement, do it. e.g.:
// `foo('a' + 'b')` => `tmp := 'a' + 'b'; foo(tmp); string_free(&tmp);`
if g.pref.autofree {
if g.is_autofree {
// if node is ast.ExprStmt {&& node.expr is ast.CallExpr {
if node !is ast.FnDecl {
// p := node.position()
@ -1584,7 +1584,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
}
// Free the old value assigned to this string var (only if it's `str = [new value]`
// or `x.str = [new value]` )
mut af := g.pref.autofree && !g.is_builtin_mod && assign_stmt.op == .assign && assign_stmt.left_types.len ==
mut af := g.is_autofree && !g.is_builtin_mod && assign_stmt.op == .assign && assign_stmt.left_types.len ==
1 &&
(assign_stmt.left[0] is ast.Ident || assign_stmt.left[0] is ast.SelectorExpr)
// assign_stmt.left_types[0] in [table.string_type, table.array_type] &&
@ -1623,7 +1623,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
}
// Autofree tmp arg vars
// first_right := assign_stmt.right[0]
// af := g.pref.autofree && first_right is ast.CallExpr && !g.is_builtin_mod
// af := g.autofree && first_right is ast.CallExpr && !g.is_builtin_mod
// if af {
// g.autofree_call_pregen(first_right as ast.CallExpr)
// }
@ -1641,7 +1641,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
// }
// int pos = *(int*)_t190.data;
mut tmp_opt := ''
is_optional := g.pref.autofree &&
is_optional := g.is_autofree &&
(assign_stmt.op in [.decl_assign, .assign]) && assign_stmt.left_types.len == 1 && assign_stmt.right[0] is
ast.CallExpr
if is_optional {
@ -1727,7 +1727,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
if left.left_type.is_ptr() {
g.write('*')
}
needs_clone := info.elem_type == table.string_type && g.pref.autofree
needs_clone := info.elem_type == table.string_type && g.is_autofree
if needs_clone {
g.write('/*1*/string_clone(')
}
@ -1969,7 +1969,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
g.write(', ')
}
mut cloned := false
if g.autofree && right_sym.kind in [.array, .string] {
if g.is_autofree && right_sym.kind in [.array, .string] {
if g.gen_clone_assignment(val, right_sym, false) {
cloned = true
}
@ -1980,7 +1980,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
// `pos := s.index(...
// `int pos = *(int)_t10.data;`
g.write('*($styp*)')
if g.pref.autofree {
if g.is_autofree {
g.write(tmp_opt + '.data/*FFz*/')
g.right_is_opt = false
g.is_assign_rhs = false
@ -2037,7 +2037,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
}
}
if unwrap_optional {
if g.pref.autofree {
if g.is_autofree {
// g.write(tmp_opt + '/*FF*/')
} else {
g.write('.data')
@ -2150,7 +2150,7 @@ fn (mut g Gen) gen_clone_assignment(val ast.Expr, right_sym table.TypeSymbol, ad
if val !is ast.Ident && val !is ast.SelectorExpr {
return false
}
if g.autofree && right_sym.kind == .array {
if g.is_autofree && right_sym.kind == .array {
// `arr1 = arr2` => `arr1 = arr2.clone()`
if add_eq {
g.write('=')
@ -2158,7 +2158,7 @@ fn (mut g Gen) gen_clone_assignment(val ast.Expr, right_sym table.TypeSymbol, ad
g.write(' array_clone_static(')
g.expr(val)
g.write(')')
} else if g.autofree && right_sym.kind == .string {
} else if g.is_autofree && right_sym.kind == .string {
if add_eq {
g.write('=')
}
@ -2407,8 +2407,8 @@ fn (mut g Gen) expr(node ast.Expr) {
// if g.fileis('1.strings') {
// println('before:' + node.autofree_pregen)
// }
if g.pref.autofree && !g.is_builtin_mod && !g.is_js_call && g.strs_to_free0.len ==
0 && !g.inside_lambda { // && g.inside_ternary ==
if g.is_autofree && !g.is_builtin_mod && !g.is_js_call && g.strs_to_free0.len == 0 &&
!g.inside_lambda { // && g.inside_ternary ==
// if len != 0, that means we are handling call expr inside call expr (arg)
// and it'll get messed up here, since it's handled recursively in autofree_call_pregen()
// so just skip it
@ -2419,9 +2419,9 @@ fn (mut g Gen) expr(node ast.Expr) {
g.strs_to_free0 = []
// println('pos=$node.pos.pos')
}
// if g.pref.autofree && node.autofree_pregen != '' { // g.strs_to_free0.len != 0 {
// if g.autofree && node.autofree_pregen != '' { // g.strs_to_free0.len != 0 {
/*
if g.pref.autofree {
if g.autofree {
s := g.autofree_pregen[node.pos.pos.str()]
if s != '' {
// g.insert_before_stmt('/*START2*/' + g.strs_to_free0.join('\n') + '/*END*/')
@ -3167,7 +3167,7 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
if elem_sym.kind == .interface_ && node.right_type != info.elem_type {
g.interface_call(node.right_type, info.elem_type)
}
// if g.pref.autofree
// if g.autofree
needs_clone := info.elem_type == table.string_type && !g.is_builtin_mod
if needs_clone {
g.write('string_clone(')
@ -3713,12 +3713,12 @@ fn (mut g Gen) if_expr(node ast.IfExpr) {
// Always use this in -autofree, since ?: can have tmp expressions that have to be freed.
first_branch := node.branches[0]
needs_tmp_var := node.is_expr &&
(g.pref.autofree || (g.pref.experimental &&
(g.is_autofree || (g.pref.experimental &&
(first_branch.stmts.len > 1 || (first_branch.stmts[0] is ast.ExprStmt &&
(first_branch.stmts[0] as ast.ExprStmt).expr is ast.IfExpr))))
/*
needs_tmp_var := node.is_expr &&
(g.pref.autofree || g.pref.experimental) &&
(g.autofree || g.pref.experimental) &&
(node.branches[0].stmts.len > 1 || node.branches[0].stmts[0] is ast.IfExpr)
*/
tmp := if needs_tmp_var { g.new_tmp_var() } else { '' }
@ -3957,7 +3957,7 @@ fn (mut g Gen) index_expr(node ast.IndexExpr) {
.function { 'voidptr*' }
else { '$elem_type_str*' }
}
needs_clone := info.elem_type == table.string_type_idx && g.pref.autofree &&
needs_clone := info.elem_type == table.string_type_idx && g.is_autofree &&
!g.is_assign_lhs
if needs_clone {
g.write('/*2*/string_clone(')
@ -4141,7 +4141,7 @@ fn (mut g Gen) return_statement(node ast.Return) {
g.writeln('$styp $tmp = {.ok = true};')
g.writeln('return $tmp;')
} else {
if g.pref.autofree && !g.is_builtin_mod {
if g.is_autofree && !g.is_builtin_mod {
g.writeln('// free before return (no values returned)')
g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true)
}
@ -4275,7 +4275,7 @@ fn (mut g Gen) return_statement(node ast.Return) {
g.writeln('return $opt_tmp;')
return
}
free := g.pref.autofree && !g.is_builtin_mod // node.exprs[0] is ast.CallExpr
free := g.is_autofree && !g.is_builtin_mod // node.exprs[0] is ast.CallExpr
mut tmp := ''
if free {
// `return foo(a, b, c)`
@ -4404,7 +4404,7 @@ fn (mut g Gen) const_decl_init_later(mod string, name string, val string, typ ta
cname := '_const_$name'
g.definitions.writeln('$styp $cname; // inited later')
g.inits[mod].writeln('\t$cname = $val;')
if g.pref.autofree {
if g.is_autofree {
if styp.starts_with('array_') {
g.cleanups[mod].writeln('\tarray_free(&$cname);')
}
@ -4491,7 +4491,7 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) {
}
field_type_sym := g.table.get_type_symbol(field.typ)
mut cloned := false
if g.autofree && !field.typ.is_ptr() && field_type_sym.kind in [.array, .string] {
if g.is_autofree && !field.typ.is_ptr() && field_type_sym.kind in [.array, .string] {
g.write('/*clone1*/')
if g.gen_clone_assignment(field.expr, field_type_sym, false) {
cloned = true
@ -4560,7 +4560,7 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) {
mut cloned := false
is_interface := expected_field_type_sym.kind == .interface_ &&
field_type_sym.kind != .interface_
if g.autofree && !sfield.typ.is_ptr() && field_type_sym.kind in [.array, .string] {
if g.is_autofree && !sfield.typ.is_ptr() && field_type_sym.kind in [.array, .string] {
g.write('/*clone1*/')
if g.gen_clone_assignment(sfield.expr, field_type_sym, false) {
cloned = true
@ -4705,7 +4705,7 @@ fn (mut g Gen) write_init_function() {
} else {
g.writeln('void _vinit() {')
}
if g.pref.autofree {
if g.is_autofree {
// Pre-allocate the string buffer
// s_str_buf_size := os.getenv('V_STRBUF_MB')
// mb_size := if s_str_buf_size == '' { 1 } else { s_str_buf_size.int() }
@ -4735,7 +4735,7 @@ fn (mut g Gen) write_init_function() {
if g.pref.printfn_list.len > 0 && '_vinit' in g.pref.printfn_list {
println(g.out.after(fn_vinit_start_pos))
}
if g.autofree {
if g.is_autofree {
fn_vcleanup_start_pos := g.out.len
g.writeln('void _vcleanup() {')
// g.writeln('puts("cleaning up...");')

View File

@ -75,7 +75,7 @@ fn (mut g Gen) gen_c_main_header() {
g.writeln('')
}
if g.is_importing_os() {
if g.autofree {
if g.is_autofree {
g.writeln('free(_const_os__args.data); // empty, inited in _vinit()')
}
if g.pref.os == .windows {
@ -90,7 +90,7 @@ fn (mut g Gen) gen_c_main_header() {
}
pub fn (mut g Gen) gen_c_main_footer() {
if g.autofree {
if g.is_autofree {
g.writeln('\t_vcleanup();')
}
g.writeln('\treturn 0;')
@ -99,7 +99,7 @@ pub fn (mut g Gen) gen_c_main_footer() {
pub fn (mut g Gen) gen_c_android_sokol_main() {
// Weave autofree into sokol lifecycle callback(s)
if g.autofree {
if g.is_autofree {
g.writeln('// Wrapping cleanup/free callbacks for sokol to include _vcleanup()
void (*_vsokol_user_cleanup_ptr)(void);
void (*_vsokol_user_cleanup_cb_ptr)(void *);
@ -126,7 +126,7 @@ sapp_desc sokol_main(int argc, char* argv[]) {
_vinit();
main__main();
')
if g.autofree {
if g.is_autofree {
g.writeln(' // Wrap user provided cleanup/free functions for sokol to be able to call _vcleanup()
if (g_desc.cleanup_cb) {
_vsokol_user_cleanup_ptr = g_desc.cleanup_cb;
@ -170,7 +170,7 @@ pub fn (mut g Gen) write_tests_main() {
g.writeln('\tmain__BenchedTests_end_testing(&bt);')
}
g.writeln('')
if g.autofree {
if g.is_autofree {
g.writeln('\t_vcleanup();')
}
g.writeln('\treturn g_test_fails > 0;')

View File

@ -16,6 +16,15 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl, skip bool) {
return
}
g.returned_var_name = ''
//
old_g_autofree := g.is_autofree
if it.is_manualfree {
g.is_autofree = false
}
defer {
g.is_autofree = old_g_autofree
}
//
// if g.fileis('vweb.v') {
// println('\ngen_fn_decl() $it.name $it.is_generic $g.cur_generic_type')
// }
@ -263,12 +272,12 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
defer {
g.inside_call = false
}
gen_or := node.or_block.kind != .absent && !g.pref.autofree
gen_or := node.or_block.kind != .absent && !g.is_autofree
// if gen_or {
// g.writeln('/*start*/')
// }
is_gen_or_and_assign_rhs := gen_or && g.is_assign_rhs
cur_line := if is_gen_or_and_assign_rhs && !g.pref.autofree {
cur_line := if is_gen_or_and_assign_rhs && !g.is_autofree {
line := g.go_before_stmt(0)
g.out.write(tabs[g.indent])
line
@ -290,8 +299,8 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
} else {
g.fn_call(node)
}
if gen_or { // && !g.pref.autofree {
if !g.pref.autofree {
if gen_or { // && !g.autofree {
if !g.is_autofree {
g.or_block(tmp_opt, node.or_block, node.return_type)
}
if is_gen_or_and_assign_rhs {
@ -557,7 +566,7 @@ fn (mut g Gen) fn_call(node ast.CallExpr) {
tmp2 = g.new_tmp_var()
g.writeln('Option_$typ $tmp2 = $fn_name ($json_obj);')
}
if !g.pref.autofree {
if !g.is_autofree {
g.write('cJSON_Delete($json_obj); //del')
}
g.write('\n$cur_line')
@ -594,7 +603,7 @@ fn (mut g Gen) fn_call(node ast.CallExpr) {
// check if alias parent also not a string
if typ != table.string_type {
expr := node.args[0].expr
if g.autofree && !typ.has_flag(.optional) {
if g.is_autofree && !typ.has_flag(.optional) {
// Create a temporary variable so that the value can be freed
tmp := g.new_tmp_var()
// tmps << tmp
@ -640,7 +649,7 @@ fn (mut g Gen) autofree_call_pregen(node ast.CallExpr) {
// g.writeln('// autofree_call_pregen()')
// Create a temporary var before fn call for each argument in order to free it (only if it's a complex expression,
// like `foo(get_string())` or `foo(a + b)`
mut free_tmp_arg_vars := g.autofree && !g.is_builtin_mod && node.args.len > 0 && !node.args[0].typ.has_flag(.optional) // TODO copy pasta checker.v
mut free_tmp_arg_vars := g.is_autofree && !g.is_builtin_mod && node.args.len > 0 && !node.args[0].typ.has_flag(.optional) // TODO copy pasta checker.v
if !free_tmp_arg_vars {
return
}
@ -775,7 +784,7 @@ fn (mut g Gen) call_args(node ast.CallExpr) {
if is_variadic && i == expected_types.len - 1 {
break
}
use_tmp_var_autofree := g.autofree && arg.typ == table.string_type && arg.is_tmp_autofree &&
use_tmp_var_autofree := g.is_autofree && arg.typ == table.string_type && arg.is_tmp_autofree &&
!g.inside_const && !g.is_builtin_mod
// g.write('/* af=$arg.is_tmp_autofree */')
mut is_interface := false

View File

@ -156,6 +156,7 @@ pub fn (mut p Parser) call_args() []ast.CallArg {
fn (mut p Parser) fn_decl() ast.FnDecl {
p.top_level_statement_start()
start_pos := p.tok.position()
is_manualfree := p.attrs.contains('manualfree')
is_deprecated := p.attrs.contains('deprecated')
is_direct_arr := p.attrs.contains('direct_array_access')
mut is_unsafe := p.attrs.contains('unsafe')
@ -395,6 +396,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
stmts: stmts
return_type: return_type
params: params
is_manualfree: is_manualfree
is_deprecated: is_deprecated
is_direct_arr: is_direct_arr
is_pub: is_pub

View File

@ -81,7 +81,6 @@ pub mut:
use_cache bool // = true
retry_compilation bool = true
is_stats bool // `v -stats file_test.v` will produce more detailed statistics for the tests that were run
no_auto_free bool // `v -nofree` disable automatic `free()` insertion for better performance in some applications (e.g. compilers)
// TODO Convert this into a []string
cflags string // Additional options which will be passed to the C compiler.
// For example, passing -cflags -Os will cause the C compiler to optimize the generated binaries for size.
@ -92,7 +91,8 @@ pub mut:
ccompiler_type CompilerType // the type of the C compiler used
third_party_option string
building_v bool
autofree bool
autofree bool // `v -manualfree` => false, `v -autofree` => true; false by default for now.
// Disabling `free()` insertion results in better performance in some applications (e.g. compilers)
compress bool
// skip_builtin bool // Skips re-compilation of the builtin module
// to increase compilation time.
@ -213,6 +213,10 @@ pub fn parse_args(args []string) (&Preferences, string) {
res.autofree = true
res.build_options << arg
}
'-manualfree' {
res.autofree = false
res.build_options << arg
}
'-compress' {
res.compress = true
}