Repository navigation
cgen: fix unused functions returning fixed arrays of custom structs - #26440
Conversation
When `skip_unused` is enabled, generating wrapper structs for fn_ret ArrayFixed types that reference unused custom structs causes C errors. Skip generating the wrapper when the element type struct is not in used_syms. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
test is best effort, this bug presents itself on a 57k+ line library that is not public so it's hard to test. |
|
Also, used claude code to find and fix the bug. So added authored-by, I can remove if that is not desired. |
|
We don't mind if you use AI to help you. As long as you use it responsibly, and check for correctness, it's fine. |
|
@felipensp the PR looks good to me (thank you @demizer), but I wonder if there is another alternative fix, done in |
|
Will take a look. |
|
This is the patch for vlib fix that passes with test-all, seems more complex to me with the added state tracking. Perhaps this can be optimized. diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v
index c3b7a25e5..7c008fe81 100644
--- a/vlib/v/ast/table.v
+++ b/vlib/v/ast/table.v
@@ -11,31 +11,32 @@ import v.token
@[heap; minify]
pub struct UsedFeatures {
pub mut:
- dump bool // filled in by markused
- anon_fn bool // fn () { }
- auto_str bool // auto str fns
- auto_str_ptr bool // auto str fns for ptr type
- auto_str_arr bool // auto str fns for array
- arr_prepend bool // arr.prepend()
- arr_insert bool // arr.insert()
- arr_first bool // arr.first()
- arr_last bool // arr.last()
- arr_pop_left bool // arr.pop_left()
- arr_pop bool // arr.pop()
- arr_delete bool // arr.delete()
- arr_reverse bool // arr.reverse()
- arr_map bool // []map[key]value
- print_options bool // print option type
- safe_int bool // needs safe int comparison
- print_types map[int]bool // print() idx types
- used_fns map[string]bool // filled in by markused
- used_consts map[string]bool // filled in by markused
- used_globals map[string]bool // filled in by markused
- used_syms map[int]bool // filled in by markused
- used_veb_types []Type // veb context types, filled in by checker
- used_maps int // how many times maps were used, filled in by markused
- used_none int // how many times `none` was used, filled in by markused
- used_closures int // number of used closures, either directly with `fn [state] () {}`, or indirectly (though `instance.method` promotions)
+ dump bool // filled in by markused
+ anon_fn bool // fn () { }
+ auto_str bool // auto str fns
+ auto_str_ptr bool // auto str fns for ptr type
+ auto_str_arr bool // auto str fns for array
+ arr_prepend bool // arr.prepend()
+ arr_insert bool // arr.insert()
+ arr_first bool // arr.first()
+ arr_last bool // arr.last()
+ arr_pop_left bool // arr.pop_left()
+ arr_pop bool // arr.pop()
+ arr_delete bool // arr.delete()
+ arr_reverse bool // arr.reverse()
+ arr_map bool // []map[key]value
+ print_options bool // print option type
+ safe_int bool // needs safe int comparison
+ print_types map[int]bool // print() idx types
+ used_fns map[string]bool // filled in by markused
+ used_consts map[string]bool // filled in by markused
+ used_globals map[string]bool // filled in by markused
+ used_syms map[int]bool // filled in by markused
+ used_fn_ret_types map[int]bool // filled in by markused
+ used_veb_types []Type // veb context types, filled in by checker
+ used_maps int // how many times maps were used, filled in by markused
+ used_none int // how many times `none` was used, filled in by markused
+ used_closures int // number of used closures, either directly with `fn [state] () {}`, or indirectly (though `instance.method` promotions)
// json bool // json is imported
comptime_calls map[string]bool // resolved name to call on comptime
comptime_syms map[Type]bool // resolved syms (generic)
diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v
index 5391666ce..f906d978b 100644
--- a/vlib/v/gen/c/cgen.v
+++ b/vlib/v/gen/c/cgen.v
@@ -2029,6 +2029,9 @@ pub fn (mut g Gen) write_array_fixed_return_types() {
// unresolved sizes e.g. [unknown_const]int
continue
}
+ if g.pref.skip_unused && sym.idx !in g.table.used_features.used_fn_ret_types {
+ continue
+ }
mut fixed_elem_name := g.styp(info.elem_type.set_nr_muls(0))
if info.elem_type.is_ptr() {
fixed_elem_name += '*'.repeat(info.elem_type.nr_muls())
diff --git a/vlib/v/markused/markused.v b/vlib/v/markused/markused.v
index 6a4205efa..49579f3da 100644
--- a/vlib/v/markused/markused.v
+++ b/vlib/v/markused/markused.v
@@ -313,6 +313,7 @@ pub fn mark_used(mut table ast.Table, mut pref_ pref.Preferences, ast_files []&a
table.used_features.used_consts = walker.used_consts.move()
table.used_features.used_globals = walker.used_globals.move()
table.used_features.used_syms = walker.used_syms.move()
+ table.used_features.used_fn_ret_types = walker.used_fn_ret_types.move()
table.used_features.used_closures = walker.used_closures
if trace_skip_unused {
diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v
index 0c39a1bfe..50160bd31 100644
--- a/vlib/v/markused/walker.v
+++ b/vlib/v/markused/walker.v
@@ -9,24 +9,25 @@ import v.pref
pub struct Walker {
pub mut:
- table &ast.Table = unsafe { nil }
- features &ast.UsedFeatures = unsafe { nil }
- used_fns map[string]bool // used_fns['println'] == true
- trace_enabled bool
- used_consts map[string]bool // used_consts['os.args'] == true
- used_globals map[string]bool
- used_fields map[string]bool
- used_structs map[string]bool
- used_types map[ast.Type]bool
- used_syms map[int]bool
- used_arr_method map[string]bool
- used_map_method map[string]bool
- used_none int // _option_none
- used_option int // _option_ok
- used_result int // _result_ok
- used_panic int // option/result propagation
- used_closures int // fn [x] (){}, and `instance.method` used in an expression
- pref &pref.Preferences = unsafe { nil }
+ table &ast.Table = unsafe { nil }
+ features &ast.UsedFeatures = unsafe { nil }
+ used_fns map[string]bool // used_fns['println'] == true
+ trace_enabled bool
+ used_consts map[string]bool // used_consts['os.args'] == true
+ used_globals map[string]bool
+ used_fields map[string]bool
+ used_structs map[string]bool
+ used_types map[ast.Type]bool
+ used_syms map[int]bool
+ used_fn_ret_types map[int]bool
+ used_arr_method map[string]bool
+ used_map_method map[string]bool
+ used_none int // _option_none
+ used_option int // _option_ok
+ used_result int // _result_ok
+ used_panic int // option/result propagation
+ used_closures int // fn [x] (){}, and `instance.method` used in an expression
+ pref &pref.Preferences = unsafe { nil }
mut:
all_fns map[string]ast.FnDecl
all_consts map[string]ast.ConstField
@@ -539,6 +540,7 @@ fn (mut w Walker) expr(node_ ast.Expr) {
w.expr(node.expr)
w.features.dump = true
w.mark_by_type(node.expr_type)
+ w.mark_fn_ret_array_fixed(node.expr_type)
}
ast.SpawnExpr {
if node.is_expr {
@@ -1107,6 +1109,7 @@ pub fn (mut w Walker) call_expr(mut node ast.CallExpr) {
receiver_typ = node.receiver_concrete_type
}
w.mark_by_type(node.return_type)
+ w.mark_fn_ret_array_fixed(node.return_type)
mut stmt := w.all_fns[fn_name] or { return }
if !stmt.should_be_skipped && stmt.name == node.name {
if !node.is_method || receiver_typ == stmt.receiver.typ {
@@ -1135,6 +1138,7 @@ pub fn (mut w Walker) call_expr(mut node ast.CallExpr) {
w.used_option++
}
}
+ w.mark_fn_ret_array_fixed(concrete_type)
}
}
}
@@ -1188,12 +1192,34 @@ pub fn (mut w Walker) mark_fn_ret_and_params(return_type ast.Type, params []ast.
w.used_result++
}
w.mark_by_type(return_type.clear_option_and_result())
+ w.mark_fn_ret_array_fixed(return_type)
}
for param in params {
w.mark_by_type(param.typ)
}
}
+fn (mut w Walker) mark_fn_ret_array_fixed(return_type ast.Type) {
+ if return_type.idx() == 0 {
+ return
+ }
+ final_sym := w.table.final_sym(return_type)
+ if final_sym.info is ast.ArrayFixed {
+ fn_ret_idx := w.table.find_or_register_array_fixed(final_sym.info.elem_type, final_sym.info.size,
+ final_sym.info.size_expr, true)
+ w.used_fn_ret_types[fn_ret_idx] = true
+ } else if final_sym.info is ast.MultiReturn {
+ for typ in final_sym.info.types {
+ inner_sym := w.table.final_sym(typ)
+ if inner_sym.info is ast.ArrayFixed {
+ fn_ret_idx := w.table.find_or_register_array_fixed(inner_sym.info.elem_type,
+ inner_sym.info.size, inner_sym.info.size_expr, true)
+ w.used_fn_ret_types[fn_ret_idx] = true
+ }
+ }
+ }
+}
+
@[inline]
pub fn (mut w Walker) mark_by_sym_name(name string) {
if sym := w.table.find_sym(name) {
@@ -1395,6 +1421,10 @@ fn (mut w Walker) mark_resource_dependencies() {
if w.table.dumps.keys().any(ast.Type(u32(it)).has_flag(.option)) {
w.fn_by_name('str_intp')
}
+ for dump_type, _ in w.table.dumps {
+ typ := ast.idx_to_type(dump_type)
+ w.mark_fn_ret_array_fixed(typ)
+ }
}
if w.features.auto_str_ptr {
w.fn_by_name('isnil')
|
|
@demizer thank you again. |
Summary
Fix C compilation error when unused functions return fixed arrays of custom structs.
Fixes #26439
Changes
Problem
When
skip_unusedis enabled, functions returning[N]CustomStructthat are never called cause C compilation errors:The wrapper struct (
_v_Array_fixed_main__QuadVertex_4) was generated, but the element type (main__QuadVertex) was not, becauseskip_unuseddetermined it was unused.Solution
In
write_array_fixed_return_types(), skip generating the wrapper struct if its element type is an unused custom struct:Test plan
vlib/v/tests/fns/unused_fn_fixed_array_ret_test.vv test vlib/v/tests/fns/passes (146/146)