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

pull/7886/head^2
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. - `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 {}`. - Smart cast in for loops: `for mut x is string {}`.
- `[noinit]` struct attribute to disallow direct struct initialization with `Foo{}`. - `[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 ## V 0.2.1
*30 Dec 2020* *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 -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. 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. each object.
For developers willing to have more low level control, autofree can be disabled with 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 Note: right now autofree is hidden behind the -autofree flag. It will be enabled by
default in V 0.3. default in V 0.3.

View File

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

View File

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

View File

@ -75,7 +75,7 @@ fn (mut g Gen) gen_c_main_header() {
g.writeln('') g.writeln('')
} }
if g.is_importing_os() { if g.is_importing_os() {
if g.autofree { if g.is_autofree {
g.writeln('free(_const_os__args.data); // empty, inited in _vinit()') g.writeln('free(_const_os__args.data); // empty, inited in _vinit()')
} }
if g.pref.os == .windows { 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() { pub fn (mut g Gen) gen_c_main_footer() {
if g.autofree { if g.is_autofree {
g.writeln('\t_vcleanup();') g.writeln('\t_vcleanup();')
} }
g.writeln('\treturn 0;') 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() { pub fn (mut g Gen) gen_c_android_sokol_main() {
// Weave autofree into sokol lifecycle callback(s) // 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() g.writeln('// Wrapping cleanup/free callbacks for sokol to include _vcleanup()
void (*_vsokol_user_cleanup_ptr)(void); void (*_vsokol_user_cleanup_ptr)(void);
void (*_vsokol_user_cleanup_cb_ptr)(void *); void (*_vsokol_user_cleanup_cb_ptr)(void *);
@ -126,7 +126,7 @@ sapp_desc sokol_main(int argc, char* argv[]) {
_vinit(); _vinit();
main__main(); 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() g.writeln(' // Wrap user provided cleanup/free functions for sokol to be able to call _vcleanup()
if (g_desc.cleanup_cb) { if (g_desc.cleanup_cb) {
_vsokol_user_cleanup_ptr = 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('\tmain__BenchedTests_end_testing(&bt);')
} }
g.writeln('') g.writeln('')
if g.autofree { if g.is_autofree {
g.writeln('\t_vcleanup();') g.writeln('\t_vcleanup();')
} }
g.writeln('\treturn g_test_fails > 0;') 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 return
} }
g.returned_var_name = '' 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') { // if g.fileis('vweb.v') {
// println('\ngen_fn_decl() $it.name $it.is_generic $g.cur_generic_type') // 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 { defer {
g.inside_call = false 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 { // if gen_or {
// g.writeln('/*start*/') // g.writeln('/*start*/')
// } // }
is_gen_or_and_assign_rhs := gen_or && g.is_assign_rhs 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) line := g.go_before_stmt(0)
g.out.write(tabs[g.indent]) g.out.write(tabs[g.indent])
line line
@ -290,8 +299,8 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
} else { } else {
g.fn_call(node) g.fn_call(node)
} }
if gen_or { // && !g.pref.autofree { if gen_or { // && !g.autofree {
if !g.pref.autofree { if !g.is_autofree {
g.or_block(tmp_opt, node.or_block, node.return_type) g.or_block(tmp_opt, node.or_block, node.return_type)
} }
if is_gen_or_and_assign_rhs { 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() tmp2 = g.new_tmp_var()
g.writeln('Option_$typ $tmp2 = $fn_name ($json_obj);') 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('cJSON_Delete($json_obj); //del')
} }
g.write('\n$cur_line') 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 // check if alias parent also not a string
if typ != table.string_type { if typ != table.string_type {
expr := node.args[0].expr 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 // Create a temporary variable so that the value can be freed
tmp := g.new_tmp_var() tmp := g.new_tmp_var()
// tmps << tmp // tmps << tmp
@ -640,7 +649,7 @@ fn (mut g Gen) autofree_call_pregen(node ast.CallExpr) {
// g.writeln('// autofree_call_pregen()') // 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, // 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)` // 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 { if !free_tmp_arg_vars {
return return
} }
@ -775,7 +784,7 @@ fn (mut g Gen) call_args(node ast.CallExpr) {
if is_variadic && i == expected_types.len - 1 { if is_variadic && i == expected_types.len - 1 {
break 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.inside_const && !g.is_builtin_mod
// g.write('/* af=$arg.is_tmp_autofree */') // g.write('/* af=$arg.is_tmp_autofree */')
mut is_interface := false 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 { fn (mut p Parser) fn_decl() ast.FnDecl {
p.top_level_statement_start() p.top_level_statement_start()
start_pos := p.tok.position() start_pos := p.tok.position()
is_manualfree := p.attrs.contains('manualfree')
is_deprecated := p.attrs.contains('deprecated') is_deprecated := p.attrs.contains('deprecated')
is_direct_arr := p.attrs.contains('direct_array_access') is_direct_arr := p.attrs.contains('direct_array_access')
mut is_unsafe := p.attrs.contains('unsafe') mut is_unsafe := p.attrs.contains('unsafe')
@ -395,6 +396,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
stmts: stmts stmts: stmts
return_type: return_type return_type: return_type
params: params params: params
is_manualfree: is_manualfree
is_deprecated: is_deprecated is_deprecated: is_deprecated
is_direct_arr: is_direct_arr is_direct_arr: is_direct_arr
is_pub: is_pub is_pub: is_pub

View File

@ -81,7 +81,6 @@ pub mut:
use_cache bool // = true use_cache bool // = true
retry_compilation 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 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 // TODO Convert this into a []string
cflags string // Additional options which will be passed to the C compiler. 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. // 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 ccompiler_type CompilerType // the type of the C compiler used
third_party_option string third_party_option string
building_v bool 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 compress bool
// skip_builtin bool // Skips re-compilation of the builtin module // skip_builtin bool // Skips re-compilation of the builtin module
// to increase compilation time. // to increase compilation time.
@ -213,6 +213,10 @@ pub fn parse_args(args []string) (&Preferences, string) {
res.autofree = true res.autofree = true
res.build_options << arg res.build_options << arg
} }
'-manualfree' {
res.autofree = false
res.build_options << arg
}
'-compress' { '-compress' {
res.compress = true res.compress = true
} }