Skip to content

Commit 083dc23

Browse files
committed
gen: implement a [manualfree] tag, for functions, that want to do their own memory management
1 parent 06bcd40 commit 083dc23

10 files changed

Lines changed: 77 additions & 53 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- `byte.str()` has been fixed and works like with all other numbers. `byte.ascii_str()` has been added.
88
- Smart cast in for loops: `for mut x is string {}`.
99
- `[noinit]` struct attribute to disallow direct struct initialization with `Foo{}`.
10+
- `[manualfree]` attribute for functions, that want to do their own memory management.
1011

1112
## V 0.2.1
1213
*30 Dec 2020*

cmd/v/help/build-c.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,10 @@ These build flags are enabled on `build` and `run` as long as the backend is set
6767

6868
-keepc
6969
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.
70+
71+
-autofree
72+
Free memory used in functions automatically.
73+
74+
-manualfree
75+
Do not free memory used in functions (the developer has to put x.free() and unsafe{free(x)} calls manually in this mode).
76+
Some short lived applications, like compilers and other CLI tools are more performant without autofree.

doc/docs.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2517,7 +2517,8 @@ Python, Go, or Java, except there's no heavy GC tracing everything or expensive
25172517
each object.
25182518

25192519
For developers willing to have more low level control, autofree can be disabled with
2520-
`-noautofree`.
2520+
`-manualfree`, or by adding a `[manualfree]` on each function that wants manage its
2521+
memory manually.
25212522

25222523
Note: right now autofree is hidden behind the -autofree flag. It will be enabled by
25232524
default in V 0.3.

vlib/v/ast/ast.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ pub:
296296
is_pub bool
297297
is_variadic bool
298298
is_anon bool
299+
is_manualfree bool // true, when [manualfree] is used on a fn
299300
receiver Field
300301
receiver_pos token.Position // `(u User)` in `fn (u User) name()` position
301302
is_method bool

vlib/v/gen/auto_str_methods.v

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
module gen
44

55
import v.table
6-
import v.pref
76
import v.util
87

98
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
230229
}
231230
}
232231
g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, x);')
233-
if g.pref.autofree && typ != table.bool_type {
232+
if g.is_autofree && typ != table.bool_type {
234233
// no need to free "true"/"false" literals
235234
g.auto_str_funcs.writeln('\t\tstring_free(&x);')
236235
}

vlib/v/gen/cgen.v

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ mut:
7878
stmt_path_pos []int // positions of each statement start, for inserting C statements before the current statement
7979
skip_stmt_pos bool // for handling if expressions + autofree (since both prepend C statements)
8080
right_is_opt bool
81-
autofree bool
81+
is_autofree bool // false, inside the bodies of fns marked with [manualfree], otherwise === g.pref.autofree
8282
indent int
8383
empty_line bool
8484
is_test bool
@@ -186,7 +186,7 @@ pub fn cgen(files []ast.File, table &table.Table, pref &pref.Preferences) string
186186
table: table
187187
pref: pref
188188
fn_decl: 0
189-
autofree: true
189+
is_autofree: true
190190
indent: -1
191191
module_built: module_built
192192
timers: util.new_timers(timers_should_print)
@@ -216,9 +216,9 @@ pub fn cgen(files []ast.File, table &table.Table, pref &pref.Preferences) string
216216
if g.file.path == '' || !g.pref.autofree {
217217
// cgen test or building V
218218
// println('autofree=false')
219-
g.autofree = false
219+
g.is_autofree = false
220220
} else {
221-
g.autofree = true
221+
g.is_autofree = true
222222
autofree_used = true
223223
}
224224
// 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
232232
}
233233
g.timers.start('cgen common')
234234
if autofree_used {
235-
g.autofree = true // so that void _vcleanup is generated
235+
g.is_autofree = true // so that void _vcleanup is generated
236236
}
237237
// to make sure type idx's are the same in cached mods
238238
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) {
799799
g.write('')
800800
g.write(')')
801801
}
802-
if g.pref.autofree && !g.inside_vweb_tmpl && stmts.len > 0 {
802+
if g.is_autofree && !g.inside_vweb_tmpl && stmts.len > 0 {
803803
// use the first stmt to get the scope
804804
stmt := stmts[0]
805805
// stmt := stmts[stmts.len-1]
@@ -878,7 +878,7 @@ fn (mut g Gen) stmt(node ast.Stmt) {
878878
}
879879
} else {
880880
// continue or break
881-
if g.pref.autofree && !g.is_builtin_mod {
881+
if g.is_autofree && !g.is_builtin_mod {
882882
g.writeln('// free before continue/break')
883883
g.autofree_scope_vars_stop(node.pos.pos - 1, node.pos.line_nr, true,
884884
g.branch_parent_pos)
@@ -935,7 +935,7 @@ fn (mut g Gen) stmt(node ast.Stmt) {
935935
}
936936
ast.ExprStmt {
937937
g.write_v_source_line_info(node.pos)
938-
// af := g.pref.autofree && node.expr is ast.CallExpr && !g.is_builtin_mod
938+
// af := g.autofree && node.expr is ast.CallExpr && !g.is_builtin_mod
939939
// if af {
940940
// g.autofree_call_pregen(node.expr as ast.CallExpr)
941941
// }
@@ -1129,9 +1129,9 @@ fn (mut g Gen) stmt(node ast.Stmt) {
11291129
}
11301130
ast.Return {
11311131
g.write_defer_stmts_when_needed()
1132-
// af := g.pref.autofree && node.exprs.len > 0 && node.exprs[0] is ast.CallExpr && !g.is_builtin_mod
1132+
// af := g.autofree && node.exprs.len > 0 && node.exprs[0] is ast.CallExpr && !g.is_builtin_mod
11331133
/*
1134-
af := g.pref.autofree && !g.is_builtin_mod
1134+
af := g.autofree && !g.is_builtin_mod
11351135
if false && af {
11361136
g.writeln('// ast.Return free')
11371137
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) {
11721172
}
11731173
// If we have temporary string exprs to free after this statement, do it. e.g.:
11741174
// `foo('a' + 'b')` => `tmp := 'a' + 'b'; foo(tmp); string_free(&tmp);`
1175-
if g.pref.autofree {
1175+
if g.is_autofree {
11761176
// if node is ast.ExprStmt {&& node.expr is ast.CallExpr {
11771177
if node !is ast.FnDecl {
11781178
// p := node.position()
@@ -1584,7 +1584,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
15841584
}
15851585
// Free the old value assigned to this string var (only if it's `str = [new value]`
15861586
// or `x.str = [new value]` )
1587-
mut af := g.pref.autofree && !g.is_builtin_mod && assign_stmt.op == .assign && assign_stmt.left_types.len ==
1587+
mut af := g.is_autofree && !g.is_builtin_mod && assign_stmt.op == .assign && assign_stmt.left_types.len ==
15881588
1 &&
15891589
(assign_stmt.left[0] is ast.Ident || assign_stmt.left[0] is ast.SelectorExpr)
15901590
// 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) {
16231623
}
16241624
// Autofree tmp arg vars
16251625
// first_right := assign_stmt.right[0]
1626-
// af := g.pref.autofree && first_right is ast.CallExpr && !g.is_builtin_mod
1626+
// af := g.autofree && first_right is ast.CallExpr && !g.is_builtin_mod
16271627
// if af {
16281628
// g.autofree_call_pregen(first_right as ast.CallExpr)
16291629
// }
@@ -1641,7 +1641,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
16411641
// }
16421642
// int pos = *(int*)_t190.data;
16431643
mut tmp_opt := ''
1644-
is_optional := g.pref.autofree &&
1644+
is_optional := g.is_autofree &&
16451645
(assign_stmt.op in [.decl_assign, .assign]) && assign_stmt.left_types.len == 1 && assign_stmt.right[0] is
16461646
ast.CallExpr
16471647
if is_optional {
@@ -1727,7 +1727,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
17271727
if left.left_type.is_ptr() {
17281728
g.write('*')
17291729
}
1730-
needs_clone := info.elem_type == table.string_type && g.pref.autofree
1730+
needs_clone := info.elem_type == table.string_type && g.is_autofree
17311731
if needs_clone {
17321732
g.write('/*1*/string_clone(')
17331733
}
@@ -1969,7 +1969,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
19691969
g.write(', ')
19701970
}
19711971
mut cloned := false
1972-
if g.autofree && right_sym.kind in [.array, .string] {
1972+
if g.is_autofree && right_sym.kind in [.array, .string] {
19731973
if g.gen_clone_assignment(val, right_sym, false) {
19741974
cloned = true
19751975
}
@@ -1980,7 +1980,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
19801980
// `pos := s.index(...
19811981
// `int pos = *(int)_t10.data;`
19821982
g.write('*($styp*)')
1983-
if g.pref.autofree {
1983+
if g.is_autofree {
19841984
g.write(tmp_opt + '.data/*FFz*/')
19851985
g.right_is_opt = false
19861986
g.is_assign_rhs = false
@@ -2037,7 +2037,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
20372037
}
20382038
}
20392039
if unwrap_optional {
2040-
if g.pref.autofree {
2040+
if g.is_autofree {
20412041
// g.write(tmp_opt + '/*FF*/')
20422042
} else {
20432043
g.write('.data')
@@ -2150,15 +2150,15 @@ fn (mut g Gen) gen_clone_assignment(val ast.Expr, right_sym table.TypeSymbol, ad
21502150
if val !is ast.Ident && val !is ast.SelectorExpr {
21512151
return false
21522152
}
2153-
if g.autofree && right_sym.kind == .array {
2153+
if g.is_autofree && right_sym.kind == .array {
21542154
// `arr1 = arr2` => `arr1 = arr2.clone()`
21552155
if add_eq {
21562156
g.write('=')
21572157
}
21582158
g.write(' array_clone_static(')
21592159
g.expr(val)
21602160
g.write(')')
2161-
} else if g.autofree && right_sym.kind == .string {
2161+
} else if g.is_autofree && right_sym.kind == .string {
21622162
if add_eq {
21632163
g.write('=')
21642164
}
@@ -2407,8 +2407,8 @@ fn (mut g Gen) expr(node ast.Expr) {
24072407
// if g.fileis('1.strings') {
24082408
// println('before:' + node.autofree_pregen)
24092409
// }
2410-
if g.pref.autofree && !g.is_builtin_mod && !g.is_js_call && g.strs_to_free0.len ==
2411-
0 && !g.inside_lambda { // && g.inside_ternary ==
2410+
if g.is_autofree && !g.is_builtin_mod && !g.is_js_call && g.strs_to_free0.len == 0 &&
2411+
!g.inside_lambda { // && g.inside_ternary ==
24122412
// if len != 0, that means we are handling call expr inside call expr (arg)
24132413
// and it'll get messed up here, since it's handled recursively in autofree_call_pregen()
24142414
// so just skip it
@@ -2419,9 +2419,9 @@ fn (mut g Gen) expr(node ast.Expr) {
24192419
g.strs_to_free0 = []
24202420
// println('pos=$node.pos.pos')
24212421
}
2422-
// if g.pref.autofree && node.autofree_pregen != '' { // g.strs_to_free0.len != 0 {
2422+
// if g.autofree && node.autofree_pregen != '' { // g.strs_to_free0.len != 0 {
24232423
/*
2424-
if g.pref.autofree {
2424+
if g.autofree {
24252425
s := g.autofree_pregen[node.pos.pos.str()]
24262426
if s != '' {
24272427
// 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) {
31673167
if elem_sym.kind == .interface_ && node.right_type != info.elem_type {
31683168
g.interface_call(node.right_type, info.elem_type)
31693169
}
3170-
// if g.pref.autofree
3170+
// if g.autofree
31713171
needs_clone := info.elem_type == table.string_type && !g.is_builtin_mod
31723172
if needs_clone {
31733173
g.write('string_clone(')
@@ -3713,12 +3713,12 @@ fn (mut g Gen) if_expr(node ast.IfExpr) {
37133713
// Always use this in -autofree, since ?: can have tmp expressions that have to be freed.
37143714
first_branch := node.branches[0]
37153715
needs_tmp_var := node.is_expr &&
3716-
(g.pref.autofree || (g.pref.experimental &&
3716+
(g.is_autofree || (g.pref.experimental &&
37173717
(first_branch.stmts.len > 1 || (first_branch.stmts[0] is ast.ExprStmt &&
37183718
(first_branch.stmts[0] as ast.ExprStmt).expr is ast.IfExpr))))
37193719
/*
37203720
needs_tmp_var := node.is_expr &&
3721-
(g.pref.autofree || g.pref.experimental) &&
3721+
(g.autofree || g.pref.experimental) &&
37223722
(node.branches[0].stmts.len > 1 || node.branches[0].stmts[0] is ast.IfExpr)
37233723
*/
37243724
tmp := if needs_tmp_var { g.new_tmp_var() } else { '' }
@@ -3957,7 +3957,7 @@ fn (mut g Gen) index_expr(node ast.IndexExpr) {
39573957
.function { 'voidptr*' }
39583958
else { '$elem_type_str*' }
39593959
}
3960-
needs_clone := info.elem_type == table.string_type_idx && g.pref.autofree &&
3960+
needs_clone := info.elem_type == table.string_type_idx && g.is_autofree &&
39613961
!g.is_assign_lhs
39623962
if needs_clone {
39633963
g.write('/*2*/string_clone(')
@@ -4141,7 +4141,7 @@ fn (mut g Gen) return_statement(node ast.Return) {
41414141
g.writeln('$styp $tmp = {.ok = true};')
41424142
g.writeln('return $tmp;')
41434143
} else {
4144-
if g.pref.autofree && !g.is_builtin_mod {
4144+
if g.is_autofree && !g.is_builtin_mod {
41454145
g.writeln('// free before return (no values returned)')
41464146
g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true)
41474147
}
@@ -4275,7 +4275,7 @@ fn (mut g Gen) return_statement(node ast.Return) {
42754275
g.writeln('return $opt_tmp;')
42764276
return
42774277
}
4278-
free := g.pref.autofree && !g.is_builtin_mod // node.exprs[0] is ast.CallExpr
4278+
free := g.is_autofree && !g.is_builtin_mod // node.exprs[0] is ast.CallExpr
42794279
mut tmp := ''
42804280
if free {
42814281
// `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
44044404
cname := '_const_$name'
44054405
g.definitions.writeln('$styp $cname; // inited later')
44064406
g.inits[mod].writeln('\t$cname = $val;')
4407-
if g.pref.autofree {
4407+
if g.is_autofree {
44084408
if styp.starts_with('array_') {
44094409
g.cleanups[mod].writeln('\tarray_free(&$cname);')
44104410
}
@@ -4491,7 +4491,7 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) {
44914491
}
44924492
field_type_sym := g.table.get_type_symbol(field.typ)
44934493
mut cloned := false
4494-
if g.autofree && !field.typ.is_ptr() && field_type_sym.kind in [.array, .string] {
4494+
if g.is_autofree && !field.typ.is_ptr() && field_type_sym.kind in [.array, .string] {
44954495
g.write('/*clone1*/')
44964496
if g.gen_clone_assignment(field.expr, field_type_sym, false) {
44974497
cloned = true
@@ -4560,7 +4560,7 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) {
45604560
mut cloned := false
45614561
is_interface := expected_field_type_sym.kind == .interface_ &&
45624562
field_type_sym.kind != .interface_
4563-
if g.autofree && !sfield.typ.is_ptr() && field_type_sym.kind in [.array, .string] {
4563+
if g.is_autofree && !sfield.typ.is_ptr() && field_type_sym.kind in [.array, .string] {
45644564
g.write('/*clone1*/')
45654565
if g.gen_clone_assignment(sfield.expr, field_type_sym, false) {
45664566
cloned = true
@@ -4705,7 +4705,7 @@ fn (mut g Gen) write_init_function() {
47054705
} else {
47064706
g.writeln('void _vinit() {')
47074707
}
4708-
if g.pref.autofree {
4708+
if g.is_autofree {
47094709
// Pre-allocate the string buffer
47104710
// s_str_buf_size := os.getenv('V_STRBUF_MB')
47114711
// 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() {
47354735
if g.pref.printfn_list.len > 0 && '_vinit' in g.pref.printfn_list {
47364736
println(g.out.after(fn_vinit_start_pos))
47374737
}
4738-
if g.autofree {
4738+
if g.is_autofree {
47394739
fn_vcleanup_start_pos := g.out.len
47404740
g.writeln('void _vcleanup() {')
47414741
// g.writeln('puts("cleaning up...");')

vlib/v/gen/cmain.v

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ fn (mut g Gen) gen_c_main_header() {
7575
g.writeln('')
7676
}
7777
if g.is_importing_os() {
78-
if g.autofree {
78+
if g.is_autofree {
7979
g.writeln('free(_const_os__args.data); // empty, inited in _vinit()')
8080
}
8181
if g.pref.os == .windows {
@@ -90,7 +90,7 @@ fn (mut g Gen) gen_c_main_header() {
9090
}
9191

9292
pub fn (mut g Gen) gen_c_main_footer() {
93-
if g.autofree {
93+
if g.is_autofree {
9494
g.writeln('\t_vcleanup();')
9595
}
9696
g.writeln('\treturn 0;')
@@ -99,7 +99,7 @@ pub fn (mut g Gen) gen_c_main_footer() {
9999

100100
pub fn (mut g Gen) gen_c_android_sokol_main() {
101101
// Weave autofree into sokol lifecycle callback(s)
102-
if g.autofree {
102+
if g.is_autofree {
103103
g.writeln('// Wrapping cleanup/free callbacks for sokol to include _vcleanup()
104104
void (*_vsokol_user_cleanup_ptr)(void);
105105
void (*_vsokol_user_cleanup_cb_ptr)(void *);
@@ -126,7 +126,7 @@ sapp_desc sokol_main(int argc, char* argv[]) {
126126
_vinit();
127127
main__main();
128128
')
129-
if g.autofree {
129+
if g.is_autofree {
130130
g.writeln(' // Wrap user provided cleanup/free functions for sokol to be able to call _vcleanup()
131131
if (g_desc.cleanup_cb) {
132132
_vsokol_user_cleanup_ptr = g_desc.cleanup_cb;
@@ -170,7 +170,7 @@ pub fn (mut g Gen) write_tests_main() {
170170
g.writeln('\tmain__BenchedTests_end_testing(&bt);')
171171
}
172172
g.writeln('')
173-
if g.autofree {
173+
if g.is_autofree {
174174
g.writeln('\t_vcleanup();')
175175
}
176176
g.writeln('\treturn g_test_fails > 0;')

0 commit comments

Comments
 (0)