From 083dc23db8ce409a41f27e668ee54f99e158ade8 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Fri, 8 Jan 2021 14:56:55 +0200 Subject: [PATCH] gen: implement a `[manualfree]` tag, for functions, that want to do their own memory management --- CHANGELOG.md | 1 + cmd/v/help/build-c.txt | 7 ++++ doc/docs.md | 3 +- vlib/v/ast/ast.v | 1 + vlib/v/gen/auto_str_methods.v | 3 +- vlib/v/gen/cgen.v | 70 +++++++++++++++++------------------ vlib/v/gen/cmain.v | 10 ++--- vlib/v/gen/fn.v | 25 +++++++++---- vlib/v/parser/fn.v | 2 + vlib/v/pref/pref.v | 8 +++- 10 files changed, 77 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98a24e2b92..39540f10ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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* diff --git a/cmd/v/help/build-c.txt b/cmd/v/help/build-c.txt index 94a103efb8..fffc8f944c 100644 --- a/cmd/v/help/build-c.txt +++ b/cmd/v/help/build-c.txt @@ -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. diff --git a/doc/docs.md b/doc/docs.md index 2e531a2b8c..c33533e151 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -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. diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index e92618e894..0309d64615 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -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 diff --git a/vlib/v/gen/auto_str_methods.v b/vlib/v/gen/auto_str_methods.v index 05fdd8696f..84df234f85 100644 --- a/vlib/v/gen/auto_str_methods.v +++ b/vlib/v/gen/auto_str_methods.v @@ -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);') } diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 0b61ef6776..ffa766bddc 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -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...");') diff --git a/vlib/v/gen/cmain.v b/vlib/v/gen/cmain.v index 0b6d764290..ad9b7ae9df 100644 --- a/vlib/v/gen/cmain.v +++ b/vlib/v/gen/cmain.v @@ -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;') diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index b63e522f58..078364ecd3 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -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 diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index b1fedca941..738aa9a65e 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -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 diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index ca86733b2b..014da6f765 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -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 }