v2 / vlib / v / markused / markused.v
475 lines · 450 sloc · 14.76 KB · ef8dce9286696e114899bf40ae8952fb921d0e61
Raw
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.
3module markused
4
5import v.ast
6import v.util
7import v.pref
8
9// mark_used walks the AST, starting at main() and marks all used fns transitively.
10pub fn mark_used(mut table ast.Table, mut pref_ pref.Preferences, ast_files []&ast.File) {
11 mut all_fns, all_consts, all_globals, all_decltypes, all_structs := all_global_decl(ast_files)
12 mut generic_fns := []&ast.FnDecl{}
13 for file in ast_files {
14 generic_fns << file.generic_fns
15 }
16 util.timing_start('MARKUSED')
17 defer {
18 util.timing_measure('MARKUSED')
19 }
20
21 trace_skip_unused := pref_.compile_values['trace_skip_unused'] == 'true'
22 trace_skip_unused_all_fns := pref_.compile_values['trace_skip_unused_all_fns'] == 'true'
23 trace_skip_unused_fn_names := pref_.compile_values['trace_skip_unused_fn_names'] == 'true'
24 trace_skip_unused_just_unused_fns := pref_.compile_values['trace_skip_unused_just_unused_fns'] == 'true'
25 used_fns := pref_.compile_values['used_fns']
26
27 string_idx_str := ast.string_type_idx.str()
28 array_idx_str := ast.array_type_idx.str()
29 map_idx_str := ast.map_type_idx.str()
30 ref_map_idx_str := int(ast.map_type.ref()).str()
31 ref_densearray_idx_str := int(table.find_type('DenseArray').ref()).str()
32 ref_array_idx_str := int(ast.array_type.ref()).str()
33
34 // Functions that must be generated and can't be skipped
35 mut all_fn_root_names := []string{}
36 mut include_panic_deps := false
37 if used_fns != '' {
38 aused_fns := used_fns.split(',')
39 all_fns_keys := all_fns.keys()
40 mut matching := []string{}
41 for ufn in aused_fns {
42 if ufn.contains('*') {
43 matching_fns := all_fns_keys.filter(it.match_glob(ufn))
44 if matching_fns.len > 0 {
45 matching << matching_fns
46 }
47 } else {
48 matching << ufn
49 }
50 }
51 all_fn_root_names << matching
52 for m in matching {
53 println('> used_fn, found matching symbol: ${m}')
54 }
55 }
56 {
57 mut core_fns := [
58 'main.main',
59 ]
60 if pref_.is_bare {
61 core_fns << 'init_global_allocator' // needed for linux_bare and wasm_bare
62 }
63 if 'use_libbacktrace' in pref_.compile_defines {
64 core_fns << 'print_libbacktrace'
65 }
66 if 'callstack' in pref_.compile_defines {
67 core_fns << ref_array_idx_str + '.push'
68 core_fns << ref_array_idx_str + '.pop'
69 }
70 if pref_.autofree {
71 core_fns << string_idx_str + '.clone_static'
72 core_fns << string_idx_str + '.option_clone_static'
73 }
74 if table.used_features.auto_str || pref_.is_shared {
75 include_panic_deps = true
76 core_fns << 'isnil'
77 core_fns << '__new_array'
78 core_fns << '__new_array_noscan'
79 core_fns << '__new_array_with_multi_default'
80 core_fns << '__new_array_with_multi_default_noscan'
81 core_fns << '__new_array_with_array_default'
82 core_fns << '__new_array_with_array_default_noscan'
83 core_fns << 'new_array_from_c_array'
84 }
85 if table.used_features.arr_prepend {
86 core_fns << ref_array_idx_str + '.prepend_many'
87 core_fns << ref_array_idx_str + '.prepend_noscan'
88 }
89 if table.used_features.arr_reverse {
90 core_fns << array_idx_str + '.reverse'
91 }
92 if table.used_features.arr_pop_left {
93 core_fns << ref_array_idx_str + '.pop_left'
94 core_fns << ref_array_idx_str + '.pop_left_noscan'
95 }
96 if table.used_features.arr_pop {
97 core_fns << ref_array_idx_str + '.pop'
98 core_fns << ref_array_idx_str + '.pop_noscan'
99 }
100 if table.used_features.arr_first {
101 core_fns << array_idx_str + '.first'
102 }
103 if table.used_features.arr_last {
104 core_fns << array_idx_str + '.last'
105 }
106 if table.used_features.arr_insert {
107 core_fns << ref_array_idx_str + '.insert_many'
108 core_fns << ref_array_idx_str + '.insert_noscan'
109 }
110 if table.used_features.print_options {
111 include_panic_deps = true
112 core_fns << '_option_ok'
113 core_fns << '_result_ok'
114 }
115 if table.used_features.anon_fn {
116 core_fns << 'memdup_uncollectable'
117 core_fns << 'builtin.closure.closure_alloc'
118 core_fns << 'builtin.closure.closure_init'
119 core_fns << 'builtin.closure.closure_create'
120 core_fns << 'builtin.closure.closure_data'
121 core_fns << 'builtin.closure.closure_try_destroy'
122 }
123 if table.used_features.arr_map {
124 include_panic_deps = true
125 core_fns << '__new_array_with_map_default'
126 core_fns << 'new_map_noscan_key'
127 core_fns << ref_map_idx_str + '.clone'
128 core_fns << ref_densearray_idx_str + '.clone'
129 core_fns << map_idx_str + '.clone'
130 }
131 if pref_.trace_calls || pref_.trace_fns.len > 0 {
132 include_panic_deps = true
133 core_fns << 'C.gettid'
134 core_fns << 'v.trace_calls.on_c_main'
135 core_fns << 'v.trace_calls.current_time'
136 core_fns << 'v.trace_calls.on_call'
137 }
138 if 'C.cJSON_Parse' in all_fns {
139 core_fns << '_result_ok'
140 core_fns << 'tos5'
141 core_fns << 'time.unix' // used by json
142 core_fns << 'error'
143 include_panic_deps = true
144 }
145 if pref_.should_use_segfault_handler() {
146 core_fns << 'v_segmentation_fault_handler'
147 }
148 if pref_.is_check_overflow {
149 // add all fns in `builtin/overflow/overflow.v`
150 for op in ['add', 'sub', 'mul'] {
151 for typ in ['i8', 'u8', 'i16', 'u16', 'i32', 'u32', 'i64', 'u64'] {
152 core_fns << 'builtin.overflow.${op}_${typ}'
153 }
154 }
155 }
156 all_fn_root_names << core_fns
157 }
158 if pref_.is_bare {
159 all_fn_root_names << [
160 'strlen',
161 'memcmp',
162 'memcpy',
163 'realloc',
164 'vsnprintf',
165 'vsprintf',
166 ]
167 }
168
169 is_noscan_whitelisted := pref_.gc_mode in [.boehm_full_opt, .boehm_incr_opt, .vgc]
170
171 has_noscan := all_fn_root_names.any(it.contains('noscan')
172 && it !in ['vcalloc_noscan', 'malloc_noscan'])
173 for k, mut mfn in all_fns {
174 if trace_skip_unused_all_fns {
175 println('k: ${k} | mfn: ${mfn.name}')
176 }
177 // public/exported functions can not be skipped,
178 // especially when producing a shared library:
179 if mfn.is_pub && pref_.is_shared {
180 all_fn_root_names << k
181 continue
182 }
183 if pref_.translated && mfn.attrs.any(it.name == 'c') {
184 all_fn_root_names << k
185 continue
186 }
187 // _noscan functions/methods are selected when the active GC can honor noscan allocations.
188 if has_noscan && is_noscan_whitelisted && mfn.name.ends_with('_noscan') {
189 all_fn_root_names << k
190 continue
191 }
192 if mfn.is_method {
193 method_receiver_typename := table.type_to_str(mfn.receiver.typ)
194 if method_receiver_typename == '&sync.Channel' {
195 all_fn_root_names << k
196 continue
197 }
198 }
199 has_dot := k.contains('.')
200 if has_dot {
201 if k.ends_with('.init') || k.ends_with('.cleanup') {
202 all_fn_root_names << k
203 continue
204 }
205 if pref_.is_prof && (k.starts_with('time.vpc_now') || k.starts_with('v.profile.')) {
206 // needed for -profile
207 all_fn_root_names << k
208 continue
209 }
210 if k.ends_with('.lock') || k.ends_with('.unlock') || k.ends_with('.rlock')
211 || k.ends_with('.runlock') {
212 all_fn_root_names << k
213 continue
214 }
215 }
216
217 if k.ends_with('before_request') {
218 // TODO: add a more specific check for the .before_request() method in veb apps
219 all_fn_root_names << k
220 continue
221 }
222 // testing framework:
223 if pref_.is_test {
224 if k.starts_with('test_')
225 || (has_dot && (k.contains('.test_') || k.contains('.vtest_'))) {
226 all_fn_root_names << k
227 continue
228 }
229 if k.starts_with('testsuite_') || (has_dot && k.contains('.testsuite_')) {
230 all_fn_root_names << k
231 continue
232 }
233 }
234 if pref_.prealloc && k.starts_with('prealloc_') {
235 all_fn_root_names << k
236 continue
237 }
238 }
239
240 // handle assertions and testing framework callbacks:
241 if pref_.is_debug {
242 all_fn_root_names << 'panic_debug'
243 }
244 // tos3 is used by cgen for typeof() on sum_types at runtime:
245 all_fn_root_names << 'tos3'
246 if pref_.is_test {
247 all_fn_root_names << 'main.cb_assertion_ok'
248 all_fn_root_names << 'main.cb_assertion_failed'
249 if benched_tests_sym := table.find_sym('main.BenchedTests') {
250 bts_type := benched_tests_sym.methods[0].params[0].typ.str()
251 all_fn_root_names << bts_type + '.testing_step_start'
252 all_fn_root_names << bts_type + '.testing_step_end'
253 all_fn_root_names << bts_type + '.end_testing'
254 all_fn_root_names << 'main.start_testing'
255 }
256 }
257
258 handle_veb(mut table, mut all_fn_root_names, 'veb.Result', 'veb.filter', 'veb.Context')
259
260 if 'debug_used_features' in pref_.compile_defines {
261 eprintln('> debug_used_features: ${table.used_features}')
262 }
263
264 mut walker := Walker.new(
265 table: table
266 all_fns: all_fns
267 generic_fns: generic_fns
268 all_consts: all_consts
269 all_globals: all_globals
270 all_decltypes: all_decltypes
271 all_structs: all_structs
272 pref: pref_
273 trace_enabled: 'trace_skip_unused_walker' in pref_.compile_defines
274 )
275 walker.mark_markused_consts() // tagged with `@[markused]`
276 walker.mark_markused_globals() // tagged with `@[markused]`
277 walker.mark_markused_syms() // tagged with `@[markused]`
278 walker.mark_markused_fns() // tagged with `@[markused]`, `@[export]` and veb actions
279 walker.mark_markused_decltypes() // tagged with `@[markused]`
280 walker.mark_generic_types()
281
282 if pref_.use_cache {
283 walker.mark_by_sym_name('IError')
284 }
285
286 walker.mark_root_fns(all_fn_root_names)
287 walker.mark_generic_fn_instances()
288
289 // Mark all concrete generic type instances as used. These are created by
290 // generic_insts_to_concrete() and unwrap_generic_type_ex() for specific
291 // type instantiations. The walker may not mark them because it visits
292 // generic function bodies with unresolved (generic) AST types.
293 for sym in table.type_symbols {
294 if sym.info is ast.Struct && sym.info.concrete_types.len > 0 && !sym.info.is_generic {
295 walker.mark_by_sym(sym)
296 } else if sym.info is ast.SumType && sym.info.concrete_types.len > 0 && !sym.info.is_generic {
297 walker.mark_by_sym(sym)
298 } else if sym.info is ast.Interface && sym.info.concrete_types.len > 0
299 && !sym.info.is_generic {
300 walker.mark_by_sym(sym)
301 } else if sym.info is ast.GenericInst && sym.info.parent_idx > 0
302 && !sym.info.concrete_types.any(it.has_flag(.generic)) {
303 walker.mark_by_sym(sym)
304 } else if sym.info is ast.Thread && sym.info.return_type != ast.void_type {
305 walker.mark_by_sym(sym)
306 } else if sym.info is ast.Array {
307 elem_sym := table.sym(sym.info.elem_type)
308 if elem_sym.info is ast.Thread && elem_sym.info.return_type != ast.void_type {
309 walker.mark_by_sym(sym)
310 }
311 }
312 }
313
314 for kcon, con in all_consts {
315 if pref_.is_shared && con.is_pub {
316 walker.mark_const_as_used(kcon)
317 continue
318 }
319 if pref_.translated && con.attrs.any(it.name == 'export') {
320 walker.mark_const_as_used(kcon)
321 continue
322 }
323 }
324
325 if trace_skip_unused_fn_names {
326 for key, _ in walker.used_fns {
327 println('> used fn key: ${key}')
328 }
329 }
330
331 walker.finalize(include_panic_deps)
332
333 table.used_features.used_none = walker.used_none
334 if walker.used_none == 0 {
335 walker.used_fns.delete('${int(ast.none_type)}.str')
336 }
337
338 table.used_features.used_fns = walker.used_fns.move()
339 table.used_features.used_consts = walker.used_consts.move()
340 table.used_features.used_globals = walker.used_globals.move()
341 table.used_features.used_syms = walker.used_syms.move()
342 table.used_features.used_closures = walker.used_closures
343
344 if trace_skip_unused {
345 eprintln('>> t.used_fns: ${table.used_features.used_fns.keys()}')
346 eprintln('>> t.used_consts: ${table.used_features.used_consts.keys()}')
347 eprintln('>> t.used_globals: ${table.used_features.used_globals.keys()}')
348 eprintln('>> t.used_syms: ${table.used_features.used_syms.keys()}')
349 eprintln('>> t.used_maps: ${table.used_features.used_maps}')
350 eprintln('>> t.used_closures: ${table.used_features.used_closures}')
351 }
352 if trace_skip_unused_just_unused_fns {
353 all_fns_keys := all_fns.keys()
354 used_fns_keys := table.used_features.used_fns.keys()
355 for k in all_fns_keys {
356 if k in used_fns_keys {
357 continue
358 }
359 println('> k: ${k}')
360 }
361 }
362}
363
364fn all_global_decl_in_stmts(stmts []ast.Stmt, mut all_fns map[string]ast.FnDecl, mut all_consts map[string]ast.ConstField, mut all_globals map[string]ast.GlobalField, mut all_decltypes map[string]ast.TypeDecl, mut all_structs map[string]ast.StructDecl) {
365 for node in stmts {
366 match node {
367 ast.FnDecl {
368 fkey := node.fkey()
369 if fkey !in all_fns || !node.no_body {
370 all_fns[fkey] = node
371 }
372 }
373 ast.ConstDecl {
374 for cfield in node.fields {
375 ckey := cfield.name
376 all_consts[ckey] = cfield
377 }
378 }
379 ast.GlobalDecl {
380 for gfield in node.fields {
381 gkey := gfield.name
382 all_globals[gkey] = gfield
383 }
384 }
385 ast.StructDecl {
386 all_structs[node.name] = node
387 }
388 ast.TypeDecl {
389 if node.is_markused {
390 all_decltypes[node.name] = node
391 }
392 }
393 ast.ExprStmt {
394 match node.expr {
395 ast.IfExpr {
396 if node.expr.is_comptime {
397 // top level comptime $if
398 for branch in node.expr.branches {
399 all_global_decl_in_stmts(branch.stmts, mut all_fns, mut all_consts, mut
400 all_globals, mut all_decltypes, mut all_structs)
401 }
402 }
403 }
404 ast.MatchExpr {
405 if node.expr.is_comptime {
406 // top level comptime $match
407 for branch in node.expr.branches {
408 all_global_decl_in_stmts(branch.stmts, mut all_fns, mut all_consts, mut
409 all_globals, mut all_decltypes, mut all_structs)
410 }
411 }
412 }
413 else {}
414 }
415 }
416 else {}
417 }
418 }
419}
420
421fn all_global_decl(ast_files []&ast.File) (map[string]ast.FnDecl, map[string]ast.ConstField, map[string]ast.GlobalField, map[string]ast.TypeDecl, map[string]ast.StructDecl) {
422 util.timing_start(@METHOD)
423 defer {
424 util.timing_measure(@METHOD)
425 }
426 mut all_fns := map[string]ast.FnDecl{}
427 mut all_consts := map[string]ast.ConstField{}
428 mut all_globals := map[string]ast.GlobalField{}
429 mut all_decltypes := map[string]ast.TypeDecl{}
430 mut all_structs := map[string]ast.StructDecl{}
431 for i in 0 .. ast_files.len {
432 all_global_decl_in_stmts(ast_files[i].stmts, mut all_fns, mut all_consts, mut all_globals, mut
433 all_decltypes, mut all_structs)
434 }
435 return all_fns, all_consts, all_globals, all_decltypes, all_structs
436}
437
438fn mark_all_methods_used(mut table ast.Table, mut all_fn_root_names []string, typ ast.Type) {
439 sym := table.sym(typ)
440 styp := int(typ).str()
441 for method in sym.methods {
442 all_fn_root_names << styp + '.' + method.name
443 }
444}
445
446fn handle_veb(mut table ast.Table, mut all_fn_root_names []string, result_name string, filter_name string,
447 context_name string) {
448 // handle veb magic router methods:
449 result_type_idx := table.find_type(result_name)
450 if result_type_idx == 0 {
451 return
452 }
453 all_fn_root_names << filter_name
454 typ_veb_context := table.find_type(context_name).set_nr_muls(1)
455 mark_all_methods_used(mut table, mut all_fn_root_names, typ_veb_context)
456 for vgt in table.used_features.used_veb_types {
457 sym_app := table.sym(vgt)
458 pvgt := int(vgt.set_nr_muls(1)).str()
459 for m in sym_app.methods {
460 mut skip := true
461 if m.name == 'before_request' {
462 // TODO: handle expansion of method calls in generic functions in a more universal way
463 skip = false
464 }
465 if m.return_type == result_type_idx {
466 skip = false
467 }
468 if skip {
469 continue
470 }
471 // eprintln('vgt: ${vgt} | pvgt: ${pvgt} | sym_app.name: ${sym_app.name} | m.name: ${m.name}')
472 all_fn_root_names << pvgt + '.' + m.name
473 }
474 }
475}
476