| 1 | // Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license that can be found in the LICENSE file. |
| 3 | module c |
| 4 | |
| 5 | import v.ast |
| 6 | import strings |
| 7 | |
| 8 | @[inline] |
| 9 | fn (mut g Gen) register_free_method(typ ast.Type) { |
| 10 | if g.type_has_unresolved_generic_parts(typ) { |
| 11 | return |
| 12 | } |
| 13 | if typ.has_flag(.shared_f) { |
| 14 | g.get_free_method(typ.clear_flag(.shared_f).set_nr_muls(0)) |
| 15 | } else { |
| 16 | g.get_free_method(typ) |
| 17 | } |
| 18 | } |
| 19 | |
| 20 | fn (mut g Gen) get_free_method(typ ast.Type) string { |
| 21 | if typ in g.autofree_methods { |
| 22 | return g.autofree_methods[typ] |
| 23 | } |
| 24 | mut sym := g.table.sym(g.unwrap_generic(typ)) |
| 25 | if mut sym.info is ast.Alias { |
| 26 | if sym.info.is_import { |
| 27 | sym = g.table.sym(sym.info.parent_type) |
| 28 | } |
| 29 | } |
| 30 | styp := g.styp(typ).replace('*', '') |
| 31 | fn_name := styp_to_free_fn_name(styp) |
| 32 | if sym.has_method_with_generic_parent('free') { |
| 33 | g.autofree_methods[typ] = fn_name |
| 34 | return fn_name |
| 35 | } |
| 36 | g.autofree_methods[typ] = fn_name |
| 37 | return fn_name |
| 38 | } |
| 39 | |
| 40 | fn (mut g Gen) gen_free_methods() { |
| 41 | for typ, _ in g.autofree_methods { |
| 42 | if g.type_has_unresolved_generic_parts(typ) { |
| 43 | continue |
| 44 | } |
| 45 | g.gen_free_method(typ) |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | fn (mut g Gen) gen_free_method(typ ast.Type) string { |
| 50 | styp := g.styp(typ).replace('*', '') |
| 51 | mut fn_name := styp_to_free_fn_name(styp) |
| 52 | if g.type_has_unresolved_generic_parts(typ) { |
| 53 | return fn_name |
| 54 | } |
| 55 | deref_typ := if typ.has_flag(.option) { typ } else { typ.set_nr_muls(0) } |
| 56 | if deref_typ in g.generated_free_methods { |
| 57 | return fn_name |
| 58 | } |
| 59 | // Also check by C function name to prevent duplicate definitions when |
| 60 | // different type IDs map to the same C name (e.g. nested array types). |
| 61 | if fn_name in g.generated_free_fn_names { |
| 62 | return fn_name |
| 63 | } |
| 64 | g.generated_free_methods[deref_typ] = true |
| 65 | g.generated_free_fn_names[fn_name] = true |
| 66 | |
| 67 | objtyp := g.unwrap_generic(typ) |
| 68 | mut sym := g.table.sym(objtyp) |
| 69 | if mut sym.info is ast.Alias { |
| 70 | if sym.info.is_import { |
| 71 | sym = g.table.sym(sym.info.parent_type) |
| 72 | } |
| 73 | } |
| 74 | if sym.kind != .interface && sym.has_method_with_generic_parent('free') { |
| 75 | return fn_name |
| 76 | } |
| 77 | |
| 78 | match mut sym.info { |
| 79 | ast.Struct { |
| 80 | g.gen_free_for_struct(objtyp, sym.info, styp, fn_name, sym.is_builtin()) |
| 81 | } |
| 82 | ast.Array { |
| 83 | g.gen_free_for_array(sym.info, styp, fn_name) |
| 84 | } |
| 85 | ast.Map { |
| 86 | g.gen_free_for_map(objtyp, styp, fn_name) |
| 87 | } |
| 88 | ast.Interface { |
| 89 | g.gen_free_for_interface(sym, sym.info, styp, fn_name) |
| 90 | } |
| 91 | ast.SumType { |
| 92 | g.gen_free_for_sumtype(sym.info, styp, fn_name) |
| 93 | } |
| 94 | else { |
| 95 | println(g.table.type_str(typ)) |
| 96 | // print_backtrace() |
| 97 | println("could not generate free method '${fn_name}' for type '${styp}'") |
| 98 | // verror("could not generate free method '${fn_name}' for type '${styp}'") |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | return fn_name |
| 103 | } |
| 104 | |
| 105 | fn (mut g Gen) gen_free_for_interface(sym ast.TypeSymbol, info ast.Interface, styp string, fn_name string) { |
| 106 | g.definitions.writeln('${g.static_non_parallel}void ${fn_name}(${styp}* it);') |
| 107 | mut fn_builder := strings.new_builder(128) |
| 108 | defer { |
| 109 | g.auto_fn_definitions << fn_builder.str() |
| 110 | } |
| 111 | fn_builder.writeln('${g.static_non_parallel}void ${fn_name}(${styp}* it) {') |
| 112 | for t in info.types { |
| 113 | typ_ := g.unwrap_generic(t) |
| 114 | sub_sym := g.table.sym(typ_) |
| 115 | if sub_sym.kind !in [.string, .array, .map, .struct] { |
| 116 | continue |
| 117 | } |
| 118 | if !sub_sym.has_method_with_generic_parent('free') { |
| 119 | continue |
| 120 | } |
| 121 | type_styp := g.gen_type_name_for_free_call(typ_) |
| 122 | free_fn_name := if sub_sym.is_builtin() { |
| 123 | 'builtin__${type_styp}_free' |
| 124 | } else { |
| 125 | '${type_styp}_free' |
| 126 | } |
| 127 | fn_builder.writeln('\tif (it->_typ == _${sym.cname}_${sub_sym.cname}_index) { ${free_fn_name}(it->_${sub_sym.cname}); return; }') |
| 128 | } |
| 129 | fn_builder.writeln('}') |
| 130 | } |
| 131 | |
| 132 | fn (mut g Gen) gen_free_for_sumtype(info ast.SumType, styp string, fn_name string) { |
| 133 | g.definitions.writeln('${g.static_non_parallel}void ${fn_name}(${styp}* it);') |
| 134 | mut fn_builder := strings.new_builder(256) |
| 135 | defer { |
| 136 | g.auto_fn_definitions << fn_builder.str() |
| 137 | } |
| 138 | fn_builder.writeln('${g.static_non_parallel}void ${fn_name}(${styp}* it) {') |
| 139 | mut idxs := []ast.Type{} |
| 140 | for variant in info.variants { |
| 141 | if variant in idxs { |
| 142 | continue |
| 143 | } |
| 144 | idxs << variant |
| 145 | free_typ := variant.clear_flag(.option) |
| 146 | free_sym := g.table.sym(g.unwrap_generic(free_typ)) |
| 147 | variant_name := g.get_sumtype_variant_name(variant, free_sym) |
| 148 | variant_ptr := 'it->_${variant_name}' |
| 149 | fn_builder.writeln('\tif (it->_typ == ${g.type_sidx(variant)}) {') |
| 150 | if variant.has_flag(.option) { |
| 151 | match free_sym.kind { |
| 152 | .string { |
| 153 | fn_builder.writeln('\t\tif (${variant_ptr}->state != 2) {') |
| 154 | fn_builder.writeln('\t\t\tbuiltin__string_free((${g.base_type(variant)}*)${variant_ptr}->data);') |
| 155 | fn_builder.writeln('\t\t}') |
| 156 | } |
| 157 | .array, .map, .struct, .sum_type, .interface { |
| 158 | mut free_fn_name := if free_sym.has_method('free') { |
| 159 | '${g.gen_type_name_for_free_call(free_typ)}_free' |
| 160 | } else { |
| 161 | g.gen_free_method(free_typ) |
| 162 | } |
| 163 | if free_sym.is_builtin() { |
| 164 | free_fn_name = 'builtin__${free_fn_name}' |
| 165 | } |
| 166 | fn_builder.writeln('\t\tif (${variant_ptr}->state != 2) {') |
| 167 | fn_builder.writeln('\t\t\t${free_fn_name}((${g.base_type(variant)}*)${variant_ptr}->data);') |
| 168 | fn_builder.writeln('\t\t}') |
| 169 | } |
| 170 | else {} |
| 171 | } |
| 172 | } else { |
| 173 | match free_sym.kind { |
| 174 | .string { |
| 175 | fn_builder.writeln('\t\tbuiltin__string_free(${variant_ptr});') |
| 176 | } |
| 177 | .array, .map, .struct, .sum_type, .interface { |
| 178 | mut free_fn_name := if free_sym.has_method('free') { |
| 179 | '${g.gen_type_name_for_free_call(free_typ)}_free' |
| 180 | } else { |
| 181 | g.gen_free_method(free_typ) |
| 182 | } |
| 183 | if free_sym.is_builtin() { |
| 184 | free_fn_name = 'builtin__${free_fn_name}' |
| 185 | } |
| 186 | fn_builder.writeln('\t\t${free_fn_name}(${variant_ptr});') |
| 187 | } |
| 188 | else {} |
| 189 | } |
| 190 | } |
| 191 | // Sumtypes box their active payload in heap memory via memdup/HEAP. |
| 192 | fn_builder.writeln('\t\tbuiltin___v_free(${variant_ptr});') |
| 193 | fn_builder.writeln('\t\treturn;') |
| 194 | fn_builder.writeln('\t}') |
| 195 | } |
| 196 | fn_builder.writeln('}') |
| 197 | } |
| 198 | |
| 199 | fn (mut g Gen) gen_free_for_struct(typ ast.Type, info ast.Struct, styp string, ofn_name string, sym_is_builtin bool) { |
| 200 | mut fn_name := ofn_name |
| 201 | if sym_is_builtin { |
| 202 | fn_name = 'builtin__${fn_name}' |
| 203 | } |
| 204 | g.definitions.writeln('${g.static_non_parallel}void ${fn_name}(${styp}* it);') |
| 205 | mut fn_builder := strings.new_builder(128) |
| 206 | defer { |
| 207 | g.auto_fn_definitions << fn_builder.str() |
| 208 | } |
| 209 | fn_builder.writeln('${g.static_non_parallel}void ${fn_name}(${styp}* it) {') |
| 210 | for field in info.fields { |
| 211 | field_name := c_name(field.name) |
| 212 | sym := g.table.sym(g.unwrap_generic(field.typ)) |
| 213 | |
| 214 | if sym.kind !in [.string, .array, .map, .struct, .sum_type] { |
| 215 | continue |
| 216 | } |
| 217 | field_styp := g.gen_type_name_for_free_call(field.typ) |
| 218 | is_struct_option := typ.has_flag(.option) |
| 219 | free_method_typ := if field.typ.has_flag(.shared_f) { |
| 220 | field.typ.clear_flag(.shared_f).set_nr_muls(0) |
| 221 | } else { |
| 222 | field.typ |
| 223 | } |
| 224 | mut field_styp_fn_name := if sym.has_method('free') { |
| 225 | '${field_styp}_free' |
| 226 | } else { |
| 227 | g.gen_free_method(free_method_typ) |
| 228 | } |
| 229 | if sym.is_builtin() { |
| 230 | field_styp_fn_name = 'builtin__${field_styp_fn_name}' |
| 231 | } |
| 232 | is_field_option := field.typ.has_flag(.option) |
| 233 | expects_opt := field_styp_fn_name.starts_with('_option_') |
| 234 | if field.typ.has_flag(.shared_f) { |
| 235 | fn_builder.writeln('\t${field_styp_fn_name}(&(it->${field_name}->val));') |
| 236 | } else if is_struct_option { |
| 237 | opt_styp := g.base_type(typ) |
| 238 | prefix := if field.typ.is_ptr() { '' } else { '&' } |
| 239 | if is_field_option { |
| 240 | opt_field_styp := if expects_opt { |
| 241 | g.styp(field.typ) |
| 242 | } else { |
| 243 | g.base_type(field.typ) |
| 244 | } |
| 245 | suffix := if expects_opt { '' } else { '.data' } |
| 246 | |
| 247 | fn_builder.writeln('\tif (((${opt_styp}*)&it->data)->${field_name}.state != 2) {') |
| 248 | fn_builder.writeln('\t\t${field_styp_fn_name}((${opt_field_styp}*)${prefix}((${opt_styp}*)&it->data)->${field_name}${suffix});') |
| 249 | fn_builder.writeln('\t}') |
| 250 | } else { |
| 251 | fn_builder.writeln('\t${field_styp_fn_name}(${prefix}((${opt_styp}*)&it->data)->${field_name});') |
| 252 | } |
| 253 | } else { |
| 254 | if is_field_option { |
| 255 | opt_field_styp := if expects_opt { |
| 256 | g.styp(field.typ) |
| 257 | } else { |
| 258 | g.base_type(field.typ) |
| 259 | } |
| 260 | suffix := if expects_opt { '' } else { '.data' } |
| 261 | |
| 262 | fn_builder.writeln('\tif (it->${field_name}.state != 2) {') |
| 263 | fn_builder.writeln('\t\t${field_styp_fn_name}((${opt_field_styp}*)&(it->${field_name}${suffix}));') |
| 264 | fn_builder.writeln('\t}') |
| 265 | } else { |
| 266 | prefix := if field.typ.is_ptr() { '' } else { '&' } |
| 267 | fn_builder.writeln('\t${field_styp_fn_name}(${prefix}(it->${field_name}));') |
| 268 | } |
| 269 | } |
| 270 | } |
| 271 | fn_builder.writeln('}') |
| 272 | } |
| 273 | |
| 274 | fn (mut g Gen) gen_type_name_for_free_call(typ ast.Type) string { |
| 275 | mut styp := g.styp(typ.set_nr_muls(0).clear_flag(.option)).replace('*', '') |
| 276 | if styp.starts_with('__shared') { |
| 277 | styp = styp.all_after('__shared__') |
| 278 | } |
| 279 | return styp |
| 280 | } |
| 281 | |
| 282 | fn (mut g Gen) gen_free_for_array(info ast.Array, styp string, fn_name string) { |
| 283 | g.definitions.writeln('${g.static_non_parallel}void ${fn_name}(${styp}* it);') |
| 284 | mut fn_builder := strings.new_builder(128) |
| 285 | defer { |
| 286 | g.auto_fn_definitions << fn_builder.str() |
| 287 | } |
| 288 | fn_builder.writeln('${g.static_non_parallel}void ${fn_name}(${styp}* it) {') |
| 289 | |
| 290 | sym := g.table.sym(g.unwrap_generic(info.elem_type)) |
| 291 | if sym.kind in [.string, .array, .map, .struct, .sum_type] { |
| 292 | fn_builder.writeln('\tfor (${ast.int_type_name} i = 0; i < it->len; i++) {') |
| 293 | |
| 294 | mut elem_styp := g.styp(info.elem_type).replace('*', '') |
| 295 | mut elem_styp_fn_name := if sym.has_method('free') { |
| 296 | '${elem_styp}_free' |
| 297 | } else { |
| 298 | g.gen_free_method(info.elem_type) |
| 299 | } |
| 300 | if sym.is_builtin() { |
| 301 | elem_styp_fn_name = 'builtin__${elem_styp_fn_name}' |
| 302 | } |
| 303 | fn_builder.writeln('\t\t${elem_styp_fn_name}(&(((${elem_styp}*)it->data)[i]));') |
| 304 | fn_builder.writeln('\t}') |
| 305 | } |
| 306 | fn_builder.writeln('\tbuiltin__array_free(it);') |
| 307 | fn_builder.writeln('}') |
| 308 | } |
| 309 | |
| 310 | fn (mut g Gen) gen_free_for_map(typ ast.Type, styp string, fn_name string) { |
| 311 | g.definitions.writeln('${g.static_non_parallel}void ${fn_name}(${styp}* it);') |
| 312 | mut fn_builder := strings.new_builder(128) |
| 313 | defer { |
| 314 | g.auto_fn_definitions << fn_builder.str() |
| 315 | } |
| 316 | fn_builder.writeln('${g.static_non_parallel}void ${fn_name}(${styp}* it) {') |
| 317 | |
| 318 | if typ.has_flag(.option) { |
| 319 | fn_builder.writeln('\tif (it->state != 2) {') |
| 320 | fn_builder.writeln('\t\tbuiltin__map_free((map*)&it->data);') |
| 321 | fn_builder.writeln('\t}') |
| 322 | } else { |
| 323 | fn_builder.writeln('\tbuiltin__map_free(it);') |
| 324 | } |
| 325 | fn_builder.writeln('}') |
| 326 | } |
| 327 | |
| 328 | @[inline] |
| 329 | fn styp_to_free_fn_name(styp string) string { |
| 330 | return styp.replace_each(['*', '', '.', '__', ' ', '__']) + '_free' |
| 331 | } |
| 332 | |