From 1498e02bf64c6e33255fc0d3055b6e72900af9a5 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 27 Jun 2026 18:54:57 +0300 Subject: [PATCH] v3: add c99 mode (#27576) --- vlib/v3/README.md | 5 + vlib/v3/gen/c/cleanc.v | 187 ++++++++++++++---- vlib/v3/gen/c/stmt.v | 134 +++++++++++-- vlib/v3/markused/markused.v | 187 +++++++++++++++--- vlib/v3/pref/pref.v | 1 + vlib/v3/test_all.vsh | 38 +++- .../comptime_top_level_decl_codegen_test.v | 5 +- .../markused_typed_receiver_codegen_test.v | 34 ++++ vlib/v3/tests/option_arg_codegen_test.v | 8 + vlib/v3/tests/prod_flag_test.v | 98 +++++++++ vlib/v3/transform/transform.v | 14 +- vlib/v3/v3.v | 45 +++-- 12 files changed, 651 insertions(+), 105 deletions(-) diff --git a/vlib/v3/README.md b/vlib/v3/README.md index 8eba8c6ad..c7be7589f 100644 --- a/vlib/v3/README.md +++ b/vlib/v3/README.md @@ -218,6 +218,11 @@ The table uses the first v3-generated C stage, `./v3 -parallel -o v4 v3.v`. Bundled TCC currently rejects the atomic helper shims on macOS and the driver falls back to `cc`. +Pass `-c99` to the v3 C backend to compile generated C and support objects as +C99 (`cc -std=c99`) instead of the default GNU11 mode. `test_all.vsh -c99` +validates the C backend and self-host chain in that mode, and skips the ARM64 +native backend step because `-c99` only applies to generated C. + | Phase | Time | Peak RSS | |----------------|----------:|---------:| | parse | 59.47 ms | 73 MB | diff --git a/vlib/v3/gen/c/cleanc.v b/vlib/v3/gen/c/cleanc.v index 7a892b4c1..8949dcf89 100644 --- a/vlib/v3/gen/c/cleanc.v +++ b/vlib/v3/gen/c/cleanc.v @@ -40,6 +40,8 @@ mut: libc_compat_fns map[string]bool tc &types.TypeChecker = unsafe { nil } has_builtins bool + has_stdatomic_header bool + has_stdatomic_compat_header bool tmp_count int line_start bool field_name_set map[string]bool // every struct field's C name (lazy) — for const/field collision checks @@ -53,6 +55,7 @@ mut: struct_decl_short_infos map[string]StructDeclInfo runtime_inits []string compiler_vroot string + c99_mode bool cur_fn_name string cur_param_names []string cur_param_type_values []types.Type @@ -100,6 +103,11 @@ pub fn (g &FlatGen) c_flags() []string { return g.c_flags.clone() } +// set_c99_mode configures whether generated C should support strict C99 builds. +pub fn (mut g FlatGen) set_c99_mode(enabled bool) { + g.c99_mode = enabled +} + // new creates a FlatGen value for c. pub fn FlatGen.new() FlatGen { return FlatGen{ @@ -208,6 +216,8 @@ pub fn (mut g FlatGen) gen_with_used_options(a &flat.FlatAst, used_fns map[strin g.c_directives = []CDirective{} g.c_flags = []string{} g.libc_compat_fns = map[string]bool{} + g.has_stdatomic_header = false + g.has_stdatomic_compat_header = false g.modules = map[string]string{} g.fn_ptr_types = map[string]string{} g.fixed_array_ret_wrappers = map[string]bool{} @@ -499,12 +509,17 @@ fn (mut g FlatGen) collect_c_directive(module_name string, node flat.Node, sourc if include_arg.len == 0 { return true } - // The atomic helper headers are superseded by the inline C11 `__atomic_*` - // helpers emitted in builtin_abi_decls(); also including them would - // redefine `v_prealloc_atomic_*` and the `atomic_*` helpers. - if include_arg.contains('prealloc_atomics.h') || include_arg.contains('stdatomic') { + // These helper headers are superseded by the inline compiler helpers emitted in + // builtin_abi_decls(); also including them would redefine the helpers. + if include_arg.contains('prealloc_atomics.h') || include_arg.contains('filelock_helpers.h') { return true } + if is_stdatomic_header(include_arg) { + g.has_stdatomic_header = true + } + if is_stdatomic_compat_header(include_arg) { + g.has_stdatomic_compat_header = true + } g.add_c_directive(module_name, '#include ${include_arg}', before_import) return true } @@ -517,6 +532,17 @@ fn (mut g FlatGen) collect_c_directive(module_name string, node flat.Node, sourc return false } +fn is_stdatomic_header(include_arg string) bool { + normalized := include_arg.replace('\\', '/') + return normalized == '' || normalized == '"stdatomic.h"' + || normalized.ends_with('/stdatomic.h"') || is_stdatomic_compat_header(normalized) +} + +fn is_stdatomic_compat_header(include_arg string) bool { + normalized := include_arg.replace('\\', '/') + return normalized.contains('/thirdparty/stdatomic/') && normalized.ends_with('/atomic.h"') +} + fn (mut g FlatGen) add_c_directive(module_name string, text string, before_import bool) { if text.len == 0 { return @@ -2815,6 +2841,7 @@ fn (g &FlatGen) is_module_qualified_enum(base flat.Node) bool { } fn (mut g FlatGen) preamble() { + g.c99_feature_test_macros() g.writeln('#include ') g.writeln('#include ') g.writeln('#include ') @@ -2823,6 +2850,7 @@ fn (mut g FlatGen) preamble() { g.writeln('#include ') // guarantees UINTPTR_MAX for the pointer-width atomic helpers g.writeln('#include ') g.writeln('#include ') + g.c99_atomic_compat_decls() if g.has_builtins { g.writeln('#include ') g.writeln('#include ') @@ -2836,7 +2864,6 @@ fn (mut g FlatGen) preamble() { g.writeln('#include ') g.writeln('#include ') g.writeln('#include ') - g.writeln('#include ') g.writeln('#include ') g.writeln('#include ') g.writeln('#include ') @@ -2904,6 +2931,36 @@ fn (mut g FlatGen) preamble() { g.writeln('') } +fn (mut g FlatGen) c99_feature_test_macros() { + if !g.c99_mode { + return + } + g.writeln('#if defined(__linux__) && !defined(_GNU_SOURCE)') + g.writeln('#define _GNU_SOURCE') + g.writeln('#endif') + g.writeln('#if defined(__linux__) && !defined(_POSIX_C_SOURCE)') + g.writeln('#define _POSIX_C_SOURCE 200809L') + g.writeln('#endif') +} + +fn (mut g FlatGen) c99_atomic_compat_decls() { + if g.has_stdatomic_header { + return + } + g.writeln('typedef volatile uintptr_t atomic_uintptr_t;') + g.writeln('#ifndef memory_order_relaxed') + g.writeln('#define memory_order_relaxed 0') + g.writeln('#define memory_order_consume 1') + g.writeln('#define memory_order_acquire 2') + g.writeln('#define memory_order_release 3') + g.writeln('#define memory_order_acq_rel 4') + g.writeln('#define memory_order_seq_cst 5') + g.writeln('#endif') + g.writeln('#ifndef atomic_thread_fence') + g.writeln('#define atomic_thread_fence(order) __sync_synchronize()') + g.writeln('#endif') +} + fn (mut g FlatGen) write_arch_macros() { g.writeln('#ifndef __V_architecture') g.writeln('#define __V_architecture 0') @@ -2954,37 +3011,26 @@ fn (mut g FlatGen) libc_compat_decls() { } } -fn (mut g FlatGen) builtin_abi_decls() { - if !g.has_builtins { +fn (mut g FlatGen) prealloc_atomic_compat_decls() { + g.writeln('static inline int v_prealloc_atomic_add_i32(int *ptr, int delta) { return __atomic_add_fetch(ptr, delta, 5); }') + g.writeln('static inline int v_prealloc_atomic_load_i32(int *ptr) { return __atomic_add_fetch(ptr, 0, 5); }') + g.writeln('#ifdef __TINYC__') + g.writeln('static inline int v_prealloc_atomic_store_i32(int *ptr, int val) { return (int)__atomic_exchange_4((u32*)ptr, (u32)val, 5); }') + g.writeln('static inline int v_prealloc_atomic_cas_i32(int *ptr, int expected, int desired) { u32 e = (u32)expected; return __atomic_compare_exchange_4((u32*)ptr, &e, (u32)desired, 5, 5); }') + g.writeln('#else') + g.writeln('static inline int v_prealloc_atomic_store_i32(int *ptr, int val) { return __atomic_exchange_n(ptr, val, 5); }') + g.writeln('static inline int v_prealloc_atomic_cas_i32(int *ptr, int expected, int desired) { return __atomic_compare_exchange_n(ptr, &expected, desired, 0, 5, 5); }') + g.writeln('#endif') +} + +fn (mut g FlatGen) atomic_builtin_compat_decls() { + if g.has_stdatomic_compat_header { return } - g.libc_compat_decls() - g.writeln('#define array_new(elem_size, len, cap) __new_array((len), (cap), (elem_size))') - g.writeln('#define array_push array__push') - g.writeln('void array__push_many(array* a, void* val, int size);') - g.writeln('#define array_push_many_ptr(a, val, size) array__push_many((a), (void*)(val), (size))') - g.writeln('#define array_get array__get') - g.writeln('#define array_set(a, i, ...) array__set(&(a), (i), __VA_ARGS__)') - g.writeln('array array__clone(array* a);') - g.writeln('#define array_slice array__slice') - g.writeln('#define array_delete array__delete') - g.writeln('#define array_ensure_cap array__ensure_cap') - g.writeln('#define map__get_or_set map__get_and_set') - g.writeln('#ifndef V_COMMIT_HASH') - g.writeln('#define V_COMMIT_HASH ""') - g.writeln('#endif') - // Weak fallbacks for the heap-tracking hooks. A program that provides real - // implementations (e.g. a `vheap_alloc`/`vheap_free` from a linked C file, as - // some projects do) overrides these without a redefinition/static-vs-non-static - // clash against that file's own non-static prototype. - g.writeln('__attribute__((weak)) void vheap_alloc(void* p, u64 n) { (void)p; (void)n; }') - g.writeln('__attribute__((weak)) void vheap_free(void* p) { (void)p; }') - // Atomic helpers. We use the C11 __atomic_* builtins (memory order 5 == __ATOMIC_SEQ_CST). + // Atomic helpers. We use compiler __atomic_* builtins (memory order 5 == __ATOMIC_SEQ_CST). // clang/gcc inline the generic _n / RMW builtins. tcc only implements the inline // __atomic_{add,sub,fetch}_* RMW builtins; for load/store/exchange/cas it has no generic // _n form, so we route those to the sized __atomic_*_N libcalls (resolved from libc). - g.writeln('static inline int v_prealloc_atomic_add_i32(int *ptr, int delta) { return __atomic_add_fetch(ptr, delta, 5); }') - g.writeln('static inline int v_prealloc_atomic_load_i32(int *ptr) { return __atomic_add_fetch(ptr, 0, 5); }') g.writeln('static inline u32 atomic_fetch_add_u32(void* ptr, u32 delta) { return __atomic_fetch_add((u32*)ptr, delta, 5); }') g.writeln('static inline u64 atomic_fetch_add_u64(void* ptr, u64 delta) { return __atomic_fetch_add((u64*)ptr, delta, 5); }') g.writeln('static inline u64 atomic_fetch_sub_u64(void* ptr, u64 delta) { return __atomic_fetch_sub((u64*)ptr, delta, 5); }') @@ -2994,8 +3040,6 @@ fn (mut g FlatGen) builtin_abi_decls() { g.writeln('static inline u64 atomic_load_u64(void* ptr) { return __atomic_fetch_add((u64*)ptr, 0, 5); }') g.writeln('static inline void* atomic_load_ptr(void* ptr) { return *(void* volatile*)ptr; }') g.writeln('#ifdef __TINYC__') - g.writeln('static inline int v_prealloc_atomic_store_i32(int *ptr, int val) { return (int)__atomic_exchange_4((u32*)ptr, (u32)val, 5); }') - g.writeln('static inline int v_prealloc_atomic_cas_i32(int *ptr, int expected, int desired) { u32 e = (u32)expected; return __atomic_compare_exchange_4((u32*)ptr, &e, (u32)desired, 5, 5); }') g.writeln('static inline void atomic_store_byte(void* ptr, byte val) { __atomic_store_1((byte*)ptr, val, 5); }') g.writeln('static inline void atomic_store_u16(void* ptr, u16 val) { __atomic_store_2((u16*)ptr, val, 5); }') g.writeln('static inline void atomic_store_u32(void* ptr, u32 val) { __atomic_store_4((u32*)ptr, val, 5); }') @@ -3017,8 +3061,6 @@ fn (mut g FlatGen) builtin_abi_decls() { g.writeln('static inline bool atomic_compare_exchange_weak_u32(void* ptr, u32* expected, u32 desired) { return __atomic_compare_exchange_4((u32*)ptr, expected, desired, 5, 5); }') g.writeln('static inline bool atomic_compare_exchange_weak_u64(void* ptr, u64* expected, u64 desired) { return __atomic_compare_exchange_8((u64*)ptr, expected, desired, 5, 5); }') g.writeln('#else') - g.writeln('static inline int v_prealloc_atomic_store_i32(int *ptr, int val) { return __atomic_exchange_n(ptr, val, 5); }') - g.writeln('static inline int v_prealloc_atomic_cas_i32(int *ptr, int expected, int desired) { return __atomic_compare_exchange_n(ptr, &expected, desired, 0, 5, 5); }') g.writeln('static inline void atomic_store_byte(void* ptr, byte val) { __atomic_store_n((byte*)ptr, val, 5); }') g.writeln('static inline void atomic_store_u16(void* ptr, u16 val) { __atomic_store_n((u16*)ptr, val, 5); }') g.writeln('static inline void atomic_store_u32(void* ptr, u32 val) { __atomic_store_n((u32*)ptr, val, 5); }') @@ -3034,6 +3076,36 @@ fn (mut g FlatGen) builtin_abi_decls() { g.writeln('#endif') g.writeln('static inline bool atomic_compare_exchange_weak_ptr(void* ptr, void* expected, ptrdiff_t desired) { return atomic_compare_exchange_strong_ptr(ptr, expected, desired); }') g.writeln('static inline void cpu_relax(void) { __asm__ __volatile__("" ::: "memory"); }') +} + +fn (mut g FlatGen) builtin_abi_decls() { + if !g.has_builtins { + return + } + g.libc_compat_decls() + g.writeln('#define array_new(elem_size, len, cap) __new_array((len), (cap), (elem_size))') + g.writeln('#define array_push array__push') + g.writeln('void array__push_many(array* a, void* val, int size);') + g.writeln('#define array_push_many_ptr(a, val, size) array__push_many((a), (void*)(val), (size))') + g.writeln('#define array_get array__get') + g.writeln('#define array_set(a, i, ...) array__set(&(a), (i), __VA_ARGS__)') + g.writeln('array array__clone(array* a);') + g.writeln('#define array_slice array__slice') + g.writeln('#define array_delete array__delete') + g.writeln('#define array_ensure_cap array__ensure_cap') + g.writeln('#define map__get_or_set map__get_and_set') + g.writeln('#ifndef V_COMMIT_HASH') + g.writeln('#define V_COMMIT_HASH ""') + g.writeln('#endif') + // Weak fallbacks for the heap-tracking hooks. A program that provides real + // implementations (e.g. a `vheap_alloc`/`vheap_free` from a linked C file, as + // some projects do) overrides these without a redefinition/static-vs-non-static + // clash against that file's own non-static prototype. + g.writeln('__attribute__((weak)) void vheap_alloc(void* p, u64 n) { (void)p; (void)n; }') + g.writeln('__attribute__((weak)) void vheap_free(void* p) { (void)p; }') + g.filelock_compat_decls() + g.prealloc_atomic_compat_decls() + g.atomic_builtin_compat_decls() g.writeln('static inline double math__abs(double a) { return a < 0 ? -a : a; }') g.writeln('static inline double math__min(double a, double b) { return a < b ? a : b; }') g.writeln('static const u64 _wyp[4] = {0x2d358dccaa6c78a5ull, 0x8bb84b93962eacc9ull, 0x4b33a62ed433d4a3ull, 0x4d5a2da51de1aa47ull};') @@ -3064,6 +3136,51 @@ fn (mut g FlatGen) builtin_abi_decls() { g.writeln('') } +fn (mut g FlatGen) filelock_compat_decls() { + g.writeln('#ifdef _WIN32') + g.writeln('#include ') + g.writeln('int v_filelock_lock(void* handle, int exclusive, int immediate, u64 start, u64 len) {') + g.writeln('\tOVERLAPPED overlap;') + g.writeln('\tmemset(&overlap, 0, sizeof(overlap));') + g.writeln('\toverlap.Offset = (DWORD)(start & 0xffffffffULL);') + g.writeln('\toverlap.OffsetHigh = (DWORD)(start >> 32);') + g.writeln('\tDWORD flags = immediate ? LOCKFILE_FAIL_IMMEDIATELY : 0;') + g.writeln('\tif (exclusive) { flags |= LOCKFILE_EXCLUSIVE_LOCK; }') + g.writeln('\tDWORD low = len == 0 ? MAXDWORD : (DWORD)(len & 0xffffffffULL);') + g.writeln('\tDWORD high = len == 0 ? MAXDWORD : (DWORD)(len >> 32);') + g.writeln('\treturn LockFileEx((HANDLE)handle, flags, 0, low, high, &overlap) ? 0 : -1;') + g.writeln('}') + g.writeln('int v_filelock_unlock(void* handle, u64 start, u64 len) {') + g.writeln('\tOVERLAPPED overlap;') + g.writeln('\tmemset(&overlap, 0, sizeof(overlap));') + g.writeln('\toverlap.Offset = (DWORD)(start & 0xffffffffULL);') + g.writeln('\toverlap.OffsetHigh = (DWORD)(start >> 32);') + g.writeln('\tDWORD low = len == 0 ? MAXDWORD : (DWORD)(len & 0xffffffffULL);') + g.writeln('\tDWORD high = len == 0 ? MAXDWORD : (DWORD)(len >> 32);') + g.writeln('\treturn UnlockFileEx((HANDLE)handle, 0, low, high, &overlap) ? 0 : -1;') + g.writeln('}') + g.writeln('#else') + g.writeln('int v_filelock_lock(i32 fd, i32 exclusive, i32 immediate, u64 start, u64 len) {') + g.writeln('\tstruct flock fl;') + g.writeln('\tmemset(&fl, 0, sizeof(fl));') + g.writeln('\tfl.l_type = exclusive ? F_WRLCK : F_RDLCK;') + g.writeln('\tfl.l_whence = SEEK_SET;') + g.writeln('\tfl.l_start = (off_t)start;') + g.writeln('\tfl.l_len = len == 0 ? 0 : (off_t)len;') + g.writeln('\treturn fcntl(fd, immediate ? F_SETLK : F_SETLKW, &fl);') + g.writeln('}') + g.writeln('int v_filelock_unlock(i32 fd, u64 start, u64 len) {') + g.writeln('\tstruct flock fl;') + g.writeln('\tmemset(&fl, 0, sizeof(fl));') + g.writeln('\tfl.l_type = F_UNLCK;') + g.writeln('\tfl.l_whence = SEEK_SET;') + g.writeln('\tfl.l_start = (off_t)start;') + g.writeln('\tfl.l_len = len == 0 ? 0 : (off_t)len;') + g.writeln('\treturn fcntl(fd, F_SETLK, &fl);') + g.writeln('}') + g.writeln('#endif') +} + fn (mut g FlatGen) collect_fixed_array_typedefs_needed() map[string]FixedArrayTypedefInfo { mut needed := map[string]FixedArrayTypedefInfo{} old_module := g.tc.cur_module diff --git a/vlib/v3/gen/c/stmt.v b/vlib/v3/gen/c/stmt.v index 8e2d609ed..6cde1973d 100644 --- a/vlib/v3/gen/c/stmt.v +++ b/vlib/v3/gen/c/stmt.v @@ -289,6 +289,7 @@ fn (mut g FlatGen) gen_node(id flat.NodeId) { && !g.type_can_wrap_as_sum(expr_value_type, base) && !g.types_numeric_compatible(expr_value_type, base) && !g.call_constructs_type(ret_id, base) + && !g.clone_call_matches_base(ret_node, base) && expr_value_type !is types.Primitive && expr_value_type !is types.Unknown { g.writeln('return (${ct}){.ok = false};') @@ -586,8 +587,8 @@ fn (mut g FlatGen) return_expr_string(node flat.Node, ret_id flat.NodeId, ret_no && !g.expr_is_nil_pointer_payload(ret_id, base) && !g.type_can_wrap_as_sum(expr_value_type, base) && !g.types_numeric_compatible(expr_value_type, base) - && !g.call_constructs_type(ret_id, base) && expr_value_type !is types.Primitive - && expr_value_type !is types.Unknown { + && !g.call_constructs_type(ret_id, base) && !g.clone_call_matches_base(ret_node, base) + && expr_value_type !is types.Primitive && expr_value_type !is types.Unknown { return '(${ct}){.ok = false}' } return '(${ct}){.ok = true, .value = ${g.expr_to_string_with_expected_type(ret_id, base)}}' @@ -737,8 +738,16 @@ fn (g &FlatGen) selector_call_return_type(fn_node flat.Node) ?types.Type { } } } - base_type := g.tc.resolve_type(base_id) + base_type0 := g.usable_expr_type(base_id) + base_type := if base_type0 is types.Unknown || base_type0 is types.Void { + g.tc.resolve_type(base_id) + } else { + base_type0 + } clean_type := types.unwrap_pointer(base_type) + if fn_node.value == 'clone' && (clean_type is types.Array || clean_type is types.Map) { + return base_type + } mut receiver_name := clean_type.name() if clean_type is types.Struct { receiver_name = clean_type.name @@ -848,6 +857,67 @@ fn (g &FlatGen) optional_result_matches_base(expr_type types.Type, base types.Ty return g.option_c_name_for_base(inner) == g.option_c_name_for_base(base) } +fn (g &FlatGen) clone_call_matches_base(call_node flat.Node, base types.Type) bool { + mut node := call_node + for node.kind in [.expr_stmt, .paren] && node.children_count > 0 { + node = *g.a.child_node(&node, 0) + } + if node.kind != .call || node.children_count == 0 { + return false + } + fn_node := g.a.child_node(&node, 0) + if fn_node.kind == .ident { + base_ct := g.tc.c_type(types.unwrap_pointer(base)) + return (fn_node.value == 'array__clone' && base_ct == 'Array') + || (fn_node.value == 'map__clone' && base_ct == 'map') + } + if fn_node.kind != .selector || fn_node.value != 'clone' || fn_node.children_count == 0 { + return false + } + base_id := g.a.child(fn_node, 0) + receiver_type0 := g.usable_expr_type(base_id) + receiver_type := if receiver_type0 is types.Unknown || receiver_type0 is types.Void { + g.tc.resolve_type(base_id) + } else { + receiver_type0 + } + clean_receiver := types.unwrap_pointer(receiver_type) + clean_base := types.unwrap_pointer(base) + if g.type_names_match(clean_receiver, clean_base) { + return true + } + receiver_ct0 := g.tc.c_type(clean_receiver) + base_ct0 := g.tc.c_type(clean_base) + if receiver_ct0.len > 0 && base_ct0.len > 0 && receiver_ct0 == base_ct0 { + return true + } + receiver := if clean_receiver is types.Alias { + clean_receiver.base_type + } else { + clean_receiver + } + expected := if clean_base is types.Alias { + clean_base.base_type + } else { + clean_base + } + if expected is types.Array || expected is types.Map { + receiver_ct := g.tc.c_type(receiver) + expected_ct := g.tc.c_type(expected) + if receiver_ct.len > 0 && receiver_ct == expected_ct { + return true + } + } + if receiver is types.Array && expected is types.Array { + return g.type_names_match(receiver.elem_type, expected.elem_type) + } + if receiver is types.Map && expected is types.Map { + return g.type_names_match(receiver.key_type, expected.key_type) + && g.type_names_match(receiver.value_type, expected.value_type) + } + return false +} + // option_c_name_for_base returns the C optional type name used for a `?base`/`!base` // value, mirroring optional_type_name without its side effects. fn (g &FlatGen) option_c_name_for_base(base types.Type) string { @@ -911,6 +981,9 @@ fn (g &FlatGen) expr_is_nil_value(id flat.NodeId) bool { fn (g &FlatGen) usable_expr_type(id flat.NodeId) types.Type { if int(id) >= 0 && int(id) < g.a.nodes.len { node := g.a.nodes[int(id)] + if node.kind in [.expr_stmt, .paren] && node.children_count > 0 { + return g.usable_expr_type(g.a.child(&node, 0)) + } if node.kind == .ident { if typ := g.cur_param_types[node.value] { return typ @@ -952,6 +1025,22 @@ fn (g &FlatGen) usable_expr_type(id flat.NodeId) types.Type { return types.Type(types.u8_) } } + if node.kind == .call && node.children_count > 0 { + fn_node := g.a.child_node(&node, 0) + if fn_node.kind == .ident { + if typ := g.tc.cur_scope.lookup(fn_node.value) { + ret := fn_type_return_type(typ) + if ret !is types.Unknown && ret !is types.Void { + return ret + } + } + if ret := g.fn_decl_return_type_for_call_name(fn_node.value) { + if ret !is types.Unknown && ret !is types.Void { + return ret + } + } + } + } } if typ := g.tc.expr_type(id) { if typ !is types.Unknown && typ !is types.Void { @@ -1041,11 +1130,22 @@ fn (g &FlatGen) is_runtime_array_flags_stmt(id flat.NodeId) bool { return owner_type is types.Array || owner_type.name() == 'strings.Builder' } +fn (g &FlatGen) multi_return_expr_type(id flat.NodeId) ?types.MultiReturn { + rtype := g.tc.resolve_type(id) + if rtype is types.MultiReturn { + return rtype + } + utype := g.usable_expr_type(id) + if utype is types.MultiReturn { + return utype + } + return none +} + // gen_decl_assign emits decl assign output for c. fn (mut g FlatGen) gen_decl_assign(node flat.Node) { if node.children_count >= 3 { - rhs_type := g.tc.resolve_type(g.a.child(&node, 1)) - if rhs_type is types.MultiReturn { + if _ := g.multi_return_expr_type(g.a.child(&node, 1)) { g.gen_multi_return_decl(node) return } @@ -1325,17 +1425,15 @@ fn (mut g FlatGen) gen_fixed_array_copy_from_node(dst string, rhs_id flat.NodeId fn (mut g FlatGen) gen_multi_return_decl(node flat.Node) { rhs_id := g.a.child(&node, 1) - rhs_type := g.tc.resolve_type(rhs_id) + rhs_multi := g.multi_return_expr_type(rhs_id) or { return } + rhs_type := types.Type(rhs_multi) ct := g.tc.c_type(rhs_type) tmp := g.tmp_name() g.write('${ct} ${tmp} = ') g.gen_expr_with_expected_type(rhs_id, rhs_type) g.writeln(';') num_lhs := node.children_count - 1 - mut multi_types := []types.Type{} - if rhs_type is types.MultiReturn { - multi_types = rhs_type.types.clone() - } + multi_types := rhs_multi.types.clone() for j in 0 .. num_lhs { lhs_idx := if j == 0 { 0 } else { j + 1 } lhs_id := g.a.child(&node, lhs_idx) @@ -1376,8 +1474,7 @@ fn (mut g FlatGen) gen_multi_return_decl(node flat.Node) { fn (mut g FlatGen) gen_assign(node flat.Node) { if node.children_count >= 3 { rhs_id := g.a.child(&node, 1) - rhs_type := g.tc.resolve_type(rhs_id) - if rhs_type is types.MultiReturn { + if _ := g.multi_return_expr_type(rhs_id) { g.gen_multi_return_assign(node) return } @@ -1427,6 +1524,11 @@ fn (mut g FlatGen) gen_assign(node flat.Node) { g.gen_expr(g.a.child(&node, i + 1)) g.writeln(';') } + } else { + g.gen_expr(g.a.child(&node, i)) + g.write(' <<= ') + g.gen_expr(g.a.child(&node, i + 1)) + g.writeln(';') } } else { rhs_id := g.a.child(&node, i + 1) @@ -1601,17 +1703,15 @@ fn (g &FlatGen) assign_lhs_needs_deref(lhs_id flat.NodeId, lhs_type types.Type, // gen_multi_return_assign emits multi return assign output for c. fn (mut g FlatGen) gen_multi_return_assign(node flat.Node) { rhs_id := g.a.child(&node, 1) - rhs_type := g.tc.resolve_type(rhs_id) + rhs_multi := g.multi_return_expr_type(rhs_id) or { return } + rhs_type := types.Type(rhs_multi) ct := g.tc.c_type(rhs_type) tmp := g.tmp_name() g.write('${ct} ${tmp} = ') g.gen_expr_with_expected_type(rhs_id, rhs_type) g.writeln(';') num_lhs := node.children_count - 1 - mut multi_types := []types.Type{} - if rhs_type is types.MultiReturn { - multi_types = rhs_type.types.clone() - } + multi_types := rhs_multi.types.clone() for j in 0 .. num_lhs { lhs_idx := if j == 0 { 0 } else { j + 1 } lhs_id := g.a.child(&node, lhs_idx) diff --git a/vlib/v3/markused/markused.v b/vlib/v3/markused/markused.v index 295b5c930..7e1534d90 100644 --- a/vlib/v3/markused/markused.v +++ b/vlib/v3/markused/markused.v @@ -817,11 +817,19 @@ fn enqueue_detected_runtime_helpers(a &flat.FlatAst, tc &types.TypeChecker, mut mut needs_string_membership_helpers := false mut needs_new_map := false mut cur_module := '' + mut imports := map[string]string{} for node in a.nodes { match node.kind { + .file { + cur_module = '' + imports = map[string]string{} + } .module_decl { cur_module = node.value } + .import_decl { + imports[node.typ] = node.value + } .fn_decl { ret_type := tc.parse_type(node.typ) if ret_type is types.OptionType || ret_type is types.ResultType { @@ -860,6 +868,10 @@ fn enqueue_detected_runtime_helpers(a &flat.FlatAst, tc &types.TypeChecker, mut && fn_node.value in ['trim_space', 'trim_space_left', 'trim_space_right', 'to_upper', 'to_upper_ascii', 'to_lower', 'to_lower_ascii'] { enqueue('string.${fn_node.value}', mut used, mut queue) } + if markused_call_lowers_to_join_path_single(a, fn_node, imports) { + enqueue('join_path_single', mut used, mut queue) + enqueue('os.join_path_single', mut used, mut queue) + } if fn_node.kind == .ident && fn_node.value in ['print', 'println', 'eprint', 'eprintln'] && node.children_count >= 2 { @@ -939,6 +951,24 @@ fn enqueue_detected_runtime_helpers(a &flat.FlatAst, tc &types.TypeChecker, mut } } +fn markused_call_lowers_to_join_path_single(a &flat.FlatAst, fn_node flat.Node, imports map[string]string) bool { + if fn_node.kind == .ident { + return fn_node.value == 'join_path' + } + if fn_node.kind != .selector || fn_node.value != 'join_path' || fn_node.children_count == 0 { + return false + } + base_id := a.child(&fn_node, 0) + if int(base_id) < 0 { + return false + } + base := a.node(base_id) + if base.kind != .ident { + return false + } + return base.value == 'os' || imports[base.value] == 'os' +} + // enqueue_stringified_custom_str_method supports enqueue_stringified_custom_str_method handling. fn enqueue_stringified_custom_str_method(expr_id flat.NodeId, cur_module string, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) { mut typ := tc.expr_type(expr_id) or { tc.resolve_type(expr_id) } @@ -1310,6 +1340,7 @@ fn receiver_info(a &flat.FlatAst, node &flat.Node) (string, string) { // collect_calls updates collect calls state for markused. fn (c &CallCollector) collect_calls(node &flat.Node, cur_module string, imports map[string]string, receiver_name string, receiver_struct string, mut calls []string) { local_values := c.local_value_names(node) + local_types := c.local_value_type_names(node, cur_module, imports) visible_local_idents := markused_visible_local_idents(c.a, node, local_values) mut stack := []flat.NodeId{cap: int(node.children_count)} for i in 0 .. node.children_count { @@ -1362,7 +1393,8 @@ fn (c &CallCollector) collect_calls(node &flat.Node, cur_module string, imports if int(base_id) >= 0 { base := c.a.nodes[int(base_id)] has_exact_selector_call = c.collect_typed_receiver_method(base_id, - callee.value, cur_module, imports, local_values, mut calls) + callee.value, cur_module, imports, local_values, + local_types, mut calls) if !has_exact_selector_call && base.kind == .ident && base.value in imports && base.value !in local_values { calls << imports[base.value] + '.' + callee.value @@ -1522,6 +1554,51 @@ fn (c &CallCollector) local_value_names(node &flat.Node) map[string]bool { return markused_local_value_names(c.a, node) } +fn (c &CallCollector) local_value_type_names(node &flat.Node, cur_module string, imports map[string]string) map[string]string { + mut result := map[string]string{} + mut stack := []flat.NodeId{cap: int(node.children_count)} + for i in 0 .. node.children_count { + child_id := c.a.child(node, i) + if int(child_id) >= 0 { + stack << child_id + } + } + for stack.len > 0 { + id := stack.pop() + child := c.a.node(id) + if child.kind == .param && child.value.len > 0 && child.typ.len > 0 { + result[child.value] = markused_resolve_imported_type_name(child.typ, imports) + } else if child.kind == .decl_assign { + mut i := 0 + for i + 1 < child.children_count { + lhs_id := c.a.child(child, i) + rhs_id := c.a.child(child, i + 1) + if int(lhs_id) >= 0 && int(rhs_id) >= 0 { + lhs := c.a.node(lhs_id) + if lhs.kind == .ident && lhs.value.len > 0 { + type_name := if child.children_count == 2 && child.typ.len > 0 { + child.typ + } else { + c.top_level_decl_rhs_type_name(rhs_id, cur_module, imports) + } + if type_name.len > 0 { + result[lhs.value] = type_name + } + } + } + i += 2 + } + } + for i in 0 .. child.children_count { + next_id := c.a.child(child, i) + if int(next_id) >= 0 { + stack << next_id + } + } + } + return result +} + fn markused_local_value_names(a &flat.FlatAst, node &flat.Node) map[string]bool { mut names := map[string]bool{} mut stack := []flat.NodeId{cap: int(node.children_count)} @@ -2011,33 +2088,25 @@ fn (c &CallCollector) collect_top_level_typed_receiver_method(base_id flat.NodeI fn (c &CallCollector) top_level_receiver_type_name(base_id flat.NodeId, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string) string { base := c.a.node(base_id) + type_name := resolve_type_name(c.node_type(base_id)) + if type_name.len > 0 { + struct_type := c.struct_lookup_name(type_name, cur_module) + if struct_type.len > 0 { + return struct_type + } + return type_name + } if base.kind == .ident && base.value.len > 0 { if local_type := local_types[base.value] { return local_type } if base.value in local_values { - type_name := resolve_type_name(c.node_type(base_id)) - if type_name.len > 0 { - struct_type := c.struct_lookup_name(type_name, cur_module) - if struct_type.len > 0 { - return struct_type - } - return type_name - } return '' } if base.value in imports { return '' } } - type_name := resolve_type_name(c.node_type(base_id)) - if type_name.len > 0 { - struct_type := c.struct_lookup_name(type_name, cur_module) - if struct_type.len > 0 { - return struct_type - } - return type_name - } if base.kind == .ident && base.value.len > 0 { return c.value_type_name(base.value, cur_module, imports) } @@ -2361,8 +2430,8 @@ fn (c &CallCollector) node_type(id flat.NodeId) types.Type { return c.tc.resolve_type(id) } -fn (c &CallCollector) collect_typed_receiver_method(base_id flat.NodeId, method string, cur_module string, imports map[string]string, local_values map[string]bool, mut calls []string) bool { - type_name := c.receiver_type_name(base_id, cur_module, imports, local_values) +fn (c &CallCollector) collect_typed_receiver_method(base_id flat.NodeId, method string, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string, mut calls []string) bool { + type_name := c.receiver_type_name(base_id, cur_module, imports, local_values, local_types) if type_name.len == 0 { return false } @@ -2371,13 +2440,18 @@ fn (c &CallCollector) collect_typed_receiver_method(base_id flat.NodeId, method return true } -fn (c &CallCollector) receiver_type_name(base_id flat.NodeId, cur_module string, imports map[string]string, local_values map[string]bool) string { +fn (c &CallCollector) receiver_type_name(base_id flat.NodeId, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string) string { + base := c.a.node(base_id) base_type := c.node_type(base_id) type_name := resolve_type_name(base_type) if type_name.len > 0 { return type_name } - base := c.a.node(base_id) + if base.kind == .ident && base.value.len > 0 { + if local_type := local_types[base.value] { + return local_type + } + } if base.kind == .ident && base.value.len > 0 { if base.value in local_values { return '' @@ -2398,7 +2472,9 @@ fn (c &CallCollector) typed_receiver_method_name(type_name string, method string return none } mut candidates := []string{cap: 4} - if type_name.contains('.') { + if type_name.starts_with('map[') { + candidates << markused_map_receiver_method_candidates(type_name, method, cur_module) + } else if type_name.contains('.') { candidates << '${type_name}.${method}' } else { if cur_module.len > 0 && cur_module != 'main' && cur_module != 'builtin' { @@ -2418,6 +2494,71 @@ fn (c &CallCollector) typed_receiver_method_name(type_name string, method string return none } +fn markused_map_receiver_method_candidates(receiver_type string, method string, cur_module string) []string { + clean_type := markused_clean_map_type(receiver_type) + key_type := markused_map_key_type(clean_type) + value_type := markused_map_value_type(clean_type) + mut candidates := []string{} + candidates << '${clean_type}.${method}' + if key_type.len == 0 || value_type.len == 0 { + return candidates + } + short_value := if value_type.contains('.') { + value_type.all_after_last('.') + } else { + value_type + } + short_map := 'map[${key_type}]${short_value}' + if short_map != clean_type { + candidates << '${short_map}.${method}' + } + if value_type.contains('.') { + mod_name := value_type.all_before_last('.') + candidates << '${mod_name}.${short_map}.${method}' + } else if cur_module.len > 0 && cur_module != 'main' && cur_module != 'builtin' { + candidates << '${cur_module}.${short_map}.${method}' + } + return candidates +} + +fn markused_clean_map_type(receiver_type string) string { + mut clean := receiver_type.trim_space() + for { + if clean.starts_with('shared ') { + clean = clean[7..].trim_space() + continue + } + if clean.starts_with('&') { + clean = clean[1..].trim_space() + continue + } + break + } + return clean +} + +fn markused_map_key_type(type_str string) string { + if !type_str.starts_with('map[') { + return '' + } + bracket_end := type_str.index(']') or { return '' } + if bracket_end > 4 { + return type_str[4..bracket_end] + } + return '' +} + +fn markused_map_value_type(type_str string) string { + if !type_str.starts_with('map[') { + return '' + } + bracket_end := type_str.index(']') or { return '' } + if bracket_end + 1 < type_str.len { + return type_str[bracket_end + 1..] + } + return '' +} + fn (c &CallCollector) add_typed_receiver_method_name(method_name string, mut calls []string) { calls << method_name lowered := markused_c_name(method_name) @@ -2581,7 +2722,7 @@ fn resolve_type_name(t types.Type) string { } else if t is types.Array { return 'Array' } else if t is types.Map { - return 'map' + return 'map[${t.key_type.name()}]${t.value_type.name()}' } else if t is types.Pointer { return resolve_type_name(t.base_type) } else if t is types.Primitive { diff --git a/vlib/v3/pref/pref.v b/vlib/v3/pref/pref.v index 09b460940..8b8e48dd3 100644 --- a/vlib/v3/pref/pref.v +++ b/vlib/v3/pref/pref.v @@ -10,6 +10,7 @@ pub mut: target_os string = os.user_os() user_defines []string backend string = 'c' + c99 bool vroot string = detect_vroot() selfhost bool building_v bool // compiling the V compiler itself: no generics, skip monomorphization diff --git a/vlib/v3/test_all.vsh b/vlib/v3/test_all.vsh index d1919a2ea..ea7a77282 100644 --- a/vlib/v3/test_all.vsh +++ b/vlib/v3/test_all.vsh @@ -11,6 +11,8 @@ struct Config { repo_root string tests_dir string v3_src string + c99 bool + c99_flag string host_backend string host_os string } @@ -51,12 +53,14 @@ fn main() { section(3, 'C backend hello world') hello_v := os.join_path(cfg.tests_dir, 'hello.v') - run('${q(v3_bin)} ${q(hello_v)} -b c -o ${q(hello_c_bin)}') + run('${q(v3_bin)} ${cfg.c99_flag} ${q(hello_v)} -b c -o ${q(hello_c_bin)}') run(q(hello_c_bin)) cleanup_files([hello_c_bin, hello_c_bin + '.c']) section(4, 'ARM64 self-host hello world') - if cfg.host_backend == 'arm64' && cfg.host_os == 'macos' { + if cfg.c99 { + println(' Skipping ARM64 self-host in C99 mode (-c99 applies to the C backend)') + } else if cfg.host_backend == 'arm64' && cfg.host_os == 'macos' { run('${q(v3_bin)} --no-parallel -selfhost -b arm64 -o ${q(v4_arm_bin)} ${q(cfg.v3_src)}') run('${q(v4_arm_bin)} --no-parallel -b arm64 -o ${q(hello_arm_bin)} ${q(hello_v)}') run(q(hello_arm_bin)) @@ -67,11 +71,11 @@ fn main() { section(5, 'Self-host chain (v3->v4->v5->v6)') println(' Building v4 from v3...') - run('${q(v3_bin)} --no-parallel -selfhost -o ${q(v4_bin)} ${q(cfg.v3_src)}') + run('${q(v3_bin)} ${cfg.c99_flag} --no-parallel -selfhost -o ${q(v4_bin)} ${q(cfg.v3_src)}') println(' Building v5 from v4...') - run('${q(v4_bin)} --no-parallel -selfhost -o ${q(v5_bin)} ${q(cfg.v3_src)}') + run('${q(v4_bin)} ${cfg.c99_flag} --no-parallel -selfhost -o ${q(v5_bin)} ${q(cfg.v3_src)}') println(' Building v6 from v5...') - run('${q(v5_bin)} --no-parallel -selfhost -o ${q(v6_bin)} ${q(cfg.v3_src)}') + run('${q(v5_bin)} ${cfg.c99_flag} --no-parallel -selfhost -o ${q(v6_bin)} ${q(cfg.v3_src)}') converged_size := assert_same_file_bytes('v5/v6 generated C output', v5_bin + '.c', v6_bin + '.c') println(' v5.c=v6.c (${converged_size} bytes) - chain converged') @@ -80,7 +84,7 @@ fn main() { section(6, 'Language feature parity') lang_v := os.join_path(cfg.tests_dir, 'test_all_lang_features.v') lang_out := os.join_path(cfg.tests_dir, 'test_all_lang_features.out') - run('${q(v3_bin)} ${q(lang_v)} -b c -o ${q(v3_lang_bin)}') + run('${q(v3_bin)} ${cfg.c99_flag} ${q(lang_v)} -b c -o ${q(v3_lang_bin)}') v3_c_out := run_output(q(v3_lang_bin)) expected_out := read_text_file(lang_out) assert_same_text('language feature output', v3_c_out, expected_out) @@ -93,6 +97,7 @@ fn main() { } fn parse_config() Config { + c99 := parse_args() script_dir := os.real_path(@DIR) repo_root := os.real_path(os.join_path(script_dir, '..', '..')) tests_dir := os.join_path(script_dir, 'tests') @@ -106,11 +111,32 @@ fn parse_config() Config { repo_root: repo_root tests_dir: tests_dir v3_src: os.join_path(script_dir, 'v3.v') + c99: c99 + c99_flag: if c99 { '-c99' } else { '' } host_backend: native_backend_arch() host_os: os.user_os() } } +fn parse_args() bool { + mut c99 := false + for arg in os.args[1..] { + match arg { + '-c99', '--c99' { + c99 = true + } + '-h', '--help' { + println('usage: test_all.vsh [-c99]') + exit(0) + } + else { + fail('unknown argument: ${arg}') + } + } + } + return c99 +} + fn native_backend_arch() string { machine := os.uname().machine.to_lower() match machine { diff --git a/vlib/v3/tests/comptime_top_level_decl_codegen_test.v b/vlib/v3/tests/comptime_top_level_decl_codegen_test.v index 3fd7a015a..184ec5bc9 100644 --- a/vlib/v3/tests/comptime_top_level_decl_codegen_test.v +++ b/vlib/v3/tests/comptime_top_level_decl_codegen_test.v @@ -156,7 +156,7 @@ fn test_synthetic_top_level_block_keeps_main_scope_locals() { v3_bin := comptime_decl_build_v3() run := comptime_decl_compile_run_source(v3_bin, 'synthetic_block_main_scope', 'module main -$if linux { +$if some_feature ? { x := 1 y := 2 } @@ -166,7 +166,8 @@ fn helper() int { } println(int_str(x + y)) -', '') +', + '-d some_feature') assert run.exit_code == 0, run.output assert run.output.trim_space() == '3' } diff --git a/vlib/v3/tests/markused_typed_receiver_codegen_test.v b/vlib/v3/tests/markused_typed_receiver_codegen_test.v index ebff7bb12..9df28a8b8 100644 --- a/vlib/v3/tests/markused_typed_receiver_codegen_test.v +++ b/vlib/v3/tests/markused_typed_receiver_codegen_test.v @@ -222,6 +222,40 @@ pub fn (s Series) measure(x int) int { assert !used['othermod__Helper__secret'], used.str() } +fn test_markused_prefers_scoped_node_type_for_shadowed_receiver_locals() { + used := typed_receiver_mark_used_project('shadowed_receiver_local_type', { + 'main.v': 'module main + +struct Outer {} +struct Inner {} + +fn (o Outer) score() int { + return 1 +} + +fn (i Inner) score() int { + return 2 +} + +fn run() int { + item := Outer{} + mut total := item.score() + if total > 0 { + item := Inner{} + total += item.score() + } + return total +} + +fn main() { + assert run() == 3 +} +' + }, 'main.v') + assert used['Outer.score'] || used['main.Outer.score'] || used['main__Outer__score'], used.str() + assert used['Inner.score'] || used['main.Inner.score'] || used['main__Inner__score'], used.str() +} + fn test_markused_roots_exact_typed_const_receiver_method() { v3_bin := typed_receiver_build_v3() main_path := typed_receiver_write_project() diff --git a/vlib/v3/tests/option_arg_codegen_test.v b/vlib/v3/tests/option_arg_codegen_test.v index c0436e353..96282c0d0 100644 --- a/vlib/v3/tests/option_arg_codegen_test.v +++ b/vlib/v3/tests/option_arg_codegen_test.v @@ -42,3 +42,11 @@ fn test_optional_if_expr_codegen_initializes_optional_temp() { "fn none_int() ?int {\n\treturn none\n}\n\nfn some_int() ?int {\n\treturn 7\n}\n\nfn choose(flag bool) ?int {\n\tvalue := if flag {\n\t\tnone_int()\n\t} else {\n\t\tsome_int()\n\t}\n\treturn value\n}\n\nfn main() {\n\tgood := choose(false) or { 0 }\n\tnone_value := choose(true) or { -1 }\n\tassert good == 7\n\tassert none_value == -1\n\tprintln('ok')\n}\n") assert out == 'ok' } + +// test_optional_clone_return_wraps_payload validates this v3 regression case. +fn test_optional_clone_return_wraps_payload() { + v3_bin := build_v3() + out := run_good(v3_bin, 'optional_clone_return_codegen_input', + "fn clone_array(src []int) ?[]int {\n\treturn src.clone()\n}\n\nfn clone_array_defer(src []int) ?[]int {\n\tdefer {}\n\treturn src.clone()\n}\n\nfn clone_map(src map[string]int) ?map[string]int {\n\treturn src.clone()\n}\n\nfn clone_map_defer(src map[string]int) ?map[string]int {\n\tdefer {}\n\treturn src.clone()\n}\n\nfn array_sum(values []int) int {\n\tmut total := 0\n\tfor value in values {\n\t\ttotal += value\n\t}\n\treturn total\n}\n\nfn main() {\n\tmut values := []int{}\n\tvalues << 2\n\tvalues << 5\n\tarr := clone_array(values) or { []int{} }\n\tarr_defer := clone_array_defer(values) or { []int{} }\n\tmut lookup := map[string]int{}\n\tlookup['a'] = 3\n\tlookup['b'] = 4\n\tcloned := clone_map(lookup) or { map[string]int{} }\n\tcloned_defer := clone_map_defer(lookup) or { map[string]int{} }\n\tassert array_sum(arr) == 7\n\tassert array_sum(arr_defer) == 7\n\tassert cloned['a'] + cloned['b'] == 7\n\tassert cloned_defer['a'] + cloned_defer['b'] == 7\n\tprintln('ok')\n}\n") + assert out == 'ok' +} diff --git a/vlib/v3/tests/prod_flag_test.v b/vlib/v3/tests/prod_flag_test.v index 29bf11c29..78e15a57c 100644 --- a/vlib/v3/tests/prod_flag_test.v +++ b/vlib/v3/tests/prod_flag_test.v @@ -22,3 +22,101 @@ fn test_prod_flag_before_input_uses_optimized_c_compile() { assert run.exit_code == 0, run.output assert run.output.trim_space() == 'hello world' } + +// test_c99_flag_uses_c99_c_compile_mode validates this v3 regression case. +fn test_c99_flag_uses_c99_c_compile_mode() { + v3_bin := os.join_path(os.temp_dir(), 'v3_c99_flag_test') + build := os.execute('${vexe} -o ${v3_bin} ${v3_src}') + assert build.exit_code == 0, build.output + + out_bin := os.join_path(os.temp_dir(), 'v3_c99_hello') + compile := os.execute('${v3_bin} -prod -c99 ${hello_src} -o ${out_bin}') + assert compile.exit_code == 0, compile.output + assert compile.output.contains('cc -std=c99 -O2') + assert !compile.output.contains('cc -std=gnu11') + assert !compile.output.contains('tcc.exe') + + run := os.execute(out_bin) + assert run.exit_code == 0, run.output + assert run.output.trim_space() == 'hello world' +} + +// test_c99_flag_emits_linux_feature_macros_before_includes validates this v3 regression case. +fn test_c99_flag_emits_linux_feature_macros_before_includes() { + v3_bin := os.join_path(os.temp_dir(), 'v3_c99_feature_macro_test') + build := os.execute('${vexe} -o ${v3_bin} ${v3_src}') + assert build.exit_code == 0, build.output + + out_c := os.join_path(os.temp_dir(), 'v3_c99_feature_macro_hello.c') + compile := os.execute('${v3_bin} -c99 ${hello_src} -o ${out_c}') + assert compile.exit_code == 0, compile.output + + c_code := os.read_file(out_c) or { panic(err) } + gnu_idx := c_code.index('#define _GNU_SOURCE') or { -1 } + posix_idx := c_code.index('#define _POSIX_C_SOURCE 200809L') or { -1 } + include_idx := c_code.index('#include ') or { -1 } + assert gnu_idx >= 0, c_code[..200] + assert posix_idx >= 0, c_code[..200] + assert include_idx >= 0, c_code[..200] + assert gnu_idx < include_idx, c_code[..200] + assert posix_idx < include_idx, c_code[..200] +} + +// test_c99_flag_preserves_stdatomic_header validates this v3 regression case. +fn test_c99_flag_preserves_stdatomic_header() { + v3_bin := os.join_path(os.temp_dir(), 'v3_c99_stdatomic_test') + build := os.execute('${vexe} -o ${v3_bin} ${v3_src}') + assert build.exit_code == 0, build.output + + src := os.join_path(os.temp_dir(), 'v3_c99_stdatomic_swap.v') + os.write_file(src, + "import sync.stdatomic\n\nfn main() {\n\tmut x := u64(1)\n\told := C.atomic_exchange_u64(voidptr(&x), u64(7))\n\tassert old == u64(1)\n\tassert x == u64(7)\n\tprintln('atomic exchange ok')\n}\n") or { + panic(err) + } + + out_bin := os.join_path(os.temp_dir(), 'v3_c99_stdatomic_swap') + compile := os.execute('${v3_bin} -c99 ${src} -o ${out_bin}') + assert compile.exit_code == 0, compile.output + assert !compile.output.contains('C compilation failed'), compile.output + + run := os.execute(out_bin) + assert run.exit_code == 0, run.output + assert run.output.trim_space() == 'atomic exchange ok' +} + +// test_c99_flag_system_stdatomic_include_suppresses_fallback_typedef validates this v3 regression case. +fn test_c99_flag_system_stdatomic_include_suppresses_fallback_typedef() { + v3_bin := os.join_path(os.temp_dir(), 'v3_c99_system_stdatomic_test') + build := os.execute('${vexe} -o ${v3_bin} ${v3_src}') + assert build.exit_code == 0, build.output + + src := os.join_path(os.temp_dir(), 'v3_c99_system_stdatomic.v') + os.write_file(src, + "#include \n\nfn main() {\n\tprintln('system stdatomic ok')\n}\n") or { + panic(err) + } + + out_c := os.join_path(os.temp_dir(), 'v3_c99_system_stdatomic.c') + gen_c := os.execute('${v3_bin} -c99 ${src} -o ${out_c}') + assert gen_c.exit_code == 0, gen_c.output + c_code := os.read_file(out_c) or { panic(err) } + assert c_code.contains('#include '), c_code + assert !c_code.contains('typedef volatile uintptr_t atomic_uintptr_t;'), c_code + assert c_code.contains('static inline u64 atomic_fetch_add_u64'), c_code + + out_bin := os.join_path(os.temp_dir(), 'v3_c99_system_stdatomic') + compile := os.execute('${v3_bin} -prod -c99 ${src} -o ${out_bin}') + assert compile.exit_code == 0, compile.output + + run := os.execute(out_bin) + assert run.exit_code == 0, run.output + assert run.output.trim_space() == 'system stdatomic ok' + + gnu_bin := os.join_path(os.temp_dir(), 'v3_gnu11_system_stdatomic') + gnu_compile := os.execute('${v3_bin} -prod ${src} -o ${gnu_bin}') + assert gnu_compile.exit_code == 0, gnu_compile.output + + gnu_run := os.execute(gnu_bin) + assert gnu_run.exit_code == 0, gnu_run.output + assert gnu_run.output.trim_space() == 'system stdatomic ok' +} diff --git a/vlib/v3/transform/transform.v b/vlib/v3/transform/transform.v index 6da8c0ce8..4af26feb0 100644 --- a/vlib/v3/transform/transform.v +++ b/vlib/v3/transform/transform.v @@ -389,11 +389,6 @@ fn (mut t Transformer) collect_types() { .fn_decl { if node.typ.len > 0 { ret_typ := t.normalize_type_in_module(node.typ, cur_mod) - t.fn_ret_types[node.value] = ret_typ - lowered := c_name(node.value) - if lowered != node.value { - t.fn_ret_types[lowered] = ret_typ - } if cur_mod.len > 0 && cur_mod != 'main' && cur_mod != 'builtin' { qname := '${cur_mod}.${node.value}' t.fn_ret_types[qname] = ret_typ @@ -401,6 +396,12 @@ fn (mut t Transformer) collect_types() { if qlowered != qname { t.fn_ret_types[qlowered] = ret_typ } + } else { + t.fn_ret_types[node.value] = ret_typ + lowered := c_name(node.value) + if lowered != node.value { + t.fn_ret_types[lowered] = ret_typ + } } } } @@ -3505,7 +3506,8 @@ fn (t &Transformer) block_multi_return_types(node flat.Node, expected_count int) fn multi_return_types_from_type(typ types.Type, expected_count int) ?[]types.Type { if typ is types.MultiReturn { if expected_count <= 0 || typ.types.len == expected_count { - return typ.types.clone() + items := typ.types.clone() + return items } return none } diff --git a/vlib/v3/v3.v b/vlib/v3/v3.v index 820f83347..62ff9a9a8 100644 --- a/vlib/v3/v3.v +++ b/vlib/v3/v3.v @@ -23,13 +23,13 @@ fn run_compile_command(cmd string) os.Result { } } -fn prepare_c_flags_for_link(flags []string) ![]string { +fn prepare_c_flags_for_link(flags []string, c99 bool) ![]string { support_flags := c_object_compile_support_flags(flags) mut prepared := []string{} for flag in flags { clean := flag.trim_space() if c_flag_is_object_file(clean) { - prepared << ensure_c_object_file(clean, support_flags)! + prepared << ensure_c_object_file(clean, support_flags, c99)! } else { prepared << flag } @@ -54,7 +54,7 @@ fn c_object_compile_support_flags(flags []string) []string { return support } -fn ensure_c_object_file(obj_path string, support_flags []string) !string { +fn ensure_c_object_file(obj_path string, support_flags []string, c99 bool) !string { if os.exists(obj_path) { return obj_path } @@ -63,13 +63,13 @@ fn ensure_c_object_file(obj_path string, support_flags []string) !string { } cache_dir := os.join_path(os.vtmp_dir(), 'v3_thirdparty_objs') os.mkdir_all(cache_dir)! - cached_obj := os.join_path(cache_dir, c_object_cache_name(obj_path, support_flags)) + std_flag := if source_file.ends_with('.cpp') { '-std=c++11' } else { c_standard_flag(c99) } + cached_obj := os.join_path(cache_dir, c_object_cache_name(obj_path, support_flags, std_flag)) if os.exists(cached_obj) && os.file_last_mod_unix(cached_obj) >= os.file_last_mod_unix(source_file) { return cached_obj } compiler := if source_file.ends_with('.cpp') { 'c++' } else { 'cc' } - std_flag := if source_file.ends_with('.cpp') { '-std=c++11' } else { '-std=gnu11' } cmd := '${compiler} ${std_flag} -w ${support_flags.join(' ')} -o ${os.quoted_path(cached_obj)} -c ${os.quoted_path(source_file)}' res := os.execute(cmd) if res.exit_code != 0 { @@ -89,15 +89,14 @@ fn c_source_from_object_file(obj_path string) ?string { return none } -fn c_object_cache_name(path string, support_flags []string) string { +fn c_object_cache_name(path string, support_flags []string, std_flag string) string { base := path.replace_each(['/', '_', '\\', '_', ':', '_', '.', '_', ' ', '_']) // The compile flags (`-D`/`-I`/...) change the object contents, so they must // be part of the cache key; otherwise a rebuild with different `#flag` defines // silently links the stale object built with the previous configuration. - if support_flags.len == 0 { - return base + '.o' - } - return '${base}_${c_flags_hash(support_flags)}.o' + mut cache_flags := [std_flag] + cache_flags << support_flags + return '${base}_${c_flags_hash(cache_flags)}.o' } fn c_flags_hash(flags []string) string { @@ -117,11 +116,15 @@ fn c_flag_is_c_source_file(flag string) bool { return flag.ends_with('.c') || flag.ends_with('.cc') || flag.ends_with('.cpp') } +fn c_standard_flag(c99 bool) string { + return if c99 { '-std=c99' } else { '-std=gnu11' } +} + // main runs the v3 entry point. fn main() { args := os.args[1..] if args.len == 0 { - eprintln('usage: v3 [-o output|file.c] [-b c|arm64|eval] [-d flag]') + eprintln('usage: v3 [-o output|file.c] [-b c|arm64|eval] [-c99] [-d flag]') exit(1) } @@ -134,6 +137,7 @@ fn main() { mut no_parallel := false mut parallel_transform := false mut building_v := false + mut c99 := false mut all_backends := false mut compile_backends := []string{} mut user_defines := []string{} @@ -156,6 +160,12 @@ fn main() { // of the generics machinery) is pure overhead when building it. building_v = true i++ + } else if args[i] == '-c99' || args[i] == '--c99' { + c99 = true + if 'c99' !in user_defines { + user_defines << 'c99' + } + i++ } else if args[i] == '-strict' { is_strict = true i++ @@ -264,6 +274,7 @@ fn main() { // Parse directly to flat AST mut prefs := pref.new_preferences() prefs.backend = backend + prefs.c99 = c99 prefs.user_defines = user_defines prefs.vroot = resolve_vroot(prefs.vroot) prefs.selfhost = is_selfhost @@ -416,7 +427,9 @@ fn main() { } } else { // C backend (default) + c_standard := c_standard_flag(prefs.c99) mut g := cgen.FlatGen.new() + g.set_c99_mode(prefs.c99) c_code := g.gen_with_used_test_options(a, used_fns, &pre_tc, no_parallel, test_files) if !write_text_file_raw(output_file, c_code) { eprintln('error writing ${output_file}') @@ -434,7 +447,7 @@ fn main() { } else { '-w' } - resolved_c_flags := prepare_c_flags_for_link(g.c_flags()) or { + resolved_c_flags := prepare_c_flags_for_link(g.c_flags(), prefs.c99) or { eprintln(err.msg()) exit(1) } @@ -464,8 +477,8 @@ fn main() { tcc_lib_dir := os.join_path_single(tcc_dir, 'lib') tcc_includes := '-I${os.join_path_single(tcc_lib_dir, 'include')}' tcc_lib := '-L${tcc_lib_dir}' - cc_cmd = '${tcc_path} ${tcc_includes} ${tcc_lib} ${warn_flags} -o ${bin_file} ${output_file} ${c_flags} -lm' - exec_cmd = 'cd ${cc_dir} && ${tcc_path} ${tcc_includes} ${tcc_lib} ${warn_flags} -o out src.c ${c_flags} -lm' + cc_cmd = '${tcc_path} ${c_standard} ${tcc_includes} ${tcc_lib} ${warn_flags} -o ${bin_file} ${output_file} ${c_flags} -lm' + exec_cmd = 'cd ${cc_dir} && ${tcc_path} ${c_standard} ${tcc_includes} ${tcc_lib} ${warn_flags} -o out src.c ${c_flags} -lm' println(' > ${cc_cmd}') result = run_compile_command(exec_cmd) } @@ -478,8 +491,8 @@ fn main() { } else { '' } - cc_cmd = 'cc -std=gnu11 ${opt_flag}${warn_flags} -Wno-int-conversion${stack_flag} -o ${bin_file} ${output_file} ${c_flags} -lm' - exec_cmd = 'cd ${cc_dir} && cc -std=gnu11 ${opt_flag}${warn_flags} -Wno-int-conversion${stack_flag} -o out src.c ${c_flags} -lm' + cc_cmd = 'cc ${c_standard} ${opt_flag}${warn_flags} -Wno-int-conversion${stack_flag} -o ${bin_file} ${output_file} ${c_flags} -lm' + exec_cmd = 'cd ${cc_dir} && cc ${c_standard} ${opt_flag}${warn_flags} -Wno-int-conversion${stack_flag} -o out src.c ${c_flags} -lm' println(' > ${cc_cmd}') result = run_compile_command(exec_cmd) if result.exit_code != 0 { -- 2.39.5