Skip to content

Commit 1498e02

Browse files
authored
v3: add c99 mode (#27576)
1 parent cef604a commit 1498e02

12 files changed

Lines changed: 651 additions & 105 deletions

vlib/v3/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ The table uses the first v3-generated C stage, `./v3 -parallel -o v4 v3.v`.
218218
Bundled TCC currently rejects the atomic helper shims on macOS and the driver
219219
falls back to `cc`.
220220

221+
Pass `-c99` to the v3 C backend to compile generated C and support objects as
222+
C99 (`cc -std=c99`) instead of the default GNU11 mode. `test_all.vsh -c99`
223+
validates the C backend and self-host chain in that mode, and skips the ARM64
224+
native backend step because `-c99` only applies to generated C.
225+
221226
| Phase | Time | Peak RSS |
222227
|----------------|----------:|---------:|
223228
| parse | 59.47 ms | 73 MB |

vlib/v3/gen/c/cleanc.v

Lines changed: 152 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ mut:
4040
libc_compat_fns map[string]bool
4141
tc &types.TypeChecker = unsafe { nil }
4242
has_builtins bool
43+
has_stdatomic_header bool
44+
has_stdatomic_compat_header bool
4345
tmp_count int
4446
line_start bool
4547
field_name_set map[string]bool // every struct field's C name (lazy) — for const/field collision checks
@@ -53,6 +55,7 @@ mut:
5355
struct_decl_short_infos map[string]StructDeclInfo
5456
runtime_inits []string
5557
compiler_vroot string
58+
c99_mode bool
5659
cur_fn_name string
5760
cur_param_names []string
5861
cur_param_type_values []types.Type
@@ -100,6 +103,11 @@ pub fn (g &FlatGen) c_flags() []string {
100103
return g.c_flags.clone()
101104
}
102105

106+
// set_c99_mode configures whether generated C should support strict C99 builds.
107+
pub fn (mut g FlatGen) set_c99_mode(enabled bool) {
108+
g.c99_mode = enabled
109+
}
110+
103111
// new creates a FlatGen value for c.
104112
pub fn FlatGen.new() FlatGen {
105113
return FlatGen{
@@ -208,6 +216,8 @@ pub fn (mut g FlatGen) gen_with_used_options(a &flat.FlatAst, used_fns map[strin
208216
g.c_directives = []CDirective{}
209217
g.c_flags = []string{}
210218
g.libc_compat_fns = map[string]bool{}
219+
g.has_stdatomic_header = false
220+
g.has_stdatomic_compat_header = false
211221
g.modules = map[string]string{}
212222
g.fn_ptr_types = map[string]string{}
213223
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
499509
if include_arg.len == 0 {
500510
return true
501511
}
502-
// The atomic helper headers are superseded by the inline C11 `__atomic_*`
503-
// helpers emitted in builtin_abi_decls(); also including them would
504-
// redefine `v_prealloc_atomic_*` and the `atomic_*` helpers.
505-
if include_arg.contains('prealloc_atomics.h') || include_arg.contains('stdatomic') {
512+
// These helper headers are superseded by the inline compiler helpers emitted in
513+
// builtin_abi_decls(); also including them would redefine the helpers.
514+
if include_arg.contains('prealloc_atomics.h') || include_arg.contains('filelock_helpers.h') {
506515
return true
507516
}
517+
if is_stdatomic_header(include_arg) {
518+
g.has_stdatomic_header = true
519+
}
520+
if is_stdatomic_compat_header(include_arg) {
521+
g.has_stdatomic_compat_header = true
522+
}
508523
g.add_c_directive(module_name, '#include ${include_arg}', before_import)
509524
return true
510525
}
@@ -517,6 +532,17 @@ fn (mut g FlatGen) collect_c_directive(module_name string, node flat.Node, sourc
517532
return false
518533
}
519534

535+
fn is_stdatomic_header(include_arg string) bool {
536+
normalized := include_arg.replace('\\', '/')
537+
return normalized == '<stdatomic.h>' || normalized == '"stdatomic.h"'
538+
|| normalized.ends_with('/stdatomic.h"') || is_stdatomic_compat_header(normalized)
539+
}
540+
541+
fn is_stdatomic_compat_header(include_arg string) bool {
542+
normalized := include_arg.replace('\\', '/')
543+
return normalized.contains('/thirdparty/stdatomic/') && normalized.ends_with('/atomic.h"')
544+
}
545+
520546
fn (mut g FlatGen) add_c_directive(module_name string, text string, before_import bool) {
521547
if text.len == 0 {
522548
return
@@ -2815,6 +2841,7 @@ fn (g &FlatGen) is_module_qualified_enum(base flat.Node) bool {
28152841
}
28162842

28172843
fn (mut g FlatGen) preamble() {
2844+
g.c99_feature_test_macros()
28182845
g.writeln('#include <stdio.h>')
28192846
g.writeln('#include <stdlib.h>')
28202847
g.writeln('#include <string.h>')
@@ -2823,6 +2850,7 @@ fn (mut g FlatGen) preamble() {
28232850
g.writeln('#include <stdint.h>') // guarantees UINTPTR_MAX for the pointer-width atomic helpers
28242851
g.writeln('#include <math.h>')
28252852
g.writeln('#include <unistd.h>')
2853+
g.c99_atomic_compat_decls()
28262854
if g.has_builtins {
28272855
g.writeln('#include <time.h>')
28282856
g.writeln('#include <sys/time.h>')
@@ -2836,7 +2864,6 @@ fn (mut g FlatGen) preamble() {
28362864
g.writeln('#include <sys/utsname.h>')
28372865
g.writeln('#include <pthread.h>')
28382866
g.writeln('#include <semaphore.h>')
2839-
g.writeln('#include <stdatomic.h>')
28402867
g.writeln('#include <termios.h>')
28412868
g.writeln('#include <unistd.h>')
28422869
g.writeln('#include <arpa/inet.h>')
@@ -2904,6 +2931,36 @@ fn (mut g FlatGen) preamble() {
29042931
g.writeln('')
29052932
}
29062933

2934+
fn (mut g FlatGen) c99_feature_test_macros() {
2935+
if !g.c99_mode {
2936+
return
2937+
}
2938+
g.writeln('#if defined(__linux__) && !defined(_GNU_SOURCE)')
2939+
g.writeln('#define _GNU_SOURCE')
2940+
g.writeln('#endif')
2941+
g.writeln('#if defined(__linux__) && !defined(_POSIX_C_SOURCE)')
2942+
g.writeln('#define _POSIX_C_SOURCE 200809L')
2943+
g.writeln('#endif')
2944+
}
2945+
2946+
fn (mut g FlatGen) c99_atomic_compat_decls() {
2947+
if g.has_stdatomic_header {
2948+
return
2949+
}
2950+
g.writeln('typedef volatile uintptr_t atomic_uintptr_t;')
2951+
g.writeln('#ifndef memory_order_relaxed')
2952+
g.writeln('#define memory_order_relaxed 0')
2953+
g.writeln('#define memory_order_consume 1')
2954+
g.writeln('#define memory_order_acquire 2')
2955+
g.writeln('#define memory_order_release 3')
2956+
g.writeln('#define memory_order_acq_rel 4')
2957+
g.writeln('#define memory_order_seq_cst 5')
2958+
g.writeln('#endif')
2959+
g.writeln('#ifndef atomic_thread_fence')
2960+
g.writeln('#define atomic_thread_fence(order) __sync_synchronize()')
2961+
g.writeln('#endif')
2962+
}
2963+
29072964
fn (mut g FlatGen) write_arch_macros() {
29082965
g.writeln('#ifndef __V_architecture')
29092966
g.writeln('#define __V_architecture 0')
@@ -2954,37 +3011,26 @@ fn (mut g FlatGen) libc_compat_decls() {
29543011
}
29553012
}
29563013

2957-
fn (mut g FlatGen) builtin_abi_decls() {
2958-
if !g.has_builtins {
3014+
fn (mut g FlatGen) prealloc_atomic_compat_decls() {
3015+
g.writeln('static inline int v_prealloc_atomic_add_i32(int *ptr, int delta) { return __atomic_add_fetch(ptr, delta, 5); }')
3016+
g.writeln('static inline int v_prealloc_atomic_load_i32(int *ptr) { return __atomic_add_fetch(ptr, 0, 5); }')
3017+
g.writeln('#ifdef __TINYC__')
3018+
g.writeln('static inline int v_prealloc_atomic_store_i32(int *ptr, int val) { return (int)__atomic_exchange_4((u32*)ptr, (u32)val, 5); }')
3019+
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); }')
3020+
g.writeln('#else')
3021+
g.writeln('static inline int v_prealloc_atomic_store_i32(int *ptr, int val) { return __atomic_exchange_n(ptr, val, 5); }')
3022+
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); }')
3023+
g.writeln('#endif')
3024+
}
3025+
3026+
fn (mut g FlatGen) atomic_builtin_compat_decls() {
3027+
if g.has_stdatomic_compat_header {
29593028
return
29603029
}
2961-
g.libc_compat_decls()
2962-
g.writeln('#define array_new(elem_size, len, cap) __new_array((len), (cap), (elem_size))')
2963-
g.writeln('#define array_push array__push')
2964-
g.writeln('void array__push_many(array* a, void* val, int size);')
2965-
g.writeln('#define array_push_many_ptr(a, val, size) array__push_many((a), (void*)(val), (size))')
2966-
g.writeln('#define array_get array__get')
2967-
g.writeln('#define array_set(a, i, ...) array__set(&(a), (i), __VA_ARGS__)')
2968-
g.writeln('array array__clone(array* a);')
2969-
g.writeln('#define array_slice array__slice')
2970-
g.writeln('#define array_delete array__delete')
2971-
g.writeln('#define array_ensure_cap array__ensure_cap')
2972-
g.writeln('#define map__get_or_set map__get_and_set')
2973-
g.writeln('#ifndef V_COMMIT_HASH')
2974-
g.writeln('#define V_COMMIT_HASH ""')
2975-
g.writeln('#endif')
2976-
// Weak fallbacks for the heap-tracking hooks. A program that provides real
2977-
// implementations (e.g. a `vheap_alloc`/`vheap_free` from a linked C file, as
2978-
// some projects do) overrides these without a redefinition/static-vs-non-static
2979-
// clash against that file's own non-static prototype.
2980-
g.writeln('__attribute__((weak)) void vheap_alloc(void* p, u64 n) { (void)p; (void)n; }')
2981-
g.writeln('__attribute__((weak)) void vheap_free(void* p) { (void)p; }')
2982-
// Atomic helpers. We use the C11 __atomic_* builtins (memory order 5 == __ATOMIC_SEQ_CST).
3030+
// Atomic helpers. We use compiler __atomic_* builtins (memory order 5 == __ATOMIC_SEQ_CST).
29833031
// clang/gcc inline the generic _n / RMW builtins. tcc only implements the inline
29843032
// __atomic_{add,sub,fetch}_* RMW builtins; for load/store/exchange/cas it has no generic
29853033
// _n form, so we route those to the sized __atomic_*_N libcalls (resolved from libc).
2986-
g.writeln('static inline int v_prealloc_atomic_add_i32(int *ptr, int delta) { return __atomic_add_fetch(ptr, delta, 5); }')
2987-
g.writeln('static inline int v_prealloc_atomic_load_i32(int *ptr) { return __atomic_add_fetch(ptr, 0, 5); }')
29883034
g.writeln('static inline u32 atomic_fetch_add_u32(void* ptr, u32 delta) { return __atomic_fetch_add((u32*)ptr, delta, 5); }')
29893035
g.writeln('static inline u64 atomic_fetch_add_u64(void* ptr, u64 delta) { return __atomic_fetch_add((u64*)ptr, delta, 5); }')
29903036
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() {
29943040
g.writeln('static inline u64 atomic_load_u64(void* ptr) { return __atomic_fetch_add((u64*)ptr, 0, 5); }')
29953041
g.writeln('static inline void* atomic_load_ptr(void* ptr) { return *(void* volatile*)ptr; }')
29963042
g.writeln('#ifdef __TINYC__')
2997-
g.writeln('static inline int v_prealloc_atomic_store_i32(int *ptr, int val) { return (int)__atomic_exchange_4((u32*)ptr, (u32)val, 5); }')
2998-
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); }')
29993043
g.writeln('static inline void atomic_store_byte(void* ptr, byte val) { __atomic_store_1((byte*)ptr, val, 5); }')
30003044
g.writeln('static inline void atomic_store_u16(void* ptr, u16 val) { __atomic_store_2((u16*)ptr, val, 5); }')
30013045
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() {
30173061
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); }')
30183062
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); }')
30193063
g.writeln('#else')
3020-
g.writeln('static inline int v_prealloc_atomic_store_i32(int *ptr, int val) { return __atomic_exchange_n(ptr, val, 5); }')
3021-
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); }')
30223064
g.writeln('static inline void atomic_store_byte(void* ptr, byte val) { __atomic_store_n((byte*)ptr, val, 5); }')
30233065
g.writeln('static inline void atomic_store_u16(void* ptr, u16 val) { __atomic_store_n((u16*)ptr, val, 5); }')
30243066
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() {
30343076
g.writeln('#endif')
30353077
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); }')
30363078
g.writeln('static inline void cpu_relax(void) { __asm__ __volatile__("" ::: "memory"); }')
3079+
}
3080+
3081+
fn (mut g FlatGen) builtin_abi_decls() {
3082+
if !g.has_builtins {
3083+
return
3084+
}
3085+
g.libc_compat_decls()
3086+
g.writeln('#define array_new(elem_size, len, cap) __new_array((len), (cap), (elem_size))')
3087+
g.writeln('#define array_push array__push')
3088+
g.writeln('void array__push_many(array* a, void* val, int size);')
3089+
g.writeln('#define array_push_many_ptr(a, val, size) array__push_many((a), (void*)(val), (size))')
3090+
g.writeln('#define array_get array__get')
3091+
g.writeln('#define array_set(a, i, ...) array__set(&(a), (i), __VA_ARGS__)')
3092+
g.writeln('array array__clone(array* a);')
3093+
g.writeln('#define array_slice array__slice')
3094+
g.writeln('#define array_delete array__delete')
3095+
g.writeln('#define array_ensure_cap array__ensure_cap')
3096+
g.writeln('#define map__get_or_set map__get_and_set')
3097+
g.writeln('#ifndef V_COMMIT_HASH')
3098+
g.writeln('#define V_COMMIT_HASH ""')
3099+
g.writeln('#endif')
3100+
// Weak fallbacks for the heap-tracking hooks. A program that provides real
3101+
// implementations (e.g. a `vheap_alloc`/`vheap_free` from a linked C file, as
3102+
// some projects do) overrides these without a redefinition/static-vs-non-static
3103+
// clash against that file's own non-static prototype.
3104+
g.writeln('__attribute__((weak)) void vheap_alloc(void* p, u64 n) { (void)p; (void)n; }')
3105+
g.writeln('__attribute__((weak)) void vheap_free(void* p) { (void)p; }')
3106+
g.filelock_compat_decls()
3107+
g.prealloc_atomic_compat_decls()
3108+
g.atomic_builtin_compat_decls()
30373109
g.writeln('static inline double math__abs(double a) { return a < 0 ? -a : a; }')
30383110
g.writeln('static inline double math__min(double a, double b) { return a < b ? a : b; }')
30393111
g.writeln('static const u64 _wyp[4] = {0x2d358dccaa6c78a5ull, 0x8bb84b93962eacc9ull, 0x4b33a62ed433d4a3ull, 0x4d5a2da51de1aa47ull};')
@@ -3064,6 +3136,51 @@ fn (mut g FlatGen) builtin_abi_decls() {
30643136
g.writeln('')
30653137
}
30663138

3139+
fn (mut g FlatGen) filelock_compat_decls() {
3140+
g.writeln('#ifdef _WIN32')
3141+
g.writeln('#include <windows.h>')
3142+
g.writeln('int v_filelock_lock(void* handle, int exclusive, int immediate, u64 start, u64 len) {')
3143+
g.writeln('\tOVERLAPPED overlap;')
3144+
g.writeln('\tmemset(&overlap, 0, sizeof(overlap));')
3145+
g.writeln('\toverlap.Offset = (DWORD)(start & 0xffffffffULL);')
3146+
g.writeln('\toverlap.OffsetHigh = (DWORD)(start >> 32);')
3147+
g.writeln('\tDWORD flags = immediate ? LOCKFILE_FAIL_IMMEDIATELY : 0;')
3148+
g.writeln('\tif (exclusive) { flags |= LOCKFILE_EXCLUSIVE_LOCK; }')
3149+
g.writeln('\tDWORD low = len == 0 ? MAXDWORD : (DWORD)(len & 0xffffffffULL);')
3150+
g.writeln('\tDWORD high = len == 0 ? MAXDWORD : (DWORD)(len >> 32);')
3151+
g.writeln('\treturn LockFileEx((HANDLE)handle, flags, 0, low, high, &overlap) ? 0 : -1;')
3152+
g.writeln('}')
3153+
g.writeln('int v_filelock_unlock(void* handle, u64 start, u64 len) {')
3154+
g.writeln('\tOVERLAPPED overlap;')
3155+
g.writeln('\tmemset(&overlap, 0, sizeof(overlap));')
3156+
g.writeln('\toverlap.Offset = (DWORD)(start & 0xffffffffULL);')
3157+
g.writeln('\toverlap.OffsetHigh = (DWORD)(start >> 32);')
3158+
g.writeln('\tDWORD low = len == 0 ? MAXDWORD : (DWORD)(len & 0xffffffffULL);')
3159+
g.writeln('\tDWORD high = len == 0 ? MAXDWORD : (DWORD)(len >> 32);')
3160+
g.writeln('\treturn UnlockFileEx((HANDLE)handle, 0, low, high, &overlap) ? 0 : -1;')
3161+
g.writeln('}')
3162+
g.writeln('#else')
3163+
g.writeln('int v_filelock_lock(i32 fd, i32 exclusive, i32 immediate, u64 start, u64 len) {')
3164+
g.writeln('\tstruct flock fl;')
3165+
g.writeln('\tmemset(&fl, 0, sizeof(fl));')
3166+
g.writeln('\tfl.l_type = exclusive ? F_WRLCK : F_RDLCK;')
3167+
g.writeln('\tfl.l_whence = SEEK_SET;')
3168+
g.writeln('\tfl.l_start = (off_t)start;')
3169+
g.writeln('\tfl.l_len = len == 0 ? 0 : (off_t)len;')
3170+
g.writeln('\treturn fcntl(fd, immediate ? F_SETLK : F_SETLKW, &fl);')
3171+
g.writeln('}')
3172+
g.writeln('int v_filelock_unlock(i32 fd, u64 start, u64 len) {')
3173+
g.writeln('\tstruct flock fl;')
3174+
g.writeln('\tmemset(&fl, 0, sizeof(fl));')
3175+
g.writeln('\tfl.l_type = F_UNLCK;')
3176+
g.writeln('\tfl.l_whence = SEEK_SET;')
3177+
g.writeln('\tfl.l_start = (off_t)start;')
3178+
g.writeln('\tfl.l_len = len == 0 ? 0 : (off_t)len;')
3179+
g.writeln('\treturn fcntl(fd, F_SETLK, &fl);')
3180+
g.writeln('}')
3181+
g.writeln('#endif')
3182+
}
3183+
30673184
fn (mut g FlatGen) collect_fixed_array_typedefs_needed() map[string]FixedArrayTypedefInfo {
30683185
mut needed := map[string]FixedArrayTypedefInfo{}
30693186
old_module := g.tc.cur_module

0 commit comments

Comments
 (0)