v4 / vlib / v / markused / markused.v
476 lines · 451 sloc · 14.8 KB · 1a2d0e5b70d6dd1baaa60290f2446e9ece4324bf
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'
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_create_with_data'
121 core_fns << 'builtin.closure.closure_data'
122 core_fns << 'builtin.closure.closure_try_destroy'
123 }
124 if table.used_features.arr_map {
125 include_panic_deps = true
126 core_fns << '__new_array_with_map_default'
127 core_fns << 'new_map_noscan_key'
128 core_fns << ref_map_idx_str + '.clone'
129 core_fns << ref_densearray_idx_str + '.clone'
130 core_fns << map_idx_str + '.clone'
131 }
132 if pref_.trace_calls || pref_.trace_fns.len > 0 {
133 include_panic_deps = true
134 core_fns << 'C.gettid'
135 core_fns << 'v.trace_calls.on_c_main'
136 core_fns << 'v.trace_calls.current_time'
137 core_fns << 'v.trace_calls.on_call'
138 }
139 if 'C.cJSON_Parse' in all_fns {
140 core_fns << '_result_ok'
141 core_fns << 'tos5'
142 core_fns << 'time.unix' // used by json
143 core_fns << 'error'
144 include_panic_deps = true
145 }
146 if pref_.should_use_segfault_handler() {
147 core_fns << 'v_segmentation_fault_handler'
148 }
149 if pref_.is_check_overflow {
150 // add all fns in `builtin/overflow/overflow.v`
151 for op in ['add', 'sub', 'mul'] {
152 for typ in ['i8', 'u8', 'i16', 'u16', 'i32', 'u32', 'i64', 'u64'] {
153 core_fns << 'builtin.overflow.${op}_${typ}'
154 }
155 }
156 }
157 all_fn_root_names << core_fns
158 }
159 if pref_.is_bare {
160 all_fn_root_names << [
161 'strlen',
162 'memcmp',
163 'memcpy',
164 'realloc',
165 'vsnprintf',
166 'vsprintf',
167 ]
168 }
169
170 is_noscan_whitelisted := pref_.gc_mode in [.boehm_full_opt, .boehm_incr_opt, .vgc]
171
172 has_noscan := all_fn_root_names.any(it.contains('noscan')
173 && it !in ['vcalloc_noscan', 'malloc_noscan'])
174 for k, mut mfn in all_fns {
175 if trace_skip_unused_all_fns {
176 println('k: ${k} | mfn: ${mfn.name}')
177 }
178 // public/exported functions can not be skipped,
179 // especially when producing a shared library:
180 if mfn.is_pub && pref_.is_shared {
181 all_fn_root_names << k
182 continue
183 }
184 if pref_.translated && mfn.attrs.any(it.name == 'c') {
185 all_fn_root_names << k
186 continue
187 }
188 // _noscan functions/methods are selected when the active GC can honor noscan allocations.
189 if has_noscan && is_noscan_whitelisted && mfn.name.ends_with('_noscan') {
190 all_fn_root_names << k
191 continue
192 }
193 if mfn.is_method {
194 method_receiver_typename := table.type_to_str(mfn.receiver.typ)
195 if method_receiver_typename == '&sync.Channel' {
196 all_fn_root_names << k
197 continue
198 }
199 }
200 has_dot := k.contains('.')
201 if has_dot {
202 if k.ends_with('.init') || k.ends_with('.cleanup') {
203 all_fn_root_names << k
204 continue
205 }
206 if pref_.is_prof && (k.starts_with('time.vpc_now') || k.starts_with('v.profile.')) {
207 // needed for -profile
208 all_fn_root_names << k
209 continue
210 }
211 if k.ends_with('.lock') || k.ends_with('.unlock') || k.ends_with('.rlock')
212 || k.ends_with('.runlock') {
213 all_fn_root_names << k
214 continue
215 }
216 }
217
218 if k.ends_with('before_request') {
219 // TODO: add a more specific check for the .before_request() method in veb apps
220 all_fn_root_names << k
221 continue
222 }
223 // testing framework:
224 if pref_.is_test {
225 if k.starts_with('test_')
226 || (has_dot && (k.contains('.test_') || k.contains('.vtest_'))) {
227 all_fn_root_names << k
228 continue
229 }
230 if k.starts_with('testsuite_') || (has_dot && k.contains('.testsuite_')) {
231 all_fn_root_names << k
232 continue
233 }
234 }
235 if pref_.prealloc && k.starts_with('prealloc_') {
236 all_fn_root_names << k
237 continue
238 }
239 }
240
241 // handle assertions and testing framework callbacks:
242 if pref_.is_debug {
243 all_fn_root_names << 'panic_debug'
244 }
245 // tos3 is used by cgen for typeof() on sum_types at runtime:
246 all_fn_root_names << 'tos3'
247 if pref_.is_test {
248 all_fn_root_names << 'main.cb_assertion_ok'
249 all_fn_root_names << 'main.cb_assertion_failed'
250 if benched_tests_sym := table.find_sym('main.BenchedTests') {
251 bts_type := benched_tests_sym.methods[0].params[0].typ.str()
252 all_fn_root_names << bts_type + '.testing_step_start'
253 all_fn_root_names << bts_type + '.testing_step_end'
254 all_fn_root_names << bts_type + '.end_testing'
255 all_fn_root_names << 'main.start_testing'
256 }
257 }
258
259 handle_veb(mut table, mut all_fn_root_names, 'veb.Result', 'veb.filter', 'veb.Context')
260
261 if 'debug_used_features' in pref_.compile_defines {
262 eprintln('> debug_used_features: ${table.used_features}')
263 }
264
265 mut walker := Walker.new(
266 table: table
267 all_fns: all_fns
268 generic_fns: generic_fns
269 all_consts: all_consts
270 all_globals: all_globals
271 all_decltypes: all_decltypes
272 all_structs: all_structs
273 pref: pref_
274 trace_enabled: 'trace_skip_unused_walker' in pref_.compile_defines
275 )
276 walker.mark_markused_consts() // tagged with `@[markused]`
277 walker.mark_markused_globals() // tagged with `@[markused]`
278 walker.mark_markused_syms() // tagged with `@[markused]`
279 walker.mark_markused_fns() // tagged with `@[markused]`, `@[export]` and veb actions
280 walker.mark_markused_decltypes() // tagged with `@[markused]`
281 walker.mark_generic_types()
282
283 if pref_.use_cache {
284 walker.mark_by_sym_name('IError')
285 }
286
287 walker.mark_root_fns(all_fn_root_names)
288 walker.mark_generic_fn_instances()
289
290 // Mark all concrete generic type instances as used. These are created by
291 // generic_insts_to_concrete() and unwrap_generic_type_ex() for specific
292 // type instantiations. The walker may not mark them because it visits
293 // generic function bodies with unresolved (generic) AST types.
294 for sym in table.type_symbols {
295 if sym.info is ast.Struct && sym.info.concrete_types.len > 0 && !sym.info.is_generic {
296 walker.mark_by_sym(sym)
297 } else if sym.info is ast.SumType && sym.info.concrete_types.len > 0 && !sym.info.is_generic {
298 walker.mark_by_sym(sym)
299 } else if sym.info is ast.Interface && sym.info.concrete_types.len > 0
300 && !sym.info.is_generic {
301 walker.mark_by_sym(sym)
302 } else if sym.info is ast.GenericInst && sym.info.parent_idx > 0
303 && !sym.info.concrete_types.any(it.has_flag(.generic)) {
304 walker.mark_by_sym(sym)
305 } else if sym.info is ast.Thread && sym.info.return_type != ast.void_type {
306 walker.mark_by_sym(sym)
307 } else if sym.info is ast.Array {
308 elem_sym := table.sym(sym.info.elem_type)
309 if elem_sym.info is ast.Thread && elem_sym.info.return_type != ast.void_type {
310 walker.mark_by_sym(sym)
311 }
312 }
313 }
314
315 for kcon, con in all_consts {
316 if pref_.is_shared && con.is_pub {
317 walker.mark_const_as_used(kcon)
318 continue
319 }
320 if pref_.translated && con.attrs.any(it.name == 'export') {
321 walker.mark_const_as_used(kcon)
322 continue
323 }
324 }
325
326 if trace_skip_unused_fn_names {
327 for key, _ in walker.used_fns {
328 println('> used fn key: ${key}')
329 }
330 }
331
332 walker.finalize(include_panic_deps)
333
334 table.used_features.used_none = walker.used_none
335 if walker.used_none == 0 {
336 walker.used_fns.delete('${int(ast.none_type)}.str')
337 }
338
339 table.used_features.used_fns = walker.used_fns.move()
340 table.used_features.used_consts = walker.used_consts.move()
341 table.used_features.used_globals = walker.used_globals.move()
342 table.used_features.used_syms = walker.used_syms.move()
343 table.used_features.used_closures = walker.used_closures
344
345 if trace_skip_unused {
346 eprintln('>> t.used_fns: ${table.used_features.used_fns.keys()}')
347 eprintln('>> t.used_consts: ${table.used_features.used_consts.keys()}')
348 eprintln('>> t.used_globals: ${table.used_features.used_globals.keys()}')
349 eprintln('>> t.used_syms: ${table.used_features.used_syms.keys()}')
350 eprintln('>> t.used_maps: ${table.used_features.used_maps}')
351 eprintln('>> t.used_closures: ${table.used_features.used_closures}')
352 }
353 if trace_skip_unused_just_unused_fns {
354 all_fns_keys := all_fns.keys()
355 used_fns_keys := table.used_features.used_fns.keys()
356 for k in all_fns_keys {
357 if k in used_fns_keys {
358 continue
359 }
360 println('> k: ${k}')
361 }
362 }
363}
364
365fn 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) {
366 for node in stmts {
367 match node {
368 ast.FnDecl {
369 fkey := node.fkey()
370 if fkey !in all_fns || !node.no_body {
371 all_fns[fkey] = node
372 }
373 }
374 ast.ConstDecl {
375 for cfield in node.fields {
376 ckey := cfield.name
377 all_consts[ckey] = cfield
378 }
379 }
380 ast.GlobalDecl {
381 for gfield in node.fields {
382 gkey := gfield.name
383 all_globals[gkey] = gfield
384 }
385 }
386 ast.StructDecl {
387 all_structs[node.name] = node
388 }
389 ast.TypeDecl {
390 if node.is_markused {
391 all_decltypes[node.name] = node
392 }
393 }
394 ast.ExprStmt {
395 match node.expr {
396 ast.IfExpr {
397 if node.expr.is_comptime {
398 // top level comptime $if
399 for branch in node.expr.branches {
400 all_global_decl_in_stmts(branch.stmts, mut all_fns, mut all_consts, mut
401 all_globals, mut all_decltypes, mut all_structs)
402 }
403 }
404 }
405 ast.MatchExpr {
406 if node.expr.is_comptime {
407 // top level comptime $match
408 for branch in node.expr.branches {
409 all_global_decl_in_stmts(branch.stmts, mut all_fns, mut all_consts, mut
410 all_globals, mut all_decltypes, mut all_structs)
411 }
412 }
413 }
414 else {}
415 }
416 }
417 else {}
418 }
419 }
420}
421
422fn 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) {
423 util.timing_start(@METHOD)
424 defer {
425 util.timing_measure(@METHOD)
426 }
427 mut all_fns := map[string]ast.FnDecl{}
428 mut all_consts := map[string]ast.ConstField{}
429 mut all_globals := map[string]ast.GlobalField{}
430 mut all_decltypes := map[string]ast.TypeDecl{}
431 mut all_structs := map[string]ast.StructDecl{}
432 for i in 0 .. ast_files.len {
433 all_global_decl_in_stmts(ast_files[i].stmts, mut all_fns, mut all_consts, mut all_globals, mut
434 all_decltypes, mut all_structs)
435 }
436 return all_fns, all_consts, all_globals, all_decltypes, all_structs
437}
438
439fn mark_all_methods_used(mut table ast.Table, mut all_fn_root_names []string, typ ast.Type) {
440 sym := table.sym(typ)
441 styp := int(typ).str()
442 for method in sym.methods {
443 all_fn_root_names << styp + '.' + method.name
444 }
445}
446
447fn handle_veb(mut table ast.Table, mut all_fn_root_names []string, result_name string, filter_name string,
448 context_name string) {
449 // handle veb magic router methods:
450 result_type_idx := table.find_type(result_name)
451 if result_type_idx == 0 {
452 return
453 }
454 all_fn_root_names << filter_name
455 typ_veb_context := table.find_type(context_name).set_nr_muls(1)
456 mark_all_methods_used(mut table, mut all_fn_root_names, typ_veb_context)
457 for vgt in table.used_features.used_veb_types {
458 sym_app := table.sym(vgt)
459 pvgt := int(vgt.set_nr_muls(1)).str()
460 for m in sym_app.methods {
461 mut skip := true
462 if m.name == 'before_request' {
463 // TODO: handle expansion of method calls in generic functions in a more universal way
464 skip = false
465 }
466 if m.return_type == result_type_idx {
467 skip = false
468 }
469 if skip {
470 continue
471 }
472 // eprintln('vgt: ${vgt} | pvgt: ${pvgt} | sym_app.name: ${sym_app.name} | m.name: ${m.name}')
473 all_fn_root_names << pvgt + '.' + m.name
474 }
475 }
476}
477