-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
cgen: fix unused functions returning fixed arrays of custom structs #26440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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. |
spytheman
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent work.
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)