| 1 | // Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module c |
| 5 | |
| 6 | import strings |
| 7 | import v.ast |
| 8 | |
| 9 | fn (g &Gen) needs_scope_cleanup() bool { |
| 10 | return g.is_autofree || g.pref.gc_mode == .boehm_leak |
| 11 | } |
| 12 | |
| 13 | fn (mut g Gen) autofree_scope_vars(pos int, line_nr int, free_parent_scopes bool) { |
| 14 | if !g.needs_scope_cleanup() { |
| 15 | return |
| 16 | } |
| 17 | // g.writeln('// afsv pos=${pos} line_nr=${line_nr} freeparent_scopes=${free_parent_scopes}') |
| 18 | g.autofree_scope_vars_stop(pos, line_nr, free_parent_scopes, -1) |
| 19 | } |
| 20 | |
| 21 | fn (mut g Gen) autofree_scope_vars_stop(pos int, line_nr int, free_parent_scopes bool, stop_pos int) { |
| 22 | if !g.needs_scope_cleanup() { |
| 23 | return |
| 24 | } |
| 25 | if g.is_builtin_mod { |
| 26 | // In `builtin` everything is freed manually. |
| 27 | return |
| 28 | } |
| 29 | if pos == -1 { |
| 30 | // TODO: why can pos be -1? |
| 31 | return |
| 32 | } |
| 33 | // eprintln('> free_scope_vars(${pos})') |
| 34 | scope := g.file.scope.innermost(pos) |
| 35 | // g.writeln('// scope start pos=${scope.start_pos} ') |
| 36 | if scope.start_pos == 0 { |
| 37 | // TODO: why can scope.pos be 0? (only outside fns?) |
| 38 | return |
| 39 | } |
| 40 | g.trace_autofree('// autofree_scope_vars(pos=${pos} line_nr=${line_nr} scope.pos=${scope.start_pos} scope.end_pos=${scope.end_pos})') |
| 41 | g.autofree_scope_vars2(scope, scope.start_pos, scope.end_pos, line_nr, free_parent_scopes, |
| 42 | stop_pos) |
| 43 | } |
| 44 | |
| 45 | @[if trace_autofree ?] |
| 46 | fn (mut g Gen) trace_autofree(line string) { |
| 47 | g.writeln(line) |
| 48 | } |
| 49 | |
| 50 | //@[if print_autofree_vars ?] |
| 51 | // fn (mut g Gen) print_autofree_var(var string, position string, comment string) { |
| 52 | fn (mut g Gen) print_autofree_var(var ast.Var, comment string) { |
| 53 | if !g.pref.print_autofree_vars && g.pref.print_autofree_vars_in_fn == '' { |
| 54 | return |
| 55 | } |
| 56 | println('autofree: ${g.file.path}:${var.pos.line_nr}: skipping `${var.name}` in fn `${g.last_fn_c_name}`. ${comment}') |
| 57 | } |
| 58 | |
| 59 | fn (mut g Gen) autofree_scope_vars2(scope &ast.Scope, start_pos int, end_pos int, line_nr int, free_parent_scopes bool, |
| 60 | stop_pos int) { |
| 61 | if scope == unsafe { nil } { |
| 62 | return |
| 63 | } |
| 64 | g.trace_autofree('// scopeobjects.len == ${scope.objects.len}') |
| 65 | for _, obj in scope.objects { |
| 66 | match obj { |
| 67 | ast.Var { |
| 68 | g.trace_autofree('// var "${obj.name}" var.pos=${obj.pos.pos} var.line_nr=${obj.pos.line_nr}') |
| 69 | if obj.name in g.returned_var_names { |
| 70 | g.print_autofree_var(obj, 'returned from function') |
| 71 | g.trace_autofree('// skipping returned var') |
| 72 | continue |
| 73 | } |
| 74 | if obj.is_or { |
| 75 | // Skip vars inited with the `or {}`, since they are generated |
| 76 | // after the or block in C. |
| 77 | g.trace_autofree('// skipping `or {}` var "${obj.name}"') |
| 78 | continue |
| 79 | } |
| 80 | if obj.is_tmp { |
| 81 | // Skip for loop vars |
| 82 | g.print_autofree_var(obj, 'tmp var (loop?)') |
| 83 | g.trace_autofree('// skipping tmp var "${obj.name}"') |
| 84 | continue |
| 85 | } |
| 86 | if obj.is_inherited { |
| 87 | g.print_autofree_var(obj, 'inherited') |
| 88 | g.trace_autofree('// skipping inherited var "${obj.name}"') |
| 89 | continue |
| 90 | } |
| 91 | // if var.typ == 0 { |
| 92 | // // TODO: why 0? |
| 93 | // continue |
| 94 | // } |
| 95 | // if v.pos.pos > end_pos { |
| 96 | if obj.pos.pos > end_pos |
| 97 | || (obj.pos.pos < start_pos && obj.pos.line_nr == line_nr) |
| 98 | || (end_pos < scope.end_pos && obj.expr is ast.IfExpr) { |
| 99 | // Do not free vars that were declared after this scope |
| 100 | continue |
| 101 | } |
| 102 | if obj.expr is ast.IfGuardExpr { |
| 103 | continue |
| 104 | } |
| 105 | if obj.expr is ast.UnsafeExpr && obj.expr.expr is ast.CallExpr |
| 106 | && (obj.expr.expr as ast.CallExpr).is_method { |
| 107 | if left_var := scope.objects[obj.expr.expr.left.str()] { |
| 108 | if func := g.table.find_method(g.table.final_sym(left_var.typ), |
| 109 | obj.expr.expr.name) |
| 110 | { |
| 111 | if func.attrs.contains('reused') && left_var is ast.Var |
| 112 | && left_var.expr is ast.CastExpr { |
| 113 | if left_var.expr.expr.is_literal() { |
| 114 | continue |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | } |
| 119 | } |
| 120 | g.autofree_variable(obj) |
| 121 | } |
| 122 | else {} |
| 123 | } |
| 124 | } |
| 125 | for g.autofree_scope_stmts.len > 0 { |
| 126 | g.write(g.autofree_scope_stmts.pop()) |
| 127 | } |
| 128 | // Free all vars in parent scopes as well: |
| 129 | // ``` |
| 130 | // s := ... |
| 131 | // if ... { |
| 132 | // s.free() |
| 133 | // return |
| 134 | // } |
| 135 | // ``` |
| 136 | // if scope.parent != unsafe { nil } && line_nr > 0 { |
| 137 | if free_parent_scopes && scope.parent != unsafe { nil } && !scope.detached_from_parent |
| 138 | && (stop_pos == -1 || scope.parent.start_pos >= stop_pos) { |
| 139 | g.trace_autofree('// af parent scope:') |
| 140 | g.autofree_scope_vars2(scope.parent, start_pos, end_pos, line_nr, true, stop_pos) |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | fn (mut g Gen) autofree_variable(v ast.Var) { |
| 145 | // filter out invalid variables |
| 146 | if v.typ == 0 { |
| 147 | return |
| 148 | } |
| 149 | sym := g.table.sym(v.typ) |
| 150 | // if v.name.contains('output2') { |
| 151 | if g.is_autofree { |
| 152 | // eprintln(' > var name: ${v.name:-20s} | is_arg: ${v.is_arg.str():6} | var type: ${int(v.typ):8} | type_name: ${sym.name:-33s}') |
| 153 | } |
| 154 | // } |
| 155 | base_typ := v.typ.set_nr_muls(0).clear_option_and_result() |
| 156 | if g.type_has_unresolved_generic_parts(base_typ) { |
| 157 | g.print_autofree_var(v, 'unresolved generic type') |
| 158 | return |
| 159 | } |
| 160 | mut free_fn := g.styp(base_typ) + '_free' |
| 161 | if sym.kind == .array { |
| 162 | free_fn = g.get_free_method(base_typ) |
| 163 | g.autofree_var_call(free_fn, v) |
| 164 | return |
| 165 | } |
| 166 | if sym.kind == .map { |
| 167 | free_fn = g.get_free_method(base_typ) |
| 168 | g.autofree_var_call(free_fn, v) |
| 169 | return |
| 170 | } |
| 171 | if sym.kind == .string { |
| 172 | // Don't free simple string literals. |
| 173 | match v.expr { |
| 174 | ast.StringLiteral { |
| 175 | g.print_autofree_var(v, 'string literal') |
| 176 | g.trace_autofree('// str literal') |
| 177 | } |
| 178 | else { |
| 179 | // NOTE/TODO: assign_stmt multi returns variables have no expr |
| 180 | // since the type comes from the called fns return type |
| 181 | /* |
| 182 | f := v.name[0] |
| 183 | if |
| 184 | //!(f >= `a` && f <= `d`) { |
| 185 | //f != `c` { |
| 186 | v.name!='cvar_name' { |
| 187 | t := typeof(v.expr) |
| 188 | return '// other ' + t + '\n' |
| 189 | } |
| 190 | */ |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | g.autofree_var_call('builtin__string_free', v) |
| 195 | return |
| 196 | } |
| 197 | if sym.is_builtin() { |
| 198 | free_fn = 'builtin__${free_fn}' |
| 199 | } |
| 200 | // Free user reference types |
| 201 | is_user_ref := v.typ.is_ptr() && sym.name.after('.')[0].is_capital() |
| 202 | // if g.pref.experimental && v.typ.is_ptr() && sym.name.after('.')[0].is_capital() { |
| 203 | if is_user_ref { |
| 204 | if g.pref.experimental { |
| 205 | g.autofree_var_call('free', v) |
| 206 | } else { |
| 207 | g.print_autofree_var(v, 'user reference type, use -experimental to autofree those') |
| 208 | } |
| 209 | } |
| 210 | if sym.has_method('free') { |
| 211 | g.autofree_var_call(free_fn, v) |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | fn (mut g Gen) autofree_var_call(free_fn_name string, v ast.Var) { |
| 216 | if v.is_arg { |
| 217 | // fn args should not be autofreed |
| 218 | return |
| 219 | } |
| 220 | if v.is_used && v.is_autofree_tmp { |
| 221 | // tmp expr vars do not need to be freed again here |
| 222 | return |
| 223 | } |
| 224 | if g.is_builtin_mod { |
| 225 | return |
| 226 | } |
| 227 | if !g.needs_scope_cleanup() { |
| 228 | return |
| 229 | } |
| 230 | // if v.is_autofree_tmp && !g.doing_autofree_tmp { |
| 231 | // return |
| 232 | // } |
| 233 | if v.name.contains('expr_write_string_1_') { |
| 234 | // TODO: remove this temporary hack |
| 235 | return |
| 236 | } |
| 237 | mut af := strings.new_builder(128) |
| 238 | if v.typ.is_ptr() && v.typ.idx() != ast.u8_type_idx { |
| 239 | if !v.is_auto_heap && !g.table.sym(v.typ).has_method('free') { |
| 240 | return |
| 241 | } |
| 242 | af.write_string('\t') |
| 243 | if v.typ.share() == .shared_t { |
| 244 | af.write_string(free_fn_name.replace_each(['__shared__', ''])) |
| 245 | } else { |
| 246 | af.write_string(free_fn_name) |
| 247 | } |
| 248 | af.write_string('(') |
| 249 | if v.typ.has_flag(.option) { |
| 250 | base_type := g.base_type(v.typ) |
| 251 | af.write_string('(${base_type}*)') |
| 252 | } |
| 253 | if v.typ.share() == .shared_t { |
| 254 | af.write_string('&') |
| 255 | } |
| 256 | af.write_string(strings.repeat(`*`, v.typ.nr_muls() - 1)) // dereference if it is a pointer to a pointer |
| 257 | af.write_string(c_name(v.name)) |
| 258 | if v.typ.share() == .shared_t { |
| 259 | af.write_string('->val') |
| 260 | } |
| 261 | if v.typ.has_flag(.option) { |
| 262 | af.write_string('.data)') |
| 263 | } |
| 264 | |
| 265 | af.writeln('); // autofreed ptr var') |
| 266 | } else { |
| 267 | if v.typ == ast.error_type && !v.is_autofree_tmp { |
| 268 | return |
| 269 | } |
| 270 | if v.is_auto_heap { |
| 271 | af.writeln('\t${free_fn_name}(${c_name(v.name)}); // autofreed heap var ${g.cur_mod.name} ${g.is_builtin_mod}') |
| 272 | } else if v.typ.has_flag(.option) { |
| 273 | base_type := g.base_type(v.typ) |
| 274 | af.writeln('\tif (${c_name(v.name)}.state != 2) {') |
| 275 | af.writeln('\t\t${free_fn_name}((${base_type}*)${c_name(v.name)}.data); // autofreed option var ${g.cur_mod.name} ${g.is_builtin_mod}') |
| 276 | af.writeln('\t}') |
| 277 | } else if v.typ.idx() != ast.u8_type_idx { |
| 278 | af.writeln('\t${free_fn_name}(&${c_name(v.name)}); // autofreed var ${g.cur_mod.name} ${g.is_builtin_mod}') |
| 279 | } |
| 280 | } |
| 281 | g.autofree_scope_stmts << af.str() |
| 282 | } |
| 283 | |
| 284 | fn (mut g Gen) detect_used_var_on_return(expr ast.Expr) { |
| 285 | match expr { |
| 286 | ast.Ident { |
| 287 | g.returned_var_names[expr.name] = true |
| 288 | } |
| 289 | ast.StructInit { |
| 290 | for field_expr in expr.init_fields { |
| 291 | g.detect_used_var_on_return(field_expr.expr) |
| 292 | } |
| 293 | } |
| 294 | else {} |
| 295 | } |
| 296 | } |
| 297 | |