From 3690f882f624c3d82b8c36d38434baf10f68ce4c Mon Sep 17 00:00:00 2001 From: GGRei Date: Wed, 10 Jun 2026 05:32:03 +0200 Subject: [PATCH] v2: cleanc selfhost c output (#27376) --- vlib/v/trace_calls/tracing_calls.c.v | 4 +- vlib/v2/gen/cleanc/cheaders.v | 31 + vlib/v2/gen/cleanc/expr.v | 84 +- vlib/v2/gen/cleanc/flag_enum_codegen_test.v | 2 +- vlib/v2/gen/cleanc/fn.v | 106 +++ vlib/v2/gen/cleanc/stmt.v | 2 +- vlib/v2/gen/cleanc/target_codegen_test.v | 998 ++++++++++++++++++++ vlib/v2/gen/cleanc/types.v | 16 + 8 files changed, 1238 insertions(+), 5 deletions(-) diff --git a/vlib/v/trace_calls/tracing_calls.c.v b/vlib/v/trace_calls/tracing_calls.c.v index 87ae3dc3e..c4c77ba65 100644 --- a/vlib/v/trace_calls/tracing_calls.c.v +++ b/vlib/v/trace_calls/tracing_calls.c.v @@ -15,7 +15,9 @@ pub fn on_call(fname string) { unsafe { $if windows { tid = C.GetCurrentThreadId() - } $else $if linux { + } $else $if no_gettid ? { + tid = u32(C.pthread_self()) + } $else $if linux && !musl ? { tid = C.gettid() } $else { tid = u32(C.pthread_self()) diff --git a/vlib/v2/gen/cleanc/cheaders.v b/vlib/v2/gen/cleanc/cheaders.v index dda3078fe..4f992bd1b 100644 --- a/vlib/v2/gen/cleanc/cheaders.v +++ b/vlib/v2/gen/cleanc/cheaders.v @@ -238,6 +238,17 @@ const apple_late_full_includes_plain = r'#include #include ' +const linux_gettid_feature_define = r'#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +' + +const linux_gettid_helper = r'#include +static inline uint32_t v_cleanc_gettid(void) { + return (uint32_t)syscall(SYS_gettid); +} +' + const apple_macos_cross_guard = 'defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__)' const apple_ios_cross_guard = 'defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__)' const freestanding_missing_alloc_hook_message = 'v2: freestanding target requires freestanding_alloc hook for heap allocation' @@ -520,6 +531,10 @@ fn (g &Gen) preamble_includes(minimal_preamble bool) string { return raw } mut out := raw + if g.should_emit_linux_gettid_compat() { + out = out.replace('// Generated by V Clean C Backend\n', + '// Generated by V Clean C Backend\n${linux_gettid_feature_define}') + } if target_os == 'macos' { out = out.replace(apple_minimal_includes_guarded, apple_minimal_includes_plain) out = out.replace(apple_full_includes_guarded, apple_full_includes_plain) @@ -532,6 +547,21 @@ fn (g &Gen) preamble_includes(minimal_preamble bool) string { return out } +fn (g &Gen) should_emit_linux_gettid_compat() bool { + return !g.is_freestanding_target() && g.target_os_name() == 'linux' + && !g.has_target_define('no_gettid') && !g.has_target_define('musl') +} + +fn (mut g Gen) emit_linux_gettid_compat(minimal_preamble bool) { + if !g.should_emit_linux_gettid_compat() { + return + } + if minimal_preamble { + g.sb.writeln('#include ') + } + g.sb.write_string(linux_gettid_helper) +} + fn (g &Gen) directive_ct_flag_matches(name string) bool { lower_name := name.to_lower() if g.target_os_name() == 'cross' && is_target_os_ct_flag(lower_name) { @@ -1574,6 +1604,7 @@ fn (mut g Gen) emit_target_rwmutex() { fn (mut g Gen) write_preamble() { minimal_preamble := g.use_minimal_preamble() g.sb.write_string(g.preamble_includes(minimal_preamble)) + g.emit_linux_gettid_compat(minimal_preamble) g.emit_stdatomic_compat_include() g.emit_v_architecture_macros() g.emit_v_commit_hash_fallback() diff --git a/vlib/v2/gen/cleanc/expr.v b/vlib/v2/gen/cleanc/expr.v index 67088e075..b88a02bb7 100644 --- a/vlib/v2/gen/cleanc/expr.v +++ b/vlib/v2/gen/cleanc/expr.v @@ -962,6 +962,63 @@ fn (mut g Gen) fn_type_alias_name_for_base_expr(base ast.Expr) ?string { return none } +fn (mut g Gen) exact_fn_type_alias_cast_target_name(typ ast.Expr) ?string { + mut candidates := []string{} + base_name := typ.name() + if base_name != '' && !base_name.contains('.') && g.cur_module != '' && g.cur_module != 'main' + && g.cur_module != 'builtin' && !base_name.contains('__') { + candidates << '${g.cur_module}__${base_name}' + } + base_c := g.expr_type_to_c(typ).trim_space() + if base_c != '' { + candidates << base_c + if base_c.ends_with('*') && !param_type_is_pointer_expr(typ) { + candidates << base_c[..base_c.len - 1].trim_space() + } + } + if base_name != '' { + if base_name.contains('.') { + candidates << base_name.replace('.', '__') + } else { + candidates << base_name + } + } + for candidate in candidates { + if g.c_type_is_fn_pointer_alias(candidate) { + return candidate + } + } + return none +} + +fn (mut g Gen) local_non_fn_alias_homonym_cast_target_name(typ ast.Expr, type_name string) ?string { + name := type_name.trim_space() + mut fn_alias_type := '' + if g.c_type_is_fn_pointer_alias(name) { + fn_alias_type = name + } else if name.len > 1 && name.ends_with('*') { + base_type := name[..name.len - 1].trim_space() + if g.c_type_is_fn_pointer_alias(base_type) { + fn_alias_type = base_type + } + } + if fn_alias_type == '' { + return none + } + base_name := typ.name() + if base_name == '' || base_name.contains('.') || base_name.contains('__') { + return none + } + if g.cur_module == '' || g.cur_module == 'main' || g.cur_module == 'builtin' { + return none + } + local_type := '${g.cur_module}__${base_name}' + if !g.is_type_name(local_type) || g.c_type_is_fn_pointer_alias(local_type) { + return none + } + return local_type +} + fn (mut g Gen) fn_type_alias_name_from_generic_name(name string) ?string { mut base_name := name if name.ends_with('_T') { @@ -1108,7 +1165,7 @@ fn (mut g Gen) gen_call_or_cast_expr(node ast.CallOrCastExpr) { return } if g.call_or_cast_lhs_is_type(node.lhs) { - g.gen_type_cast_expr(g.expr_type_to_c(node.lhs), node.expr) + g.gen_type_cast_expr(g.cast_target_type_to_c(node.lhs), node.expr) return } g.call_expr(node.lhs, [node.expr]) @@ -3904,6 +3961,26 @@ fn (mut g Gen) expr_is_explicit_value_of_type(expr ast.Expr, type_name string) b } } +fn (mut g Gen) cast_target_type_to_c(typ ast.Expr) string { + mut type_name := g.expr_type_to_c(typ) + if !param_type_is_pointer_expr(typ) { + if local_type := g.local_non_fn_alias_homonym_cast_target_name(typ, type_name) { + type_name = local_type + } else if alias_name := g.exact_fn_type_alias_cast_target_name(typ) { + type_name = g.local_non_fn_alias_homonym_cast_target_name(typ, alias_name) or { + alias_name + } + } + } + if type_name.ends_with('*') && !param_type_is_pointer_expr(typ) { + base_type := type_name[..type_name.len - 1].trim_space() + if g.c_type_is_fn_pointer_alias(base_type) { + type_name = base_type + } + } + return type_name +} + fn (mut g Gen) gen_type_cast_expr(type_name string, expr ast.Expr) { expr_type := g.get_expr_type(expr) if type_name.starts_with('_option_') && is_none_like_expr(expr) { @@ -6615,6 +6692,9 @@ fn (g &Gen) cast_target_is_aggregate_value(type_name string) bool { || g.is_enum_type(name) { return false } + if g.c_type_is_fn_pointer_alias(name) { + return false + } return name !in ['void', 'void*', 'voidptr', 'char*', 'charptr', 'byteptr'] } @@ -6632,7 +6712,7 @@ fn same_aggregate_cast_type(expr_type string, type_name string) bool { } fn (mut g Gen) gen_cast_expr(node ast.CastExpr) { - mut type_name := g.expr_type_to_c(node.typ) + mut type_name := g.cast_target_type_to_c(node.typ) if resolved_type := g.resolved_sum_data_cast_type(node) { type_name = resolved_type } diff --git a/vlib/v2/gen/cleanc/flag_enum_codegen_test.v b/vlib/v2/gen/cleanc/flag_enum_codegen_test.v index 3e0051bc5..c28f31a50 100644 --- a/vlib/v2/gen/cleanc/flag_enum_codegen_test.v +++ b/vlib/v2/gen/cleanc/flag_enum_codegen_test.v @@ -2259,7 +2259,7 @@ fn main() { ' }, ], ['issue.v']) - assert csrc.contains('array__sort_with_compare(issues, __sort_cmp_Issue_by_created_at_desc)') + assert csrc.contains('array__sort_with_compare(issues, (FnSortCB)__sort_cmp_Issue_by_created_at_desc)') assert csrc.contains('int __sort_cmp_Issue_by_created_at_desc(Issue* a, Issue* b);') assert csrc.contains('int __sort_cmp_Issue_by_created_at_desc(Issue* a, Issue* b) {') } diff --git a/vlib/v2/gen/cleanc/fn.v b/vlib/v2/gen/cleanc/fn.v index 6206a694c..017a1d52c 100644 --- a/vlib/v2/gen/cleanc/fn.v +++ b/vlib/v2/gen/cleanc/fn.v @@ -8814,6 +8814,106 @@ fn (mut g Gen) memdup_addr_arg_type(expr ast.Expr) string { return typ } +fn (mut g Gen) c_type_is_fn_pointer_value(type_name string) bool { + name := type_name.trim_space() + if name == '' || name == 'int' { + return false + } + if g.c_type_is_fn_pointer_alias(name) { + return true + } + if raw_type := g.lookup_type_by_c_name(name) { + if _ := extract_fn_type(raw_type) { + return true + } + } + if raw_type := g.lookup_type_by_c_name_const(name) { + if _ := extract_fn_type(raw_type) { + return true + } + } + return false +} + +fn (mut g Gen) selector_has_fn_pointer_value_type(sel ast.SelectorExpr) bool { + if global_type := g.global_var_type_for_selector(sel) { + if g.c_type_is_fn_pointer_value(global_type) { + return true + } + } + sel_expr := ast.Expr(sel) + if raw_type := g.get_raw_type(sel_expr) { + if _ := extract_fn_type(raw_type) { + return true + } + } + return g.c_type_is_fn_pointer_value(g.get_expr_type(sel_expr)) +} + +fn (mut g Gen) selector_is_known_fn_value(sel ast.SelectorExpr) bool { + lhs := sel.lhs + if lhs !is ast.Ident || sel.rhs.name == '' { + return false + } + mod_name := (lhs as ast.Ident).name + if mod_name == '' { + return false + } + if !g.is_module_ident(mod_name) { + return false + } + c_mod_name := g.resolve_module_name(mod_name).replace('.', '_') + c_name := '${c_mod_name}__${sel.rhs.name}' + if c_name in g.fn_return_types || c_name in g.fn_param_is_ptr { + return true + } + return g.selector_has_fn_pointer_value_type(sel) +} + +fn (mut g Gen) gen_sort_fnsortcb_arg(fn_name string, idx int, base_arg ast.Expr, expected_param_type string) bool { + if idx != 1 || fn_name !in ['array__sort_with_compare', 'array__sorted_with_compare'] { + return false + } + mut expected_sort_param_type := expected_param_type.trim_space() + // Unregistered generated builtin sort helpers still expect FnSortCB; custom + // same-name functions are declared and keep their real parameter type gate. + if expected_sort_param_type == '' && fn_name !in g.fn_param_types { + expected_sort_param_type = 'FnSortCB' + } + if expected_sort_param_type != 'FnSortCB' { + return false + } + comparator := match base_arg { + ast.ParenExpr { + base_arg.expr + } + else { + base_arg + } + } + + match comparator { + ast.Ident { + if comparator.name == '' || comparator.name == 'nil' { + return false + } + } + ast.FnLiteral {} + ast.SelectorExpr { + if !g.selector_is_known_fn_value(comparator) { + return false + } + } + else { + return false + } + } + + g.sb.write_string('(FnSortCB)') + g.expr(comparator) + return true +} + fn (mut g Gen) gen_call_arg(fn_name string, idx int, arg ast.Expr) { base_arg := if arg is ast.ModifierExpr { arg.expr } else { arg } map_runtime_name := canonical_map_runtime_fn_name(fn_name) @@ -8917,6 +9017,9 @@ fn (mut g Gen) gen_call_arg(fn_name string, idx int, arg ast.Expr) { return } } + if g.gen_sort_fnsortcb_arg(fn_name, idx, base_arg, expected_param_type) { + return + } if expected_param_type != '' && g.gen_auto_deref_value_param_arg(expected_param_type, base_arg) { return } @@ -11843,6 +11946,9 @@ fn (mut g Gen) call_expr(lhs ast.Expr, args []ast.Expr) { // Handle C.puts, C.putchar etc. if lhs.lhs is ast.Ident && lhs.lhs.name == 'C' { name = lhs.rhs.name + if name == 'gettid' && args.len == 0 && g.should_emit_linux_gettid_compat() { + name = 'v_cleanc_gettid' + } // With prealloc, free() is redefined as a no-op macro. // C.free calls in prealloc_vcleanup need the real free via _v_cfree. if name == 'free' && g.pref != unsafe { nil } && g.pref.prealloc { diff --git a/vlib/v2/gen/cleanc/stmt.v b/vlib/v2/gen/cleanc/stmt.v index 4d391fc37..8b096f6a5 100644 --- a/vlib/v2/gen/cleanc/stmt.v +++ b/vlib/v2/gen/cleanc/stmt.v @@ -650,7 +650,7 @@ fn (mut g Gen) gen_stmt(node ast.Stmt) { } ast.LabelStmt { g.write_indent() - g.sb.writeln('${node.name}:') + g.sb.writeln('${node.name}:;') if node.stmt !is ast.EmptyStmt { g.gen_stmt(node.stmt) } diff --git a/vlib/v2/gen/cleanc/target_codegen_test.v b/vlib/v2/gen/cleanc/target_codegen_test.v index f73960179..240511f07 100644 --- a/vlib/v2/gen/cleanc/target_codegen_test.v +++ b/vlib/v2/gen/cleanc/target_codegen_test.v @@ -255,6 +255,67 @@ fn generated_c_for_target_program_with_defines(name string, source string, targe user_defines, freestanding, skip_builtin, []) } +fn generated_c_for_trace_calls_import_with_defines(name string, user_defines []string) string { + source := 'module main + +import v.trace_calls + +pub struct C.FILE {} + +pub struct C.timespec { +pub mut: + tv_sec i64 + tv_nsec i64 +} + +__global C.stderr &C.FILE + +pub const C.CLOCK_MONOTONIC int + +fn C.fprintf(fstream &C.FILE, const_format &char, opt ...voidptr) i32 +fn C.fflush(fstream &C.FILE) i32 +fn C.clock_gettime(i32, &C.timespec) i32 +fn C.pthread_self() usize + +fn main() { + trace_calls.on_call("probe") +} +' + trace_calls_file := os.join_path(os.getwd(), 'vlib', 'v', 'trace_calls', 'tracing_calls.c.v') + return generated_c_for_target_program_with_extra_files(name, source, 'linux', user_defines, + false, false, [trace_calls_file]) +} + +fn generated_c_for_target_program_with_extra_files(name string, source string, target_os string, user_defines []string, freestanding bool, skip_builtin bool, extra_files []string) string { + tmp_file := os.join_path(os.temp_dir(), 'v2_cleanc_target_codegen_${name}_${os.getpid()}.v') + os.write_file(tmp_file, source) or { panic(err) } + defer { + os.rm(tmp_file) or {} + } + prefs := &vpref.Preferences{ + backend: .cleanc + target_os: target_os + freestanding: freestanding + skip_builtin: skip_builtin + user_defines: user_defines + explicit_user_defines: user_defines.clone() + no_parallel: true + } + mut paths := [tmp_file] + paths << extra_files + mut file_set := token.FileSet.new() + mut par := parser.Parser.new(prefs) + files := par.parse_files(paths, mut file_set) + env := types.Environment.new() + mut checker := types.Checker.new(prefs, file_set, env) + checker.check_files(files) + mut trans := transformer.Transformer.new_with_pref(env, prefs) + trans.set_file_set(file_set) + transformed_files := trans.transform_files(files) + mut gen := Gen.new_with_env_and_pref(transformed_files, env, prefs) + return gen.gen() +} + fn generated_c_for_target_program_with_hooks(name string, source string, hooks []string) string { return generated_c_for_target_program_with_defines_and_hooks(name, source, 'linux', freestanding_hook_defines(hooks), true, true, hooks) @@ -392,6 +453,19 @@ fn assert_no_os_runtime_headers(csrc string) { } } +fn assert_c_marker_before(csrc string, before string, after string) { + before_idx := csrc.index(before) or { panic('missing marker: ${before}') } + after_idx := csrc.index(after) or { panic('missing marker: ${after}') } + assert before_idx < after_idx +} + +fn assert_no_gettid_compat(csrc string) { + assert !csrc.contains('_GNU_SOURCE') + assert !csrc.contains('SYS_gettid') + assert !csrc.contains('v_cleanc_gettid') + assert !csrc.contains('#define gettid') +} + fn test_eval_comptime_flag_uses_target_os_preference() { windows_gen := new_target_test_gen('windows', []) assert windows_gen.eval_comptime_flag('windows') @@ -908,6 +982,930 @@ fn test_preamble_specializes_apple_includes_by_target() { assert full_cross_src.contains('extern char** environ;\n#endif') } +fn test_linux_preamble_declares_v_owned_gettid_helper_before_c_calls() { + src := full_preamble_for_target('linux', []) + assert src.contains(linux_gettid_feature_define) + assert src.contains('#include ') + assert src.contains(linux_gettid_helper) + assert !src.contains('#define gettid') + assert_c_marker_before(src, '#define _GNU_SOURCE', '#include ') + assert_c_marker_before(src, '#include ', '#include ') + assert_c_marker_before(src, '#include ', + 'static inline uint32_t v_cleanc_gettid(void)') +} + +fn test_linux_minimal_preamble_declares_v_owned_gettid_helper() { + src := preamble_for_target('linux', []) + assert src.contains(linux_gettid_feature_define) + assert src.contains('#include ') + assert src.contains(linux_gettid_helper) + assert !src.contains('#define gettid') + assert_c_marker_before(src, '#define _GNU_SOURCE', '#include ') + assert_c_marker_before(src, '#include ', '#include ') + assert_c_marker_before(src, '#include ', + 'static inline uint32_t v_cleanc_gettid(void)') +} + +fn test_gettid_linux_compat_does_not_leak_to_non_linux_targets() { + for target in ['windows', 'macos', 'cross', 'freebsd', 'android', 'termux'] { + minimal_src := preamble_for_target(target, []) + full_src := full_preamble_for_target(target, []) + assert_no_gettid_compat(minimal_src) + assert_no_gettid_compat(full_src) + } + + freestanding_src := full_preamble_for_options('linux', [], true, false) + assert_no_gettid_compat(freestanding_src) + assert_no_os_runtime_headers(freestanding_src) +} + +fn test_gettid_linux_compat_respects_no_gettid_and_musl_defines() { + for define in ['no_gettid', 'musl'] { + assert_no_gettid_compat(preamble_for_target('linux', [define])) + assert_no_gettid_compat(full_preamble_for_target('linux', [define])) + } +} + +fn test_generated_linux_gettid_call_has_compat_preamble() { + source := 'module main + +fn C.gettid() u32 + +fn thread_id() u32 { + return C.gettid() +} + +fn main() { + _ = thread_id() +} +' + src := generated_c_for_target_program_with_options('linux_gettid_call', source, 'linux', false, + false) + assert src.contains(linux_gettid_feature_define) + assert src.contains(linux_gettid_helper) + assert !src.contains('#define gettid') + assert src.contains('return v_cleanc_gettid();') + assert !src.contains('return gettid();') + assert_c_marker_before(src, '#define _GNU_SOURCE', '#include ') + assert_c_marker_before(src, 'static inline uint32_t v_cleanc_gettid(void)', + 'return v_cleanc_gettid();') +} + +fn test_generated_musl_optional_gettid_branch_is_not_selected() { + source := 'module main + +fn C.gettid() u32 + +fn thread_id() u32 { + $if linux && !musl ? { + return C.gettid() + } $else { + return 0 + } +} + +fn main() { + _ = thread_id() +} +' + src := generated_c_for_target_program_with_defines('linux_musl_gettid_branch', source, 'linux', [ + 'musl', + ], false, false) + assert_no_gettid_compat(src) + assert !src.contains('return gettid();') + assert src.contains('return 0;') +} + +fn test_generated_no_gettid_optional_branch_does_not_emit_compat() { + source := 'module main + +fn C.gettid() u32 + +fn thread_id() u32 { + $if no_gettid ? { + return 0 + } $else $if linux && !musl ? { + return C.gettid() + } $else { + return 1 + } +} + +fn main() { + _ = thread_id() +} +' + src := generated_c_for_target_program_with_defines('linux_no_gettid_branch', source, 'linux', [ + 'no_gettid', + ], false, false) + assert_no_gettid_compat(src) + assert !src.contains('return gettid();') + assert src.contains('return 0;') +} + +fn test_trace_calls_import_respects_no_gettid_and_musl() { + for define in ['no_gettid', 'musl'] { + src := generated_c_for_trace_calls_import_with_defines('trace_calls_${define}_import', [ + define, + ]) + assert_no_gettid_compat(src) + assert !src.contains('gettid(') + assert src.contains('pthread_self()') + } +} + +fn test_label_followed_by_result_or_temp_emits_null_statement() { + src := generated_c_for_target_program('label_before_result_or_temp', ' +fn maybe_label_value() !string { + return "ok" +} + +fn main() { + start_no_time: + value := maybe_label_value() or { return } + _ = value +} +') + assert src.contains('start_no_time:;') + assert src.contains('_result_string _or_t') + assert !src.contains('start_no_time:\n\t_result_string _or_t') +} + +fn test_generated_sort_comparator_casts_named_fn_to_fnsortcb() { + src := generated_c_for_target_program('sort_generated_comparator_fnsortcb_cast', ' +struct RepIndex { + idx int +} + +fn main() { + mut idxs := [RepIndex{idx: 2}, RepIndex{idx: 1}] + idxs.sort(a.idx < b.idx) +} +') + assert src.contains('int __sort_cmp_RepIndex_by_idx_asc(RepIndex* a, RepIndex* b);') + assert src.contains('array__sort_with_compare(idxs, (FnSortCB)__sort_cmp_RepIndex_by_idx_asc);') + assert !src.contains('array__sort_with_compare(idxs, __sort_cmp_RepIndex_by_idx_asc);') +} + +fn test_sort_with_compare_named_comparator_casts_to_fnsortcb() { + src := generated_c_for_target_program('sort_named_comparator_fnsortcb_cast', ' +type FnSortCB = fn (voidptr, voidptr) int + +struct Item { + value int +} + +fn array__sort_with_compare(mut items []Item, callback FnSortCB) + +fn compare_items(a &Item, b &Item) int { + return a.value - b.value +} + +fn main() { + mut items := [Item{value: 2}, Item{value: 1}] + array__sort_with_compare(mut items, compare_items) +} +') + assert src.contains('array__sort_with_compare(&items, (FnSortCB)compare_items);') + assert !src.contains('array__sort_with_compare(&items, compare_items);') +} + +fn test_sort_with_compare_same_name_non_fnsortcb_callback_is_not_cast_to_fnsortcb() { + src := generated_c_for_target_program('sort_same_name_non_fnsortcb_callback_no_cast', ' +struct Item { + value int +} + +type ItemCallback = fn (&Item, &Item) int + +fn array__sort_with_compare(mut items []Item, callback ItemCallback) + +fn compare_items(a &Item, b &Item) int { + return a.value - b.value +} + +fn main() { + mut items := [Item{value: 2}, Item{value: 1}] + array__sort_with_compare(mut items, compare_items) +} +') + assert src.contains('array__sort_with_compare(&items, compare_items);') + assert !src.contains('(FnSortCB)compare_items') +} + +fn test_sort_with_compare_module_qualified_comparator_casts_to_fnsortcb() { + other_file := os.join_path(os.temp_dir(), + 'v2_cleanc_target_codegen_sort_other_${os.getpid()}.v') + os.write_file(other_file, ' +module other + +pub struct Item { +pub: + value int +} + +pub fn compare_items(a &Item, b &Item) int { + return a.value - b.value +} + ') or { + panic(err) + } + defer { + os.rm(other_file) or {} + } + src := generated_c_for_target_program_with_extra_files('sort_module_qualified_comparator_fnsortcb_cast', ' +module main + +import other + +type FnSortCB = fn (voidptr, voidptr) int + +fn array__sort_with_compare(mut items []other.Item, callback FnSortCB) + +fn main() { + mut items := [other.Item{value: 2}, other.Item{value: 1}] + array__sort_with_compare(mut items, other.compare_items) +} + ', + 'linux', [], false, false, [other_file]) + assert src.contains('array__sort_with_compare(&items, (FnSortCB)other__compare_items);') + assert !src.contains('array__sort_with_compare(&items, other__compare_items);') +} + +fn test_sort_with_compare_import_alias_comparator_casts_to_real_module_fnsortcb() { + other_file := os.join_path(os.temp_dir(), + 'v2_cleanc_target_codegen_sort_other_alias_${os.getpid()}.v') + os.write_file(other_file, ' +module other + +pub struct Item { +pub: + value int +} + +pub fn compare_items(a &Item, b &Item) int { + return a.value - b.value +} + ') or { + panic(err) + } + defer { + os.rm(other_file) or {} + } + src := generated_c_for_target_program_with_extra_files('sort_import_alias_comparator_fnsortcb_cast', ' +module main + +import other as oth + +type FnSortCB = fn (voidptr, voidptr) int + +fn array__sort_with_compare(mut items []oth.Item, callback FnSortCB) + +fn main() { + mut items := [oth.Item{value: 2}, oth.Item{value: 1}] + array__sort_with_compare(mut items, oth.compare_items) +} + ', + 'linux', [], false, false, [other_file]) + assert src.contains('array__sort_with_compare(&items, (FnSortCB)other__compare_items);') + assert !src.contains('array__sort_with_compare(&items, other__compare_items);') + assert !src.contains('(FnSortCB)oth__compare_items') +} + +fn test_sort_with_compare_module_global_callback_casts_to_fnsortcb() { + other_file := os.join_path(os.temp_dir(), + 'v2_cleanc_target_codegen_sort_other_global_callback_${os.getpid()}.v') + os.write_file(other_file, ' +module other + +pub struct Item { +pub: + value int +} + +pub type ItemCallback = fn (&Item, &Item) int + +pub __global callback ItemCallback + ') or { + panic(err) + } + defer { + os.rm(other_file) or {} + } + src := generated_c_for_target_program_with_extra_files('sort_module_global_callback_fnsortcb_cast', ' +module main + +import other + +type FnSortCB = fn (voidptr, voidptr) int + +fn array__sort_with_compare(mut items []other.Item, callback FnSortCB) + +fn main() { + mut items := [other.Item{value: 2}, other.Item{value: 1}] + array__sort_with_compare(mut items, other.callback) +} + ', + 'linux', [], false, false, [other_file]) + assert src.contains('array__sort_with_compare(&items, (FnSortCB)other__callback);') + assert !src.contains('array__sort_with_compare(&items, other__callback);') +} + +fn test_sort_with_compare_import_alias_global_callback_casts_to_real_module_fnsortcb() { + other_file := os.join_path(os.temp_dir(), + 'v2_cleanc_target_codegen_sort_other_alias_global_callback_${os.getpid()}.v') + os.write_file(other_file, ' +module other + +pub struct Item { +pub: + value int +} + +pub type ItemCallback = fn (&Item, &Item) int + +pub __global callback ItemCallback + ') or { + panic(err) + } + defer { + os.rm(other_file) or {} + } + src := generated_c_for_target_program_with_extra_files('sort_import_alias_global_callback_fnsortcb_cast', ' +module main + +import other as oth + +type FnSortCB = fn (voidptr, voidptr) int + +fn array__sort_with_compare(mut items []oth.Item, callback FnSortCB) + +fn main() { + mut items := [oth.Item{value: 2}, oth.Item{value: 1}] + array__sort_with_compare(mut items, oth.callback) +} + ', + 'linux', [], false, false, [other_file]) + assert src.contains('array__sort_with_compare(&items, (FnSortCB)other__callback);') + assert !src.contains('array__sort_with_compare(&items, other__callback);') + assert !src.contains('(FnSortCB)oth__callback') +} + +fn test_sort_with_compare_module_const_callback_casts_to_fnsortcb() { + other_file := os.join_path(os.temp_dir(), + 'v2_cleanc_target_codegen_sort_other_const_callback_${os.getpid()}.v') + os.write_file(other_file, ' +module other + +pub struct Item { +pub: + value int +} + +pub type ItemCallback = fn (&Item, &Item) int + +pub fn compare_items(a &Item, b &Item) int { + return a.value - b.value +} + +pub const callback = ItemCallback(compare_items) + ') or { + panic(err) + } + defer { + os.rm(other_file) or {} + } + src := generated_c_for_target_program_with_extra_files('sort_module_const_callback_fnsortcb_cast', ' +module main + +import other + +type FnSortCB = fn (voidptr, voidptr) int + +fn array__sort_with_compare(mut items []other.Item, callback FnSortCB) + +fn main() { + mut items := [other.Item{value: 2}, other.Item{value: 1}] + array__sort_with_compare(mut items, other.callback) +} + ', + 'linux', [], false, false, [other_file]) + assert src.contains('array__sort_with_compare(&items, (FnSortCB)other__callback);') + assert !src.contains('array__sort_with_compare(&items, other__callback);') +} + +fn test_sort_with_compare_module_global_non_fn_value_is_not_cast_to_fnsortcb() { + other_file := os.join_path(os.temp_dir(), + 'v2_cleanc_target_codegen_sort_other_global_non_fn_${os.getpid()}.v') + os.write_file(other_file, ' +module other + +pub struct Item { +pub: + value int +} + +pub __global callback int + ') or { + panic(err) + } + defer { + os.rm(other_file) or {} + } + src := generated_c_for_target_program_with_extra_files('sort_module_global_non_fn_no_fnsortcb_cast', ' +module main + +import other + +type FnSortCB = fn (voidptr, voidptr) int + +fn array__sort_with_compare(mut items []other.Item, callback FnSortCB) + +fn main() { + mut items := [other.Item{value: 2}, other.Item{value: 1}] + array__sort_with_compare(mut items, other.callback) +} + ', + 'linux', [], false, false, [other_file]) + assert src.contains('array__sort_with_compare(&items, other__callback);') + assert !src.contains('(FnSortCB)other__callback') +} + +fn test_sort_with_compare_local_selector_shadowing_module_is_not_cast_to_fnsortcb() { + other_file := os.join_path(os.temp_dir(), + 'v2_cleanc_target_codegen_sort_other_shadowed_module_${os.getpid()}.v') + os.write_file(other_file, ' +module other + +pub struct Item { +pub: + value int +} + +pub type ItemCallback = fn (&Item, &Item) int + +pub __global callback ItemCallback + ') or { + panic(err) + } + defer { + os.rm(other_file) or {} + } + src := generated_c_for_target_program_with_extra_files('sort_local_selector_shadowed_module_no_fnsortcb_cast', ' +module main + +import other + +type FnSortCB = fn (voidptr, voidptr) int + +type ItemCallback = fn (&other.Item, &other.Item) int + +struct Holder { + callback ItemCallback +} + +fn array__sort_with_compare(mut items []other.Item, callback FnSortCB) + +fn compare_items(a &other.Item, b &other.Item) int { + return a.value - b.value +} + +fn main() { + mut items := [other.Item{value: 2}, other.Item{value: 1}] + other := Holder{ + callback: compare_items + } + array__sort_with_compare(mut items, other.callback) +} + ', + 'linux', [], false, false, [other_file]) + assert src.contains('array__sort_with_compare(&items, other.callback);') + assert !src.contains('array__sort_with_compare(&items, (FnSortCB)other.callback);') + assert !src.contains('(FnSortCB)other__callback') +} + +fn test_sort_with_compare_selector_field_callback_is_not_cast_to_fnsortcb() { + src := generated_c_for_target_program('sort_selector_field_callback_no_fnsortcb_cast', ' +type FnSortCB = fn (voidptr, voidptr) int + +struct Item { + value int +} + +type ItemCallback = fn (&Item, &Item) int + +struct Holder { + callback ItemCallback +} + +fn array__sort_with_compare(mut items []Item, callback FnSortCB) + +fn compare_items(a &Item, b &Item) int { + return a.value - b.value +} + +fn main() { + mut items := [Item{value: 2}, Item{value: 1}] + holder := Holder{ + callback: compare_items + } + array__sort_with_compare(mut items, holder.callback) +} +') + assert src.contains('array__sort_with_compare(&items, holder.callback);') + assert !src.contains('array__sort_with_compare(&items, (FnSortCB)holder.callback);') + assert !src.contains('(FnSortCB)holder__callback') +} + +fn test_sorted_with_compare_named_comparator_casts_to_fnsortcb() { + src := generated_c_for_target_program('sorted_named_comparator_fnsortcb_cast', ' +type FnSortCB = fn (voidptr, voidptr) int + +struct Item { + value int +} + +fn array__sorted_with_compare(items []Item, callback FnSortCB) []Item + +fn compare_items(a &Item, b &Item) int { + return a.value - b.value +} + +fn main() { + items := [Item{value: 2}, Item{value: 1}] + sorted := array__sorted_with_compare(items, compare_items) + _ = sorted +} +') + assert src.contains('array__sorted_with_compare(items, (FnSortCB)compare_items);') + assert !src.contains('array__sorted_with_compare(items, compare_items);') +} + +fn test_string_sort_helpers_cast_named_comparators_to_fnsortcb() { + src := generated_c_for_target_program('string_sort_helpers_fnsortcb_cast', ' +type FnSortCB = fn (voidptr, voidptr) int + +fn array__sort_with_compare(mut words []string, callback FnSortCB) + +fn compare_lower_strings(a &string, b &string) int { + return 0 +} + +fn compare_strings_by_len(a &string, b &string) int { + return 0 +} + +fn (mut words []string) sort_ignore_case() { + array__sort_with_compare(mut words, compare_lower_strings) +} + +fn (mut words []string) sort_by_len() { + array__sort_with_compare(mut words, compare_strings_by_len) +} + +fn main() { + mut words := ["Beta", "alpha", "z"] + words.sort_ignore_case() + words.sort_by_len() +} +') + assert src.contains('Array_string__sort_ignore_case(&words);') + assert src.contains('Array_string__sort_by_len(&words);') + assert src.contains('array__sort_with_compare(words, (FnSortCB)compare_lower_strings);') + assert src.contains('array__sort_with_compare(words, (FnSortCB)compare_strings_by_len);') + assert !src.contains('array__sort_with_compare(words, compare_lower_strings);') + assert !src.contains('array__sort_with_compare(words, compare_strings_by_len);') +} + +fn test_sort_with_compare_capturing_fn_literal_casts_statement_expr_to_fnsortcb() { + src := generated_c_for_target_program('sort_capturing_fn_literal_fnsortcb_cast', ' +type FnSortCB = fn (voidptr, voidptr) int + +struct Item { + value int +} + +fn array__sort_with_compare(mut items []Item, callback FnSortCB) + +fn main() { + bias := 1 + mut items := [Item{value: 2}, Item{value: 1}] + array__sort_with_compare(mut items, fn [bias] (a &Item, b &Item) int { + return bias + a.value - b.value + }) +} +') + assert src.contains('array__sort_with_compare(&items, (FnSortCB)({') + assert src.contains('_anon_fn_') + assert src.contains('_capture_0 = bias;') + assert !src.contains('array__sort_with_compare(&items, ({') +} + +fn test_sorted_with_compare_fn_literal_casts_to_fnsortcb() { + src := generated_c_for_target_program('sorted_fn_literal_fnsortcb_cast', ' +type FnSortCB = fn (voidptr, voidptr) int + +struct Item { + value int +} + +fn array__sorted_with_compare(items []Item, callback FnSortCB) []Item + +fn main() { + items := [Item{value: 2}, Item{value: 1}] + sorted := array__sorted_with_compare(items, fn (a &Item, b &Item) int { + return a.value - b.value + }) + _ = sorted +} +') + assert src.contains('array__sorted_with_compare(items, (FnSortCB)_anon_fn_') + assert !src.contains('array__sorted_with_compare(items, _anon_fn_') +} + +fn test_sort_with_compare_nil_callback_is_not_cast_to_fnsortcb() { + src := generated_c_for_target_program('sort_nil_callback_no_fnsortcb_cast', ' +type FnSortCB = fn (voidptr, voidptr) int + +struct Item { + value int +} + +fn array__sort_with_compare(mut items []Item, callback FnSortCB) + +fn main() { + mut items := [Item{value: 2}, Item{value: 1}] + array__sort_with_compare(mut items, nil) +} +') + assert src.contains('array__sort_with_compare(&items, NULL);') + assert !src.contains('(FnSortCB)nil') + assert !src.contains('array__sort_with_compare(&items, (FnSortCB)') +} + +fn test_non_sort_callback_arg_is_not_cast_to_fnsortcb() { + src := generated_c_for_target_program('non_sort_callback_no_fnsortcb_cast', ' +struct Item { + value int +} + +type ItemCallback = fn (&Item, &Item) int + +fn compare_items(a &Item, b &Item) int { + return a.value - b.value +} + +fn use_callback(callback ItemCallback) { + _ = callback +} + +fn main() { + use_callback(compare_items) +} +') + assert src.contains('use_callback(compare_items);') + assert !src.contains('(FnSortCB)compare_items') +} + +fn test_non_sort_fn_literal_callback_arg_is_not_cast_to_fnsortcb() { + src := generated_c_for_target_program('non_sort_fn_literal_callback_no_fnsortcb_cast', ' +struct Item { + value int +} + +type ItemCallback = fn (&Item, &Item) int + +fn use_callback(callback ItemCallback) { + _ = callback +} + +fn main() { + bias := 1 + use_callback(fn [bias] (a &Item, b &Item) int { + return bias + a.value - b.value + }) +} +') + assert src.contains('use_callback(({') + assert src.contains('_anon_fn_') + assert src.contains('_capture_0 = bias;') + assert !src.contains('(FnSortCB)') + assert !src.contains('use_callback((FnSortCB)') +} + +fn test_fn_pointer_alias_cast_from_voidptr_uses_alias_value_cast() { + src := generated_c_for_target_program('fn_pointer_alias_voidptr_cast', ' +module os + +type Signal = int +type SignalHandler = fn (Signal) + +fn handler_from_ptr(prev_handler voidptr) !SignalHandler { + return SignalHandler(prev_handler) +} +') + assert src.contains('_result_os__SignalHandler os__handler_from_ptr(void* prev_handler)') + assert src.contains('os__SignalHandler _val = ((os__SignalHandler)(prev_handler));') + assert !src.contains('os__SignalHandler _val = ((os__SignalHandler*)(prev_handler));') +} + +fn test_option_fn_pointer_alias_cast_from_voidptr_uses_alias_value_cast() { + src := generated_c_for_target_program('option_fn_pointer_alias_voidptr_cast', ' +module os + +type Signal = int +type SignalHandler = fn (Signal) + +fn handler_from_ptr(prev_handler voidptr) ?SignalHandler { + return SignalHandler(prev_handler) +} +') + assert src.contains('_option_os__SignalHandler os__handler_from_ptr(void* prev_handler)') + assert src.contains('os__SignalHandler _val = ((os__SignalHandler)(prev_handler));') + assert !src.contains('os__SignalHandler _val = ((os__SignalHandler*)(prev_handler));') +} + +fn test_explicit_pointer_to_fn_pointer_alias_cast_preserves_star() { + src := generated_c_for_target_program('fn_pointer_alias_explicit_pointer_cast', ' +module os + +type Signal = int +type SignalHandler = fn (Signal) + +fn handler_ptr_from_ptr(prev_handler voidptr) &SignalHandler { + return &SignalHandler(prev_handler) +} +') + assert src.contains('os__SignalHandler* os__handler_ptr_from_ptr(void* prev_handler)') + assert src.contains('return ((os__SignalHandler*)(prev_handler));') + assert !src.contains('return ((os__SignalHandler)(prev_handler));') +} + +fn test_result_pointer_to_fn_pointer_alias_cast_preserves_payload_pointer() { + src := generated_c_for_target_program('result_fn_pointer_alias_explicit_pointer_cast', ' +module os + +type Signal = int +type SignalHandler = fn (Signal) + +fn handler_ptr_from_ptr(prev_handler voidptr) !&SignalHandler { + return &SignalHandler(prev_handler) +} +') + assert src.contains('_result_os__SignalHandlerptr os__handler_ptr_from_ptr(void* prev_handler)') + assert src.contains('os__SignalHandler* _val = ((os__SignalHandler*)(prev_handler));') + assert !src.contains('os__SignalHandler _val = ((os__SignalHandler)(prev_handler));') +} + +fn test_option_pointer_to_fn_pointer_alias_cast_preserves_payload_pointer() { + src := generated_c_for_target_program('option_fn_pointer_alias_explicit_pointer_cast', ' +module os + +type Signal = int +type SignalHandler = fn (Signal) + +fn handler_ptr_from_ptr(prev_handler voidptr) ?&SignalHandler { + return &SignalHandler(prev_handler) +} +') + assert src.contains('_option_os__SignalHandlerptr os__handler_ptr_from_ptr(void* prev_handler)') + assert src.contains('os__SignalHandler* _val = ((os__SignalHandler*)(prev_handler));') + assert !src.contains('os__SignalHandler _val = ((os__SignalHandler)(prev_handler));') +} + +fn test_non_fn_pointer_result_payload_keeps_pointer_cast() { + src := generated_c_for_target_program('non_fn_pointer_result_payload', ' +module os + +struct Payload { + value int +} + +fn payload_from_ptr(raw voidptr) !&Payload { + return &Payload(raw) +} +') + assert src.contains('_result_os__Payloadptr os__payload_from_ptr(void* raw)') + assert src.contains('os__Payload* _val = ((os__Payload*)(raw));') + assert !src.contains('os__Payload _val = ((os__Payload)(raw));') +} + +fn test_fn_pointer_alias_helper_has_no_suffix_fallback() { + g := Gen{ + fn_type_aliases: { + 'os__SignalHandler': true + } + } + assert g.c_type_is_fn_pointer_alias('os__SignalHandler') + assert !g.c_type_is_fn_pointer_alias('other__SignalHandler') + assert !g.c_type_is_fn_pointer_alias('os__SignalHandlerptr') + assert !g.c_type_is_fn_pointer_alias('os__HandlerFn') + assert g.result_value_type('_result_os__SignalHandlerptr') == 'os__SignalHandler*' + assert g.result_value_type('_result_os__Payloadptr') == 'os__Payload*' +} + +fn test_inter_module_non_fn_alias_cast_is_not_remapped_to_fn_alias_homonym() { + foo_file := os.join_path(os.temp_dir(), + 'v2_cleanc_target_codegen_signalhandler_foo_${os.getpid()}.v') + bar_file := os.join_path(os.temp_dir(), + 'v2_cleanc_target_codegen_signalhandler_bar_${os.getpid()}.v') + os.write_file(foo_file, ' +module foo + +pub type SignalHandler = fn (int) + ') or { panic(err) } + os.write_file(bar_file, ' +module bar + +pub type SignalHandler = int + ') or { panic(err) } + defer { + os.rm(foo_file) or {} + os.rm(bar_file) or {} + } + src := generated_c_for_target_program_with_extra_files('inter_module_signalhandler_alias_collision', ' +module main + +import bar +import foo + +fn keep_foo(handler foo.SignalHandler) { + _ = handler +} + +fn cast_bar() bar.SignalHandler { + return bar.SignalHandler(1) +} + ', + 'linux', [], false, false, [foo_file, bar_file]) + assert src.contains('typedef void (*foo__SignalHandler)(int);') + assert src.contains('typedef int bar__SignalHandler;') + assert src.contains('return ((bar__SignalHandler)(1));') + assert !src.contains('return ((foo__SignalHandler)(1));') + assert !src.contains('return ((os__SignalHandler)(1));') +} + +fn test_module_local_non_fn_alias_cast_prefers_local_alias_over_main_fn_alias_homonym() { + bar_file := os.join_path(os.temp_dir(), + 'v2_cleanc_target_codegen_signalhandler_local_bar_${os.getpid()}.v') + os.write_file(bar_file, ' +module bar + +pub type SignalHandler = int + +pub fn cast_local() SignalHandler { + return SignalHandler(1) +} + ') or { + panic(err) + } + defer { + os.rm(bar_file) or {} + } + src := generated_c_for_target_program_with_extra_files('module_local_signalhandler_alias_collision', ' +module main + +type SignalHandler = fn (int) + +fn main() { +} + ', + 'linux', [], false, false, [bar_file]) + assert src.contains('typedef void (*SignalHandler)(int);') + assert src.contains('typedef int bar__SignalHandler;') + assert src.contains('return ((bar__SignalHandler)(1));') + assert !src.contains('return ((SignalHandler)(1));') + assert !src.contains('return ((foo__SignalHandler)(1));') +} + +fn test_optional_sumtype_field_initializer_wraps_cast_value_as_option() { + src := generated_c_for_target_program('optional_sumtype_field_initializer', ' +type Type = Bool | Int + +struct Bool {} +struct Int {} + +struct FnType { + return_type ?Type +} + +fn make_fn_type() FnType { + return FnType{ + return_type: Type(Bool{}) + } +} + +fn main() { + _ = make_fn_type() +} +') + assert src.contains('struct FnType {\n\t_option_Type return_type;\n};') + assert src.contains('.return_type = ({ _option_Type _opt = (_option_Type){ .state = 2 }; Type _val = ((Type){') + assert src.contains('_option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt; })') + assert !src.contains('.return_type = ((Type){') + assert !src.contains('.return_type = ((main__Type){') +} + fn test_freestanding_minimal_preamble_avoids_implicit_os_runtime_headers() { src := preamble_for_freestanding_field('linux') assert src.contains('#include ') diff --git a/vlib/v2/gen/cleanc/types.v b/vlib/v2/gen/cleanc/types.v index 6c6dd8be4..b6a6e7473 100644 --- a/vlib/v2/gen/cleanc/types.v +++ b/vlib/v2/gen/cleanc/types.v @@ -54,6 +54,22 @@ fn (g &Gen) result_value_c_type(result_type string) string { return g.option_result_payload_c_type(g.result_value_type(result_type)) } +fn (g &Gen) c_type_is_fn_pointer_alias(type_name string) bool { + name := type_name.trim_space() + if name == '' { + return false + } + if name in g.fn_type_aliases { + return true + } + if raw_type := g.lookup_type_by_c_name_const(name) { + if raw_type is types.Alias && raw_type.base_type is types.FnType { + return true + } + } + return false +} + fn option_value_type(option_type string) string { if !option_type.starts_with('_option_') { return '' -- 2.39.5