// Copyright (c) 2026 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module cleanc import v2.ast import v2.parser import v2.pref as vpref import v2.token import v2.types import os const preamble_includes_freestanding = r'// Generated by V Clean C Backend #include #include #ifndef __TINYC__ #include #endif #include #include ' const preamble_includes_minimal = r'// Generated by V Clean C Backend #include #include #include #include #ifndef __TINYC__ #include #endif #include #include #include #if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) #include #include #include #endif #include ' const preamble_includes_minimal_windows = r'// Generated by V Clean C Backend #include #include #include #include #ifndef __TINYC__ #include #endif #include #include #include ' const preamble_includes_full = r'// Generated by V Clean C Backend #include #include #include #include #ifndef __TINYC__ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) #include #include #include #include #include #include #endif #include #include #include #ifndef PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP #define PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 0 #define pthread_rwlockattr_setkind_np(attr, kind) 0 #endif #include #include #include #include #include #if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) #include #include #endif extern char** environ; #define signal(v_sig, v_handler) signal((v_sig), ((void (*)(int))(v_handler))) #define v_signal_with_handler_cast(sig, handler) signal((sig), ((void (*)(int))(handler))) ' const preamble_includes_full_windows = r'// Generated by V Clean C Backend #include #include #include #include #ifndef __TINYC__ #include #endif #include #include #include #include #include #include #include #include #define signal(v_sig, v_handler) signal((v_sig), ((void (*)(int))(v_handler))) #define v_signal_with_handler_cast(sig, handler) signal((sig), ((void (*)(int))(handler))) ' const preamble_includes_minimal_cross = r'// Generated by V Clean C Backend #include #include #include #include #ifndef __TINYC__ #include #endif #include #include #if defined(_WIN32) #include #else #include #include #endif #if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) #include #include #include #endif ' const preamble_includes_full_cross = r'// Generated by V Clean C Backend #include #include #include #include #ifndef __TINYC__ #include #endif #include #include #include #include #include #include #include #if defined(_WIN32) #include #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP #define PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 0 #define pthread_rwlockattr_setkind_np(attr, kind) 0 #endif extern char** environ; #endif #if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) #include #include #include #include #include #include #include #include #endif #define signal(v_sig, v_handler) signal((v_sig), ((void (*)(int))(v_handler))) #define v_signal_with_handler_cast(sig, handler) signal((sig), ((void (*)(int))(handler))) ' const apple_minimal_includes_guarded = r'#if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) #include #include #include #endif ' const apple_minimal_includes_plain = r'#include #include #include ' const apple_full_includes_guarded = r'#if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) #include #include #include #include #include #include #endif ' const apple_full_includes_plain = r'#include #include #include #include #include #include ' const apple_late_full_includes_guarded = r'#if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) #include #include #endif ' 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' const freestanding_missing_format_hook_message = 'v2: freestanding target cannot print non-string values without formatting support' const freestanding_missing_heap_runtime_message = 'v2: freestanding target cannot use V runtime heap helpers with --skip-builtin' const freestanding_missing_output_hook_message = 'v2: freestanding target requires freestanding_output hook for output' const freestanding_missing_panic_hook_message = 'v2: freestanding target requires freestanding_panic hook for panic' fn (g &Gen) target_os_name() string { return normalize_c_target_os_name(g.pref.target_os_or_host()) } fn normalize_c_target_os_name(target_os string) string { return match target_os.to_lower() { 'darwin', 'mac' { 'macos' } else { target_os.to_lower() } } } fn (g &Gen) has_target_define(name string) bool { if g.pref == unsafe { nil } { return false } return vpref.comptime_optional_define_value(name, g.pref.user_defines, g.pref.explicit_user_defines) } fn (g &Gen) is_freestanding_target() bool { return g.pref != unsafe { nil } && g.pref.is_freestanding() } fn (g &Gen) has_freestanding_hook_capability(capability string) bool { if !g.is_freestanding_target() || g.pref == unsafe { nil } { return false } return g.pref.has_freestanding_hook(capability) } fn (g &Gen) has_freestanding_hooks() bool { return g.has_freestanding_hook_capability('output') || g.has_freestanding_hook_capability('panic') || g.has_freestanding_hook_capability('alloc') } fn (g &Gen) c_heap_malloc_call(size_expr string) string { if g.has_freestanding_hook_capability('alloc') { return 'v_platform_malloc(${size_expr})' } if g.is_freestanding_target() { return g.c_freestanding_missing_alloc_ptr_expr() } return 'malloc(${size_expr})' } fn (g &Gen) c_heap_realloc_call(ptr_expr string, size_expr string) string { if g.has_freestanding_hook_capability('alloc') { return 'v_platform_realloc(${ptr_expr}, ${size_expr})' } if g.is_freestanding_target() { return g.c_freestanding_missing_alloc_ptr_expr() } return 'realloc(${ptr_expr}, ${size_expr})' } fn (g &Gen) c_heap_free_call(ptr_expr string) string { if g.has_freestanding_hook_capability('alloc') { return 'v_platform_free(${ptr_expr})' } if g.is_freestanding_target() { return g.c_freestanding_missing_alloc_void_expr() } return 'free(${ptr_expr})' } fn (g &Gen) c_freestanding_missing_alloc_ptr_expr() string { return '({ _Static_assert(0, "${freestanding_missing_alloc_hook_message}"); (void*)0; })' } fn (g &Gen) c_freestanding_missing_alloc_void_expr() string { return '({ _Static_assert(0, "${freestanding_missing_alloc_hook_message}"); (void)0; })' } fn (g &Gen) c_freestanding_missing_format_string_expr() string { return '({ _Static_assert(0, "${freestanding_missing_format_hook_message}"); (string){0}; })' } fn is_target_os_ct_flag(name string) bool { return name in [ 'darwin', 'macos', 'mac', 'linux', 'windows', 'bsd', 'freebsd', 'openbsd', 'netbsd', 'dragonfly', 'android', 'termux', 'ios', 'solaris', 'qnx', 'serenity', 'plan9', 'vinix', ] } fn c_preprocessor_flag_expr_for_ct_flag(name string) ?string { return match name.to_lower() { 'linux' { 'defined(__linux__)' } 'windows' { 'defined(_WIN32)' } 'darwin', 'macos', 'mac' { apple_macos_cross_guard } 'bsd' { '(${apple_macos_cross_guard}) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)' } 'freebsd' { 'defined(__FreeBSD__)' } 'openbsd' { 'defined(__OpenBSD__)' } 'netbsd' { 'defined(__NetBSD__)' } 'dragonfly' { 'defined(__DragonFly__)' } 'android' { 'defined(__ANDROID__)' } 'termux' { 'defined(__TERMUX__)' } 'ios' { apple_ios_cross_guard } 'solaris' { 'defined(__sun)' } 'qnx' { 'defined(__QNX__)' } 'serenity' { 'defined(__serenity__)' } 'plan9' { 'defined(__plan9__)' } 'vinix' { 'defined(__vinix__)' } else { none } } } fn optional_user_ct_flag_name(cond string) ?string { trimmed := cond.trim_space() if !trimmed.ends_with('?') { return none } name := trimmed[..trimmed.len - 1].trim_space() if name == '' { return none } return name } fn (g &Gen) c_preprocessor_expr_for_ct_cond(cond string) ?string { trimmed := cond.trim_space() if trimmed == '' { return none } if or_idx := top_level_bool_op_index(trimmed, '||') { left := g.c_preprocessor_expr_for_ct_cond(trimmed[..or_idx].trim_space()) or { return none } right := g.c_preprocessor_expr_for_ct_cond(trimmed[or_idx + 2..].trim_space()) or { return none } return combine_c_preprocessor_or_expr(left, right) } if and_idx := top_level_bool_op_index(trimmed, '&&') { left := g.c_preprocessor_expr_for_ct_cond(trimmed[..and_idx].trim_space()) or { return none } right := g.c_preprocessor_expr_for_ct_cond(trimmed[and_idx + 2..].trim_space()) or { return none } return combine_c_preprocessor_and_expr(left, right) } if trimmed.starts_with('!') { inner := g.c_preprocessor_expr_for_ct_cond(trimmed[1..]) or { return none } return negate_c_preprocessor_expr(inner) } if stripped := strip_outer_bool_parens(trimmed) { return g.c_preprocessor_expr_for_ct_cond(stripped) } if optional_name := optional_user_ct_flag_name(trimmed) { return if g.has_target_define(optional_name) { '' } else { '0' } } lower_trimmed := trimmed.to_lower() if os_guard := c_preprocessor_flag_expr_for_ct_flag(lower_trimmed) { return os_guard } if g.directive_ct_flag_matches(lower_trimmed) { return '' } return '0' } fn combine_c_preprocessor_and_expr(left string, right string) string { if left == '0' || right == '0' { return '0' } if left == '' { return right } if right == '' { return left } return '(${left}) && (${right})' } fn combine_c_preprocessor_or_expr(left string, right string) string { if left == '' || right == '' { return '' } if left == '0' { return right } if right == '0' { return left } return '(${left}) || (${right})' } fn negate_c_preprocessor_expr(expr string) string { if expr == '' { return '0' } if expr == '0' { return '' } return '!(${expr})' } fn (g &Gen) preamble_includes(minimal_preamble bool) string { if g.is_freestanding_target() { return preamble_includes_freestanding } target_os := g.target_os_name() if target_os == 'cross' { return if minimal_preamble { preamble_includes_minimal_cross } else { preamble_includes_full_cross } } if target_os == 'windows' { return if minimal_preamble { preamble_includes_minimal_windows } else { preamble_includes_full_windows } } raw := if minimal_preamble { preamble_includes_minimal } else { preamble_includes_full } if target_os == '' { 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) out = out.replace(apple_late_full_includes_guarded, apple_late_full_includes_plain) } else { out = out.replace(apple_minimal_includes_guarded, '') out = out.replace(apple_full_includes_guarded, '') out = out.replace(apple_late_full_includes_guarded, '') } 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) { return true } if vpref.comptime_flag_value(g.pref, lower_name) { return true } target_os := g.target_os_name() return match lower_name { 'mac' { target_os == 'macos' } 'android' { target_os == 'android' } 'ios' { target_os == 'ios' } 'cross' { target_os == 'cross' } 'freestanding', 'bare' { g.is_freestanding_target() } else { false } } } fn (g &Gen) directive_ct_cond_matches(cond string) bool { if cond == '' { return true } trimmed := cond.trim_space() if g.target_os_name() == 'cross' && g.c_preprocessor_expr_for_ct_cond(trimmed) != none { return true } if or_idx := top_level_bool_op_index(trimmed, '||') { left := trimmed[..or_idx].trim_space() right := trimmed[or_idx + 2..].trim_space() return g.directive_ct_cond_matches(left) || g.directive_ct_cond_matches(right) } if and_idx := top_level_bool_op_index(trimmed, '&&') { left := trimmed[..and_idx].trim_space() right := trimmed[and_idx + 2..].trim_space() return g.directive_ct_cond_matches(left) && g.directive_ct_cond_matches(right) } if trimmed.starts_with('!') { return !g.directive_ct_cond_matches(trimmed[1..]) } if stripped := strip_outer_bool_parens(trimmed) { return g.directive_ct_cond_matches(stripped) } if optional_name := optional_user_ct_flag_name(trimmed) { return g.has_target_define(optional_name) } return g.directive_ct_flag_matches(trimmed) } fn top_level_bool_op_index(expr string, op string) ?int { mut depth := 0 mut i := 0 for i < expr.len { ch := expr[i] if ch == `(` { depth++ } else if ch == `)` { if depth > 0 { depth-- } } else if depth == 0 && i + op.len <= expr.len && expr[i..i + op.len] == op { return i } i++ } return none } fn strip_outer_bool_parens(expr string) ?string { if expr.len < 2 || expr[0] != `(` || expr[expr.len - 1] != `)` { return none } mut depth := 0 for i, ch in expr { if ch == `(` { depth++ } else if ch == `)` { depth-- if depth == 0 && i < expr.len - 1 { return none } } } if depth == 0 { return expr[1..expr.len - 1].trim_space() } return none } fn combine_ct_conditions(outer string, inner string) string { if outer == '' { return inner } if inner == '' { return outer } return '(${outer}) && (${inner})' } fn ast_ct_cond_string(expr ast.Expr) ?string { return match expr { ast.Ident { expr.name } ast.PrefixExpr { if expr.op == .not { inner := ast_ct_cond_string(expr.expr) or { return none } '!(${inner})' } else { none } } ast.InfixExpr { left := ast_ct_cond_string(expr.lhs) or { return none } right := ast_ct_cond_string(expr.rhs) or { return none } if expr.op == .and { '(${left}) && (${right})' } else if expr.op == .logical_or { '(${left}) || (${right})' } else { none } } ast.PostfixExpr { if expr.op == .question && expr.expr is ast.Ident { '${expr.expr.name}?' } else { none } } ast.ParenExpr { inner := ast_ct_cond_string(expr.expr) or { return none } '(${inner})' } else { none } } } fn ast_ct_cond_string_cursor(expr ast.Cursor) ?string { if !expr.is_valid() { return none } return match expr.kind() { .expr_ident { expr.name() } .expr_comptime { ast_ct_cond_string_cursor(expr.edge(0)) or { return none } } .expr_prefix { if unsafe { token.Token(int(expr.aux())) } == .not { inner := ast_ct_cond_string_cursor(expr.edge(0)) or { return none } '!(${inner})' } else { none } } .expr_infix { left := ast_ct_cond_string_cursor(expr.edge(0)) or { return none } right := ast_ct_cond_string_cursor(expr.edge(1)) or { return none } op := unsafe { token.Token(int(expr.aux())) } if op == .and { '(${left}) && (${right})' } else if op == .logical_or { '(${left}) || (${right})' } else { none } } .expr_postfix { if unsafe { token.Token(int(expr.aux())) } == .question && expr.edge(0).kind() == .expr_ident { '${expr.edge(0).name()}?' } else { none } } .expr_paren { inner := ast_ct_cond_string_cursor(expr.edge(0)) or { return none } '(${inner})' } else { none } } } fn (g &Gen) cross_directive_guard(cond string) ?string { if g.target_os_name() != 'cross' { return none } return g.c_preprocessor_expr_for_ct_cond(cond) } fn find_vmod_root_for_file(file_path string) string { mut dir := os.dir(file_path) for _ in 0 .. 12 { if os.exists(os.join_path(dir, 'v.mod')) { return dir } parent := os.dir(dir) if parent == dir || parent == '' { break } dir = parent } return os.dir(file_path) } fn normalize_c_directive_value(name string, raw string, file_name string, vroot string) string { mut value := raw.trim_space() if value.ends_with(';') { value = value[..value.len - 1].trim_space() } if value.contains('@VMODROOT') { value = value.replace('@VMODROOT', find_vmod_root_for_file(file_name)) } if value.contains('@VEXEROOT') { value = value.replace('@VEXEROOT', vroot) } if name == 'include' { if value == '' { return value } mut had_trailing_angle := false if value.ends_with('>') && !value.starts_with('<') && !value.starts_with('"') { value = value[..value.len - 1].trim_space() had_trailing_angle = true } if !value.starts_with('<') && !value.starts_with('"') { if value.starts_with('/') || value.starts_with('./') || value.starts_with('../') { value = '"${value}"' } else if had_trailing_angle { value = '<${value}>' } else if value.contains('/') { value = '<${value}>' } else if value.ends_with('.h') || value.ends_with('.hh') || value.ends_with('.hpp') { value = '"${value}"' } else if value.ends_with('>') { value = '<${value}' } else { value = '<${value}>' } } } return value } fn (mut g Gen) emit_collected_c_directives() { mut seen := map[string]bool{} mut cross_source_paths := map[string]bool{} if g.target_os_name() == 'cross' { cross_source_paths = g.collect_cross_directives_from_original_sources(mut seen) } if g.has_flat() { for i in 0 .. g.flat.files.len { fc := g.flat.file_cursor(i) file_name := fc.name() if file_name in cross_source_paths { continue } g.set_file_cursor_module(fc) emit_implementation_directives := g.should_emit_current_file() g.collect_directives_from_cursor_stmts(fc.stmts(), file_name, emit_implementation_directives, mut seen) } return } for file in g.files { if file.name in cross_source_paths { continue } g.set_file_module(file) emit_implementation_directives := g.should_emit_current_file() g.collect_directives_from_stmts(file.stmts, file.name, emit_implementation_directives, mut seen) } } fn (mut g Gen) collect_cross_directives_from_original_sources(mut seen map[string]bool) map[string]bool { // Cross mode reparses only the files that survived normal source filtering. // This recovers C directives from $if OS branches before transform strips // them, but it does not provide a second OS-specific function-body pipeline. mut parsed_paths := map[string]bool{} mut file_set := token.FileSet.new() mut par := parser.Parser.new(g.pref) if g.has_flat() { for i in 0 .. g.flat.files.len { fc := g.flat.file_cursor(i) source_path := fc.name() if source_path == '' || source_path in parsed_paths || !os.exists(source_path) { continue } parsed_paths[source_path] = true source_files := par.parse_files([source_path], mut file_set) for source_file in source_files { g.set_file_module(source_file) emit_implementation_directives := g.should_emit_current_file() g.collect_directives_from_stmts(source_file.stmts, source_file.name, emit_implementation_directives, mut seen) } } return parsed_paths } for file in g.files { source_path := file.name if source_path == '' || source_path in parsed_paths || !os.exists(source_path) { continue } parsed_paths[source_path] = true source_files := par.parse_files([source_path], mut file_set) for source_file in source_files { g.set_file_module(source_file) emit_implementation_directives := g.should_emit_current_file() g.collect_directives_from_stmts(source_file.stmts, source_file.name, emit_implementation_directives, mut seen) } } return parsed_paths } fn (mut g Gen) collect_directives_from_stmts(stmts []ast.Stmt, file_name string, emit_implementation_directives bool, mut seen map[string]bool) { g.collect_directives_from_stmts_with_ct_cond(stmts, file_name, emit_implementation_directives, '', mut seen) } fn (mut g Gen) collect_directives_from_cursor_stmts(stmts ast.CursorList, file_name string, emit_implementation_directives bool, mut seen map[string]bool) { g.collect_directives_from_cursor_stmts_with_ct_cond(stmts, file_name, emit_implementation_directives, '', mut seen) } fn (mut g Gen) collect_directives_from_cursor_stmts_with_ct_cond(stmts ast.CursorList, file_name string, emit_implementation_directives bool, outer_ct_cond string, mut seen map[string]bool) { g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(stmts, file_name, emit_implementation_directives, outer_ct_cond, '', mut seen) } fn (mut g Gen) collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(stmts ast.CursorList, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { if outer_ct_cond != '' && g.target_os_name() == 'cross' { if guard := g.cross_directive_guard(outer_ct_cond) { if guard == '0' { return } if guard != '' { g.sb.writeln('#if ${guard}') g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(stmts, file_name, emit_implementation_directives, '', combine_ct_conditions(active_seen_guard, guard), mut seen) g.sb.writeln('#endif') return } } } for i in 0 .. stmts.len() { stmt := stmts.at(i) match stmt.kind() { .stmt_directive { g.emit_directive_cursor_with_outer_ct_cond_and_seen_guard(stmt, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } .stmt_expr { g.collect_directives_from_expr_cursor_with_ct_cond_and_seen_guard(stmt.edge(0), file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } else {} } } } fn (mut g Gen) collect_directives_from_stmts_with_ct_cond(stmts []ast.Stmt, file_name string, emit_implementation_directives bool, outer_ct_cond string, mut seen map[string]bool) { g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(stmts, file_name, emit_implementation_directives, outer_ct_cond, '', mut seen) } fn (mut g Gen) collect_directives_from_stmts_with_ct_cond_and_seen_guard(stmts []ast.Stmt, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { if outer_ct_cond != '' && g.target_os_name() == 'cross' { if guard := g.cross_directive_guard(outer_ct_cond) { if guard == '0' { return } if guard != '' { g.sb.writeln('#if ${guard}') g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(stmts, file_name, emit_implementation_directives, '', combine_ct_conditions(active_seen_guard, guard), mut seen) g.sb.writeln('#endif') return } } } for stmt in stmts { if stmt is ast.Directive { g.emit_directive_with_outer_ct_cond_and_seen_guard(stmt, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } else if stmt is ast.ExprStmt { g.collect_directives_from_expr_with_ct_cond_and_seen_guard(stmt.expr, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } } } fn (mut g Gen) collect_directives_from_expr(expr ast.Expr, file_name string, emit_implementation_directives bool, mut seen map[string]bool) { g.collect_directives_from_expr_with_ct_cond(expr, file_name, emit_implementation_directives, '', mut seen) } fn (mut g Gen) collect_directives_from_expr_with_ct_cond(expr ast.Expr, file_name string, emit_implementation_directives bool, outer_ct_cond string, mut seen map[string]bool) { g.collect_directives_from_expr_with_ct_cond_and_seen_guard(expr, file_name, emit_implementation_directives, outer_ct_cond, '', mut seen) } fn (mut g Gen) collect_directives_from_expr_with_ct_cond_and_seen_guard(expr ast.Expr, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { if expr is ast.ComptimeExpr { if expr.expr is ast.IfExpr { g.collect_directives_from_active_comptime_if(expr.expr, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } else { g.collect_directives_from_expr_with_ct_cond_and_seen_guard(expr.expr, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } } else if expr is ast.IfExpr { g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(expr.stmts, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) if expr.else_expr !is ast.EmptyExpr { g.collect_directives_from_expr_with_ct_cond_and_seen_guard(expr.else_expr, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } } } fn (mut g Gen) collect_directives_from_expr_cursor_with_ct_cond_and_seen_guard(expr ast.Cursor, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { if !expr.is_valid() { return } if expr.kind() == .expr_comptime { inner := expr.edge(0) if inner.kind() == .expr_if { g.collect_directives_from_active_comptime_if_cursor(inner, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } else { g.collect_directives_from_expr_cursor_with_ct_cond_and_seen_guard(inner, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } return } if expr.kind() == .expr_if { g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(if_expr_body_cursor_list(expr), file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) else_expr := expr.edge(1) if else_expr.is_valid() && else_expr.kind() != .expr_empty { g.collect_directives_from_expr_cursor_with_ct_cond_and_seen_guard(else_expr, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } } } fn (mut g Gen) emit_directive_with_outer_ct_cond(stmt ast.Directive, file_name string, emit_implementation_directives bool, outer_ct_cond string, mut seen map[string]bool) { g.emit_directive_with_outer_ct_cond_and_seen_guard(stmt, file_name, emit_implementation_directives, outer_ct_cond, '', mut seen) } fn (mut g Gen) emit_directive_with_outer_ct_cond_and_seen_guard(stmt ast.Directive, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { if outer_ct_cond == '' { g.emit_directive_with_seen_guard(stmt, file_name, emit_implementation_directives, active_seen_guard, mut seen) return } g.emit_directive_fields_with_seen_guard(stmt.name, stmt.value, combine_ct_conditions(outer_ct_cond, stmt.ct_cond), file_name, emit_implementation_directives, active_seen_guard, mut seen) } fn (mut g Gen) emit_directive_cursor_with_outer_ct_cond_and_seen_guard(stmt ast.Cursor, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { if !stmt.is_valid() || stmt.kind() != .stmt_directive { return } ct_cond := if stmt.edge_count() > 0 { stmt.edge(0).name() } else { '' } resolved_ct_cond := if outer_ct_cond == '' { ct_cond } else { combine_ct_conditions(outer_ct_cond, ct_cond) } g.emit_directive_fields_with_seen_guard(stmt.name(), stmt.extra_str(), resolved_ct_cond, file_name, emit_implementation_directives, active_seen_guard, mut seen) } fn (mut g Gen) collect_directives_from_active_comptime_if(expr ast.IfExpr, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { if g.target_os_name() == 'cross' { if cond := ast_ct_cond_string(expr.cond) { if g.c_preprocessor_expr_for_ct_cond(cond) != none { g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(expr.stmts, file_name, emit_implementation_directives, combine_ct_conditions(outer_ct_cond, cond), active_seen_guard, mut seen) next_outer_cond := combine_ct_conditions(outer_ct_cond, '!(${cond})') if expr.else_expr is ast.IfExpr { if expr.else_expr.cond is ast.EmptyExpr { g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(expr.else_expr.stmts, file_name, emit_implementation_directives, next_outer_cond, active_seen_guard, mut seen) } else { g.collect_directives_from_active_comptime_if(expr.else_expr, file_name, emit_implementation_directives, next_outer_cond, active_seen_guard, mut seen) } } else if expr.else_expr !is ast.EmptyExpr { g.collect_directives_from_expr_with_ct_cond_and_seen_guard(expr.else_expr, file_name, emit_implementation_directives, next_outer_cond, active_seen_guard, mut seen) } return } } } if g.ast_comptime_simple_cond_matches(expr.cond) { g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(expr.stmts, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) return } if expr.else_expr is ast.IfExpr { if expr.else_expr.cond is ast.EmptyExpr { g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(expr.else_expr.stmts, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } else { g.collect_directives_from_active_comptime_if(expr.else_expr, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } } else if expr.else_expr !is ast.EmptyExpr { g.collect_directives_from_expr_with_ct_cond_and_seen_guard(expr.else_expr, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } } fn (mut g Gen) collect_directives_from_active_comptime_if_cursor(expr ast.Cursor, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { if !expr.is_valid() || expr.kind() != .expr_if { return } cond_expr := expr.edge(0) if g.target_os_name() == 'cross' { if cond := ast_ct_cond_string_cursor(cond_expr) { if g.c_preprocessor_expr_for_ct_cond(cond) != none { g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(if_expr_body_cursor_list(expr), file_name, emit_implementation_directives, combine_ct_conditions(outer_ct_cond, cond), active_seen_guard, mut seen) next_outer_cond := combine_ct_conditions(outer_ct_cond, '!(${cond})') else_expr := expr.edge(1) if else_expr.kind() == .expr_if { if else_expr.edge(0).kind() == .expr_empty { g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(if_expr_body_cursor_list(else_expr), file_name, emit_implementation_directives, next_outer_cond, active_seen_guard, mut seen) } else { g.collect_directives_from_active_comptime_if_cursor(else_expr, file_name, emit_implementation_directives, next_outer_cond, active_seen_guard, mut seen) } } else if else_expr.is_valid() && else_expr.kind() != .expr_empty { g.collect_directives_from_expr_cursor_with_ct_cond_and_seen_guard(else_expr, file_name, emit_implementation_directives, next_outer_cond, active_seen_guard, mut seen) } return } } } if g.ast_comptime_simple_cond_matches_cursor(cond_expr) { g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(if_expr_body_cursor_list(expr), file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) return } else_expr := expr.edge(1) if else_expr.kind() == .expr_if { if else_expr.edge(0).kind() == .expr_empty { g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(if_expr_body_cursor_list(else_expr), file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } else { g.collect_directives_from_active_comptime_if_cursor(else_expr, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } } else if else_expr.is_valid() && else_expr.kind() != .expr_empty { g.collect_directives_from_expr_cursor_with_ct_cond_and_seen_guard(else_expr, file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) } } fn if_expr_body_cursor_list(expr ast.Cursor) ast.CursorList { return ast.CursorList{ flat: expr.flat parent_id: expr.id offset: 2 } } fn (g &Gen) ast_comptime_simple_cond_matches(expr ast.Expr) bool { if expr is ast.Ident { return g.ast_comptime_flag_matches(expr.name) } if expr is ast.ComptimeExpr { return g.ast_comptime_simple_cond_matches(expr.expr) } if expr is ast.CallExpr { if pkg_name := cleanc_pkgconfig_call_name(expr) { return vpref.comptime_pkgconfig_value(pkg_name) } } if expr is ast.CallOrCastExpr { if pkg_name := cleanc_pkgconfig_call_name(expr) { return vpref.comptime_pkgconfig_value(pkg_name) } } if expr is ast.PrefixExpr { if expr.op == .not { return !g.ast_comptime_simple_cond_matches(expr.expr) } return false } if expr is ast.InfixExpr { if expr.op == .and { return g.ast_comptime_simple_cond_matches(expr.lhs) && g.ast_comptime_simple_cond_matches(expr.rhs) } if expr.op == .logical_or { return g.ast_comptime_simple_cond_matches(expr.lhs) || g.ast_comptime_simple_cond_matches(expr.rhs) } return false } if expr is ast.PostfixExpr { if expr.op == .question && expr.expr is ast.Ident { return g.has_target_define(expr.expr.name) } return false } if expr is ast.ParenExpr { return g.ast_comptime_simple_cond_matches(expr.expr) } return false } fn (g &Gen) ast_comptime_simple_cond_matches_cursor(expr ast.Cursor) bool { if !expr.is_valid() { return false } match expr.kind() { .expr_ident { return g.ast_comptime_flag_matches(expr.name()) } .expr_comptime, .expr_paren { return g.ast_comptime_simple_cond_matches_cursor(expr.edge(0)) } .expr_call, .expr_call_or_cast { if pkg_name := cleanc_pkgconfig_call_name_cursor(expr) { return vpref.comptime_pkgconfig_value(pkg_name) } } .expr_prefix { if unsafe { token.Token(int(expr.aux())) } == .not { return !g.ast_comptime_simple_cond_matches_cursor(expr.edge(0)) } } .expr_infix { op := unsafe { token.Token(int(expr.aux())) } if op == .and { return g.ast_comptime_simple_cond_matches_cursor(expr.edge(0)) && g.ast_comptime_simple_cond_matches_cursor(expr.edge(1)) } if op == .logical_or { return g.ast_comptime_simple_cond_matches_cursor(expr.edge(0)) || g.ast_comptime_simple_cond_matches_cursor(expr.edge(1)) } } .expr_postfix { if unsafe { token.Token(int(expr.aux())) } == .question && expr.edge(0).kind() == .expr_ident { return g.has_target_define(expr.edge(0).name()) } } else {} } return false } fn (g &Gen) ast_comptime_flag_matches(name string) bool { lower_name := name.to_lower() if g.pref == unsafe { nil } { return false } match lower_name { 'macos', 'darwin', 'mac', 'linux', 'windows', 'bsd', 'freebsd', 'openbsd', 'netbsd', 'dragonfly', 'android', 'termux', 'ios', 'solaris', 'qnx', 'serenity', 'plan9', 'vinix', 'none' { return vpref.comptime_flag_value(g.pref, lower_name) } 'cross', 'freestanding', 'bare' { return vpref.comptime_flag_value(g.pref, lower_name) || vpref.define_list_contains(g.pref.user_defines, lower_name) } else {} } return vpref.define_list_contains(g.pref.user_defines, lower_name) } fn cleanc_pkgconfig_call_name(expr ast.Expr) ?string { match expr { ast.CallExpr { if expr.lhs is ast.Ident && expr.lhs.name == 'pkgconfig' && expr.args.len == 1 { return cleanc_string_literal_value(expr.args[0]) } } ast.CallOrCastExpr { if expr.lhs is ast.Ident && expr.lhs.name == 'pkgconfig' { return cleanc_string_literal_value(expr.expr) } } else {} } return none } fn cleanc_pkgconfig_call_name_cursor(expr ast.Cursor) ?string { if !expr.is_valid() { return none } if expr.kind() == .expr_call { lhs := expr.edge(0) if lhs.kind() == .expr_ident && lhs.name() == 'pkgconfig' && expr.edge_count() == 2 { return cleanc_string_literal_value_cursor(expr.edge(1)) } } else if expr.kind() == .expr_call_or_cast { lhs := expr.edge(0) if lhs.kind() == .expr_ident && lhs.name() == 'pkgconfig' { return cleanc_string_literal_value_cursor(expr.edge(1)) } } return none } fn cleanc_string_literal_value(expr ast.Expr) ?string { if expr is ast.StringLiteral { return cleanc_unquote_string_literal_value(expr.value) } return none } fn cleanc_string_literal_value_cursor(expr ast.Cursor) ?string { if !expr.is_valid() || (expr.kind() != .expr_string && expr.kind() != .expr_basic_literal) { return none } return cleanc_unquote_string_literal_value(expr.name()) } fn cleanc_unquote_string_literal_value(value string) string { if value.len >= 2 && ((value[0] == `"` && value[value.len - 1] == `"`) || (value[0] == `'` && value[value.len - 1] == `'`)) { return value[1..value.len - 1] } return value } fn c_directive_emits_linked_symbols(value string) bool { lower_value := value.trim_space().to_lower() for marker in ['.c"', ".c'", '.c>', '.m"', ".m'", '.m>'] { if lower_value.contains(marker) { return true } } return lower_value.contains('"miniz.h"') || lower_value.contains('') || lower_value.contains('/miniz.h"') || lower_value.contains('/miniz.h>') } fn c_define_enables_linked_symbols(value string) bool { upper_value := value.trim_space().to_upper() return upper_value.contains('IMPLEMENTATION') } fn (mut g Gen) emit_directive(stmt ast.Directive, file_name string, emit_implementation_directives bool, mut seen map[string]bool) { g.emit_directive_with_seen_guard(stmt, file_name, emit_implementation_directives, '', mut seen) } fn (mut g Gen) emit_directive_with_seen_guard(stmt ast.Directive, file_name string, emit_implementation_directives bool, active_seen_guard string, mut seen map[string]bool) { g.emit_directive_fields_with_seen_guard(stmt.name, stmt.value, stmt.ct_cond, file_name, emit_implementation_directives, active_seen_guard, mut seen) } fn (mut g Gen) emit_directive_fields_with_seen_guard(raw_name string, raw_value string, ct_cond string, file_name string, emit_implementation_directives bool, active_seen_guard string, mut seen map[string]bool) { name := raw_name.trim_space() if name == '' || name == 'flag' { return } if !g.directive_ct_cond_matches(ct_cond) { return } if name !in ['include', 'define', 'undef', 'ifdef', 'ifndef', 'if', 'elif', 'else', 'endif', 'pragma', 'insert'] { return } // #insert is V's directive to inline a file; for C backend, emit as #include. emit_name := if name == 'insert' { 'include' } else { name } // Skip includes with corrupt paths (ARM64 backend may corrupt string data) if emit_name == 'include' { v := raw_value.trim_space() if v.len > 0 && !v.contains('/') && !v.contains('.') && !v.starts_with('"') && !v.starts_with('<') { return } } vroot := if g.pref != unsafe { nil } { g.pref.vroot } else { '' } value := normalize_c_directive_value(emit_name, raw_value, file_name, vroot) if !emit_implementation_directives && emit_name == 'include' && c_directive_emits_linked_symbols(value) { return } if !emit_implementation_directives && emit_name == 'define' && c_define_enables_linked_symbols(value) { return } line := if value == '' { '#${emit_name}' } else { '#${emit_name} ${value}' } guard := g.cross_directive_guard(ct_cond) or { '' } if guard == '0' { return } mut seen_key := if guard == '' { line } else { '#if ${guard}\n${line}' } if active_seen_guard != '' { seen_key = '#if ${active_seen_guard}\n${seen_key}' } deduplicate := emit_name !in ['if', 'ifdef', 'ifndef', 'elif', 'else', 'endif'] if deduplicate { if seen_key in seen { return } seen[seen_key] = true } if emit_name == 'include' && value.contains('/thirdparty/stdatomic/nix/atomic.h') { if guard != '' { g.sb.writeln('#if ${guard}') } g.sb.writeln('#if defined(__TINYC__) && defined(__APPLE__) && defined(__aarch64__)') g.sb.writeln('#define extern static') g.sb.writeln('#endif') g.sb.writeln(line) g.sb.writeln('#if defined(__TINYC__) && defined(__APPLE__) && defined(__aarch64__)') g.sb.writeln('#undef extern') g.sb.writeln('#endif') if guard != '' { g.sb.writeln('#endif') } return } // Defer .m (Objective-C) includes until after type definitions, // because they reference V-generated C types like gg__Color, string, etc. if emit_name == 'include' && (value.contains('.m"') || value.contains(".m'")) { deferred_guard := combine_ct_conditions(active_seen_guard, guard) g.deferred_m_includes << if deferred_guard == '' { line } else { '#if ${deferred_guard}\n${line}\n#endif' } return } if guard != '' { g.sb.writeln('#if ${guard}') g.sb.writeln(line) g.sb.writeln('#endif') return } g.sb.writeln(line) } fn (mut g Gen) emit_deferred_m_includes() { // Skip .m includes when compiling as shared library (avoids duplicate ObjC classes) if g.pref != unsafe { nil } && g.pref.is_shared_lib { return } for line in g.deferred_m_includes { g.sb.writeln(line) } } fn (mut g Gen) emit_vec_simd_fallback_aliases() { g.sb.writeln('#if defined(__clang__)') g.sb.writeln('typedef float vec__SimdFloat2 __attribute__((ext_vector_type(2)));') g.sb.writeln('typedef float vec__SimdFloat4 __attribute__((ext_vector_type(4)));') g.sb.writeln('typedef int vec__SimdInt4 __attribute__((ext_vector_type(4)));') g.sb.writeln('typedef unsigned int vec__SimdU32_4 __attribute__((ext_vector_type(4)));') g.sb.writeln('#elif defined(__GNUC__)') g.sb.writeln('typedef float vec__SimdFloat2 __attribute__((vector_size(8)));') g.sb.writeln('typedef float vec__SimdFloat4 __attribute__((vector_size(16)));') g.sb.writeln('typedef int vec__SimdInt4 __attribute__((vector_size(16)));') g.sb.writeln('typedef unsigned int vec__SimdU32_4 __attribute__((vector_size(16)));') g.sb.writeln('#else') g.sb.writeln('typedef struct vec__SimdFloat2 { f32 x; f32 y; } vec__SimdFloat2;') g.sb.writeln('typedef struct vec__SimdFloat4 { f32 x; f32 y; f32 z; f32 w; } vec__SimdFloat4;') g.sb.writeln('typedef struct vec__SimdInt4 { i32 x; i32 y; i32 z; i32 w; } vec__SimdInt4;') g.sb.writeln('typedef struct vec__SimdU32_4 { u32 x; u32 y; u32 z; u32 w; } vec__SimdU32_4;') g.sb.writeln('#endif') } fn (mut g Gen) emit_tinyc_arm_cpu_relax_fallback() { g.sb.writeln('#if defined(__TINYC__) && (defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM))') g.sb.writeln('#ifdef cpu_relax') g.sb.writeln('#undef cpu_relax') g.sb.writeln('#endif') g.sb.writeln('#define cpu_relax() ((void)0)') g.sb.writeln('#endif') } fn (mut g Gen) emit_stdatomic_compat_include() { if g.pref == unsafe { nil } || g.pref.vroot.len == 0 { return } dir_name := if g.target_os_name() == 'windows' { 'win' } else { 'nix' } header_path := os.join_path(g.pref.vroot, 'thirdparty', 'stdatomic', dir_name, 'atomic.h') if !os.exists(header_path) { return } include_path := header_path.replace('\\', '/') // The `extern -> static` hack only matters for tcc on Apple arm64; keep // the __APPLE__ guard out of non-macos target preambles (they are // asserted apple-free by target_codegen_test). apple_target := g.target_os_name() == 'macos' if apple_target { g.sb.writeln('#if defined(__TINYC__) && defined(__APPLE__) && defined(__aarch64__)') g.sb.writeln('#define extern static') g.sb.writeln('#endif') } g.sb.writeln('#include "${include_path}"') if apple_target { g.sb.writeln('#if defined(__TINYC__) && defined(__APPLE__) && defined(__aarch64__)') g.sb.writeln('#undef extern') g.sb.writeln('#endif') } } fn (mut g Gen) emit_v_architecture_macros() { g.sb.writeln('#if defined(__x86_64__) || defined(_M_AMD64)') g.sb.writeln('#define __V_amd64 1') g.sb.writeln('#define __V_architecture 1') g.sb.writeln('#elif defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64)') g.sb.writeln('#define __V_arm64 1') g.sb.writeln('#define __V_architecture 2') g.sb.writeln('#elif defined(__arm__) || defined(_M_ARM)') g.sb.writeln('#define __V_arm32 1') g.sb.writeln('#define __V_architecture 3') g.sb.writeln('#elif defined(__riscv) && __riscv_xlen == 64') g.sb.writeln('#define __V_rv64 1') g.sb.writeln('#define __V_architecture 4') g.sb.writeln('#elif defined(__riscv) && __riscv_xlen == 32') g.sb.writeln('#define __V_rv32 1') g.sb.writeln('#define __V_architecture 5') g.sb.writeln('#elif defined(__i386__) || defined(_M_IX86)') g.sb.writeln('#define __V_x86 1') g.sb.writeln('#define __V_architecture 6') g.sb.writeln('#elif defined(__s390x__)') g.sb.writeln('#define __V_s390x 1') g.sb.writeln('#define __V_architecture 7') g.sb.writeln('#elif defined(__powerpc64__) && defined(__LITTLE_ENDIAN__)') g.sb.writeln('#define __V_ppc64le 1') g.sb.writeln('#define __V_architecture 8') g.sb.writeln('#elif defined(__loongarch64)') g.sb.writeln('#define __V_loongarch64 1') g.sb.writeln('#define __V_architecture 9') g.sb.writeln('#elif defined(__sparc__)') g.sb.writeln('#define __V_sparc64 1') g.sb.writeln('#define __V_architecture 10') g.sb.writeln('#elif defined(__powerpc64__) && defined(__BIG_ENDIAN__)') g.sb.writeln('#define __V_ppc64 1') g.sb.writeln('#define __V_architecture 11') g.sb.writeln('#elif (defined(__powerpc__) || defined(__powerpc) || defined(__POWERPC__) || defined(__ppc__) || defined(__ppc) || defined(__PPC__)) && !defined(__powerpc64__) && !defined(__ppc64__) && !defined(__PPC64__)') g.sb.writeln('#define __V_ppc 1') g.sb.writeln('#define __V_architecture 12') g.sb.writeln('#else') g.sb.writeln('#define __V_architecture 0') g.sb.writeln('#endif') } fn (mut g Gen) emit_v_commit_hash_fallback() { g.sb.writeln('#ifndef V_COMMIT_HASH') g.sb.writeln('#define V_COMMIT_HASH "@@@"') g.sb.writeln('#endif') } fn (mut g Gen) emit_stub_rwmutex() { g.sb.writeln('typedef struct sync__RwMutex { u32 inited; } sync__RwMutex;') g.sb.writeln('static inline void sync__RwMutex_rlock(sync__RwMutex* m) { (void)m; }') g.sb.writeln('static inline void sync__RwMutex_runlock(sync__RwMutex* m) { (void)m; }') g.sb.writeln('static inline void sync__RwMutex_lock(sync__RwMutex* m) { (void)m; }') g.sb.writeln('static inline void sync__RwMutex_unlock(sync__RwMutex* m) { (void)m; }') g.emitted_types['body_sync__RwMutex'] = true } fn (mut g Gen) emit_pthread_rwmutex() { g.sb.writeln('typedef struct sync__RwMutex { pthread_rwlock_t mutex; u32 inited; } sync__RwMutex;') g.sb.writeln('static inline void sync__RwMutex_rlock(sync__RwMutex* m) { pthread_rwlock_rdlock(&m->mutex); }') g.sb.writeln('static inline void sync__RwMutex_runlock(sync__RwMutex* m) { pthread_rwlock_unlock(&m->mutex); }') g.sb.writeln('static inline void sync__RwMutex_lock(sync__RwMutex* m) { pthread_rwlock_wrlock(&m->mutex); }') g.sb.writeln('static inline void sync__RwMutex_unlock(sync__RwMutex* m) { pthread_rwlock_unlock(&m->mutex); }') g.emitted_types['body_sync__RwMutex'] = true } fn (mut g Gen) emit_windows_rwmutex() { g.sb.writeln('typedef struct sync__RwMutex { SRWLOCK mutex; u32 inited; } sync__RwMutex;') g.sb.writeln('static inline void sync__RwMutex_rlock(sync__RwMutex* m) { AcquireSRWLockShared(&m->mutex); }') g.sb.writeln('static inline void sync__RwMutex_runlock(sync__RwMutex* m) { ReleaseSRWLockShared(&m->mutex); }') g.sb.writeln('static inline void sync__RwMutex_lock(sync__RwMutex* m) { AcquireSRWLockExclusive(&m->mutex); }') g.sb.writeln('static inline void sync__RwMutex_unlock(sync__RwMutex* m) { ReleaseSRWLockExclusive(&m->mutex); }') g.emitted_types['body_sync__RwMutex'] = true } fn (mut g Gen) emit_cross_rwmutex() { g.sb.writeln('#if defined(_WIN32)') g.emit_windows_rwmutex() g.sb.writeln('#else') g.emit_pthread_rwmutex() g.sb.writeln('#endif') g.emitted_types['body_sync__RwMutex'] = true } fn (mut g Gen) emit_target_rwmutex() { target_os := g.target_os_name() if g.is_freestanding_target() { g.emit_stub_rwmutex() return } if target_os == 'windows' { g.emit_windows_rwmutex() return } if target_os == 'cross' { g.emit_cross_rwmutex() return } g.emit_pthread_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() g.emit_collected_c_directives() g.emit_tinyc_arm_cpu_relax_fallback() if g.pref != unsafe { nil } && g.pref.prealloc && !g.is_freestanding_target() { g.sb.writeln('#define _VPREALLOC (1)') // Save the real free() before redefining it as a no-op. // prealloc_vcleanup needs the real free to release arena chunks. g.sb.writeln('static inline void _v_cfree(void *p) { free(p); }') g.sb.writeln('#define free(p) ((void)(p), (void)0)') } g.sb.writeln('') // V primitive type aliases g.sb.writeln('// V primitive types') g.sb.writeln('typedef int8_t i8;') g.sb.writeln('typedef int16_t i16;') g.sb.writeln('typedef int32_t i32;') g.sb.writeln('typedef int64_t i64;') g.sb.writeln('typedef uint8_t u8;') g.sb.writeln('typedef uint16_t u16;') g.sb.writeln('typedef uint32_t u32;') g.sb.writeln('typedef uint64_t u64;') g.sb.writeln('typedef float f32;') g.sb.writeln('typedef double f64;') g.sb.writeln('typedef u8 byte;') g.sb.writeln('typedef size_t usize;') g.sb.writeln('typedef ptrdiff_t isize;') g.sb.writeln('typedef u32 rune;') g.sb.writeln('typedef char* byteptr;') g.sb.writeln('typedef char* charptr;') g.sb.writeln('typedef void* voidptr;') g.sb.writeln('typedef struct sync__Channel* chan;') g.sb.writeln('typedef double float_literal;') g.sb.writeln('typedef int64_t int_literal;') g.sb.writeln('extern int g_main_argc;') g.sb.writeln('extern void* g_main_argv;') g.emit_freestanding_platform_hook_decls() g.emit_vec_simd_fallback_aliases() if minimal_preamble { g.emit_target_rwmutex() g.sb.writeln('') return } // wyhash implementation used by builtin/map and hash modules. g.sb.writeln('#ifndef wyhash_final_version_4_2') g.sb.writeln('#define wyhash_final_version_4_2') g.sb.writeln('#define WYHASH_CONDOM 1') g.sb.writeln('#define WYHASH_32BIT_MUM 0') g.sb.writeln('#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)') g.sb.writeln(' #define _likely_(x) __builtin_expect(x,1)') g.sb.writeln(' #define _unlikely_(x) __builtin_expect(x,0)') g.sb.writeln('#else') g.sb.writeln(' #define _likely_(x) (x)') g.sb.writeln(' #define _unlikely_(x) (x)') g.sb.writeln('#endif') g.sb.writeln('static inline uint64_t _wyrot(uint64_t x) { return (x>>32)|(x<<32); }') g.sb.writeln('static inline void _wymum(uint64_t *A, uint64_t *B){') g.sb.writeln('#if defined(__SIZEOF_INT128__)') g.sb.writeln(' __uint128_t r=*A; r*=*B; *A=(uint64_t)r; *B=(uint64_t)(r>>64);') g.sb.writeln('#elif defined(_MSC_VER) && defined(_M_X64)') g.sb.writeln(' *A=_umul128(*A,*B,B);') g.sb.writeln('#else') g.sb.writeln(' uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo;') g.sb.writeln(' uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t>32)+(rm1>>32)+c;') g.sb.writeln(' *A=lo; *B=hi;') g.sb.writeln('#endif') g.sb.writeln('}') g.sb.writeln('static inline uint64_t _wymix(uint64_t A, uint64_t B){ _wymum(&A,&B); return A^B; }') g.sb.writeln('#ifndef WYHASH_LITTLE_ENDIAN') g.sb.writeln(' #if defined(__LITTLE_ENDIAN__) || defined(__aarch64__) || defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) || defined(TARGET_ORDER_IS_LITTLE)') g.sb.writeln(' #define WYHASH_LITTLE_ENDIAN 1') g.sb.writeln(' #else') g.sb.writeln(' #define WYHASH_LITTLE_ENDIAN 0') g.sb.writeln(' #endif') g.sb.writeln('#endif') g.sb.writeln('#if (WYHASH_LITTLE_ENDIAN)') g.sb.writeln(' static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;}') g.sb.writeln(' static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;}') g.sb.writeln('#elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)') g.sb.writeln(' static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);}') g.sb.writeln(' static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);}') g.sb.writeln('#else') g.sb.writeln(' static inline uint64_t _wyr8(const uint8_t *p) {') g.sb.writeln(' uint64_t v; memcpy(&v, p, 8);') g.sb.writeln(' return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000));') g.sb.writeln(' }') g.sb.writeln(' static inline uint64_t _wyr4(const uint8_t *p) {') g.sb.writeln(' uint32_t v; memcpy(&v, p, 4);') g.sb.writeln(' return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000));') g.sb.writeln(' }') g.sb.writeln('#endif') g.sb.writeln('static inline uint64_t _wyr3(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];}') g.sb.writeln('static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){') g.sb.writeln(' const uint8_t *p=(const uint8_t *)key; seed^=_wymix(seed^secret[0]^len,secret[1]); uint64_t a, b;') g.sb.writeln(' if(_likely_(len<=16)){') g.sb.writeln(' if(_likely_(len>=4)){ a=(_wyr4(p)<<32)|_wyr4(p+((len>>3)<<2)); b=(_wyr4(p+len-4)<<32)|_wyr4(p+len-4-((len>>3)<<2)); }') g.sb.writeln(' else if(_likely_(len>0)){ a=_wyr3(p,len); b=0; }') g.sb.writeln(' else a=b=0;') g.sb.writeln(' } else {') g.sb.writeln(' size_t i=len;') g.sb.writeln(' if(_unlikely_(i>=48)){') g.sb.writeln(' uint64_t see1=seed, see2=seed;') g.sb.writeln(' do{') g.sb.writeln(' seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed);') g.sb.writeln(' see1=_wymix(_wyr8(p+16)^secret[2],_wyr8(p+24)^see1);') g.sb.writeln(' see2=_wymix(_wyr8(p+32)^secret[3],_wyr8(p+40)^see2);') g.sb.writeln(' p+=48; i-=48;') g.sb.writeln(' }while(_likely_(i>=48));') g.sb.writeln(' seed^=see1^see2;') g.sb.writeln(' }') g.sb.writeln(' while(_unlikely_(i>16)){ seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed); i-=16; p+=16; }') g.sb.writeln(' a=_wyr8(p+i-16); b=_wyr8(p+i-8);') g.sb.writeln(' }') g.sb.writeln(' a^=secret[1]; b^=seed; _wymum(&a,&b);') g.sb.writeln(' return _wymix(a^secret[0]^len,b^secret[1]);') g.sb.writeln('}') g.sb.writeln('static const uint64_t _wyp[4] = {0x2d358dccaa6c78a5ull, 0x8bb84b93962eacc9ull, 0x4b33a62ed433d4a3ull, 0x4d5a2da51de1aa47ull};') g.sb.writeln('static inline uint64_t wyhash64(uint64_t A, uint64_t B){ A^=0x2d358dccaa6c78a5ull; B^=0x8bb84b93962eacc9ull; _wymum(&A,&B); return _wymix(A^0x2d358dccaa6c78a5ull,B^0x8bb84b93962eacc9ull);}') g.sb.writeln('#endif') g.sb.writeln('#define _MOV') if g.target_os_name() != 'windows' && !g.is_freestanding_target() { g.sb.writeln('typedef u8 termios__Cc;') } // sync__RwMutex for shared variables g.emit_target_rwmutex() g.sb.writeln('') g.sb.writeln('') } fn (mut g Gen) emit_freestanding_platform_hook_decls() { if !g.has_freestanding_hooks() { return } g.sb.writeln('') if g.has_freestanding_hook_capability('output') { g.sb.writeln('isize v_platform_write(int stream, const u8* buf, isize len);') } if g.has_freestanding_hook_capability('panic') { g.sb.writeln('void v_platform_panic(const u8* msg, isize len);') } if g.has_freestanding_hook_capability('alloc') { g.sb.writeln('void* v_platform_malloc(isize n);') g.sb.writeln('void* v_platform_realloc(void* ptr, isize n);') g.sb.writeln('void v_platform_free(void* ptr);') } } fn (g &Gen) use_minimal_preamble() bool { return g.emit_modules.len == 1 && 'main' in g.emit_modules } fn (mut g Gen) emit_runtime_aliases() { mut array_names := g.array_aliases.keys() array_names.sort() if 'Array_math__T' !in g.array_aliases { g.sb.writeln('typedef array Array_math__T;') } // Emit dynamic array aliases (skip fixed arrays) for name in array_names { if name.starts_with('Array_fixed_') { continue } g.emit_array_alias_decl(name) } // Pointer element typedefs (e.g. 'typedef Coord* Coordptr;') are deferred // to emit_pointer_typedefs() which runs after pass 2 (enum/alias definitions). // Emit primitive fixed array typedefs (non-primitive ones deferred until after struct defs) for name in array_names { if !name.starts_with('Array_fixed_') { continue } if info := g.collected_fixed_array_types[name] { if info.elem_type in primitive_types || info.elem_type in ['char', 'voidptr', 'charptr', 'byteptr', 'void*', 'char*'] { g.sb.writeln('typedef ${info.elem_type} ${name} [${info.size}];') alias_key := 'alias_${name}' body_key := 'body_${name}' g.emitted_types[alias_key] = true g.emitted_types[body_key] = true // Emit fallback str macros for fixed array types (only if no real function was generated) str_fn := '${name}_str' if str_fn !in g.fn_return_types { g.sb.writeln('#define ${name}_str(a) ((string){.str = "${name}", .len = ${name.len}, .is_lit = 1})') g.sb.writeln('#define ${name}__str(a) ${name}_str(a)') } } } } g.sb.writeln('typedef array VArg_string;') g.sb.writeln('bool map_map_eq(map a, map b);') mut map_names := g.map_aliases.keys() map_names.sort() for name in map_names { g.emit_map_alias_decl(name) } // Option/Result forward declarations (struct definitions emitted later // after IError is defined, via emit_option_result_structs) mut option_names := g.option_aliases.keys() option_names.sort() for name in option_names { if g.should_skip_shadowed_option_result_alias(name) { continue } val_type := option_value_type(name) g.emit_option_result_forward_decl(name, val_type) } if '_option_string' in g.option_aliases { g.sb.writeln('static _option_string builtin__Option_string__clone(_option_string s);') } mut result_names := g.result_aliases.keys() result_names.sort() for name in result_names { if g.should_skip_shadowed_option_result_alias(name) { continue } val_type := g.result_value_type(name) g.emit_option_result_forward_decl(name, val_type) } } fn (mut g Gen) emit_option_result_forward_decl(name string, val_type string) { if g.option_result_payload_invalid(val_type) { return } key := 'option_result_forward_${name}' if key in g.emitted_types { return } g.emitted_types[key] = true if val_type != '' && val_type != 'void' { g.sb.writeln('typedef struct ${name} ${name};') } else if name.starts_with('_option_') { g.sb.writeln('typedef _option ${name};') } else { g.sb.writeln('typedef _result ${name};') } } // emit_pointer_typedefs emits pointer element typedefs needed by array helper functions. // e.g. Array_Coordptr needs 'typedef Coord* Coordptr;'. // Must run AFTER pass 2 so that enum and type alias definitions are available. fn (mut g Gen) emit_pointer_typedefs() { mut array_names := g.array_aliases.keys() array_names.sort() for name in array_names { if name.starts_with('Array_fixed_') { continue } elem := name['Array_'.len..] if elem.len > 3 && elem.ends_with('ptr') { base := elem[..elem.len - 3] if base !in ['void', 'char', 'byte'] && !elem.starts_with('Array_') && !elem.starts_with('Map_') { g.sb.writeln('typedef ${base}* ${elem};') } } } } fn (mut g Gen) get_enum_name(node ast.EnumDecl) string { if g.cur_module != '' && g.cur_module != 'main' && g.cur_module != 'builtin' { return '${g.cur_module}__${node.name}' } return node.name } fn (mut g Gen) emit_array_alias_decl(name string) { array_name := name.trim_right('*') if !array_name.starts_with('Array_') || array_name.starts_with('Array_fixed_') { return } alias_key := 'alias_${array_name}' if alias_key in g.emitted_types { return } g.emitted_types[alias_key] = true g.array_aliases[array_name] = true g.sb.writeln('typedef array ${array_name};') } fn (mut g Gen) emit_map_alias_decl(name string) { map_name := name.trim_right('*') if !map_name.starts_with('Map_') { return } alias_key := 'alias_${map_name}' if alias_key in g.emitted_types { return } g.emitted_types[alias_key] = true g.map_aliases[map_name] = true g.sb.writeln('typedef map ${map_name};') // Forward-declare map helper functions (bodies generated later). map_str_fn := '${map_name}_str' if map_str_fn !in g.fn_return_types { g.sb.writeln('string ${map_name}_str(${map_name} m);') } g.sb.writeln('bool ${map_name}_map_eq(${map_name} a, ${map_name} b);') } fn (mut g Gen) gen_enum_decl(node ast.EnumDecl) { name := g.get_enum_name(node) enum_key := 'enum_${name}' if enum_key in g.emitted_types { return } g.emitted_types[enum_key] = true mut is_flag := false for attribute in node.attributes { if attribute.name == 'flag' { is_flag = true break } if attribute.name == '' && attribute.value is ast.Ident && attribute.value.name == 'flag' { is_flag = true break } } g.sb.writeln('typedef enum {') for i, field in node.fields { g.sb.write_string('\t${g.enum_member_c_name(name, field.name)}') if field.value !is ast.EmptyExpr { g.sb.write_string(' = ') if enum_value := g.enum_field_value_int_expr(field.value) { g.sb.write_string(enum_value) } else { g.expr(field.value) } } else if is_flag { g.sb.write_string(' = ${u64(1) << i}U') } if i < node.fields.len - 1 { g.sb.writeln(',') } else { g.sb.writeln('') } } g.sb.writeln('} ${name};') g.sb.writeln('') enum_str_fn := '${name}__str' has_explicit_str := g.has_explicit_str_method_for_c_type(name) if !has_explicit_str && ((g.cache_bundle_name == 'virtuals' && g.should_emit_current_file()) || enum_str_fn in g.force_emit_fn_names) { mut enum_str_expr := c_static_v_string_expr('unknown enum value') for i := node.fields.len - 1; i >= 0; i-- { field := node.fields[i] enum_str_expr = '((v == ${g.enum_member_c_name(name, field.name)}) ? ${c_static_v_string_expr(field.name)} : ${enum_str_expr})' } g.late_struct_defs << 'string ${name}__str(${name} v) { return ${enum_str_expr}; }\n' g.fn_return_types[enum_str_fn] = 'string' } else if enum_str_fn !in g.fn_return_types { // Emit a macro so the expression is type-checked only at use sites, // where `string` is fully defined. mut enum_str_expr := c_static_v_string_expr('unknown enum value') for i := node.fields.len - 1; i >= 0; i-- { field := node.fields[i] enum_str_expr = '((_e == ${g.enum_member_c_name(name, field.name)}) ? ${c_static_v_string_expr(field.name)} : ${enum_str_expr})' } g.sb.writeln('#define ${name}__str(v) ({ ${name} _e = (v); ${enum_str_expr}; })') } enum_short_str_fn := '${name}_str' if enum_short_str_fn !in g.fn_return_types { g.sb.writeln('#define ${name}_str(v) ${name}__str(v)') } // Runtime enum values array for lowered `EnumType.values` usages. Keep the // helper name distinct from enum fields; enums can have a field named `values`. values_name := '${name}__values_array' values_data_name := '__enum_values_data_${name}' if node.fields.len > 0 { g.sb.write_string('static ${name} ${values_data_name}[${node.fields.len}] = {') for i, field in node.fields { if i > 0 { g.sb.write_string(', ') } g.sb.write_string(g.enum_member_c_name(name, field.name)) } g.sb.writeln('};') g.sb.writeln('#define ${values_name} ((array){ .data = ${values_data_name}, .offset = 0, .len = ${node.fields.len}, .cap = ${node.fields.len}, .flags = 0, .element_size = sizeof(${name}) })') } else { g.sb.writeln('#define ${values_name} ((array){ .data = 0, .offset = 0, .len = 0, .cap = 0, .flags = 0, .element_size = sizeof(${name}) })') } g.sb.writeln('') } fn (mut g Gen) gen_enum_from_string_helper(node ast.EnumDecl) { name := g.get_enum_name(node) opt_name := '_option_' + mangle_alias_component(name) if opt_name !in g.option_aliases || !g.should_emit_current_file() { return } helper_name := '${name}__from_string' helper_key := 'enum_from_string_${helper_name}' if helper_key in g.emitted_types { return } g.emitted_types[helper_key] = true g.declared_fn_names[helper_name] = true g.sb.writeln('${opt_name} ${helper_name}(string s);') g.sb.writeln('${opt_name} ${helper_name}(string s) {') for field in node.fields { field_lit := c_string_literal_content_to_c(field.name) g.sb.writeln('\tif (s.len == ${field.name.len} && memcmp(s.str, ${field_lit}, ${field.name.len}) == 0) {') g.sb.writeln('\t\t${name} _val = ${g.enum_member_c_name(name, field.name)};') g.sb.writeln('\t\t${opt_name} _opt = (${opt_name}){ .state = 2 };') g.sb.writeln('\t\t_option_ok(&_val, (_option*)&_opt, sizeof(_val));') g.sb.writeln('\t\treturn _opt;') g.sb.writeln('\t}') } g.sb.writeln('\treturn (${opt_name}){ .state = 2 };') g.sb.writeln('}') g.sb.writeln('') } fn (mut g Gen) emit_enum_from_string_helpers() { if g.has_flat() { for i in 0 .. g.flat.files.len { fc := g.flat.file_cursor(i) g.set_file_cursor_module(fc) stmts := fc.stmts() for j in 0 .. stmts.len() { stmt := stmts.at(j) if stmt.kind() == .stmt_enum_decl { g.gen_enum_from_string_helper(stmt.enum_decl(true)) } } } return } for file in g.files { g.set_file_module(file) for stmt in file.stmts { if !stmt_has_valid_data(stmt) { continue } if stmt is ast.EnumDecl { g.gen_enum_from_string_helper(stmt) } } } } fn (mut g Gen) enum_field_value_int_expr(expr ast.Expr) ?string { match expr { ast.BasicLiteral { if expr.kind == .number { return sanitize_c_number_literal(expr.value) } } ast.CallOrCastExpr { return g.enum_field_value_int_expr(expr.expr) } ast.CastExpr { return g.enum_field_value_int_expr(expr.expr) } ast.ParenExpr { return g.enum_field_value_int_expr(expr.expr) } ast.PrefixExpr { if expr.op == .minus { if value := g.enum_field_value_int_expr(expr.expr) { return '-${value}' } } } ast.SelectorExpr { return g.enum_selector_int_value(expr) } else {} } return none } fn (mut g Gen) enum_selector_int_value(sel ast.SelectorExpr) ?string { rhs_name := sel.rhs.name if sel.lhs is ast.SelectorExpr { lhs_sel := sel.lhs as ast.SelectorExpr if lhs_sel.lhs is ast.Ident { lhs_mod := lhs_sel.lhs as ast.Ident mut module_names := [lhs_mod.name] resolved_mod_name := g.resolve_module_name(lhs_mod.name) if resolved_mod_name !in module_names { module_names << resolved_mod_name } for mod_name in module_names { enum_name := '${mod_name}__${lhs_sel.rhs.name}' if value := g.enum_decl_field_int_value(enum_name, rhs_name) { return value } } } } if sel.lhs is ast.Ident { lhs_name := sel.lhs.name mut enum_names := [lhs_name] if g.cur_module != '' && g.cur_module != 'main' && g.cur_module != 'builtin' { enum_names << '${g.cur_module}__${lhs_name}' } for enum_name in enum_names { if value := g.enum_decl_field_int_value(enum_name, rhs_name) { return value } } } return none } fn (mut g Gen) enum_decl_field_int_value(enum_name string, field_name string) ?string { short_name := enum_name.all_after_last('__') module_name := if enum_name.contains('__') { enum_name.all_before_last('__') } else { g.cur_module } if g.has_flat() { for i in 0 .. g.flat.files.len { fc := g.flat.file_cursor(i) if module_name != '' && flat_file_module_name(fc) != module_name { continue } stmts := fc.stmts() for j in 0 .. stmts.len() { stmt := stmts.at(j) if stmt.kind() != .stmt_enum_decl || stmt.name() != short_name { continue } decl := stmt.enum_decl(true) mut next_value := 0 for field in decl.fields { if field.value !is ast.EmptyExpr { if value := g.enum_field_value_int_expr(field.value) { next_value = value.int() } } if field.name == field_name { return '${next_value}' } next_value++ } } } return none } for file in g.files { if module_name != '' && file.mod != module_name { continue } for stmt in file.stmts { if stmt is ast.EnumDecl && stmt.name == short_name { mut next_value := 0 for field in stmt.fields { if field.value !is ast.EmptyExpr { if value := g.enum_field_value_int_expr(field.value) { next_value = value.int() } } if field.name == field_name { return '${next_value}' } next_value++ } } } } return none } // collect_force_emit_str_fns scans map_aliases and array_aliases to find which // str functions will be called from generated map/array str code, and adds them // to force_emit_fn_names so they get emitted even if mark_used didn't trace them. fn (mut g Gen) collect_force_emit_str_fns() { if g.used_fn_keys.len == 0 { return } for name, _ in g.map_aliases { if '${name}_str' in g.fn_return_types { continue // user-defined map str function } without_prefix := name.all_after('Map_') _, value_type := g.parse_map_kv_types(without_prefix) if value_type == '' { continue } g.add_force_emit_str_fn(value_type) } // Force-emit all Array_*_str functions that have registered signatures. // These are generated by the transformer for string interpolation but the // calls to them are emitted directly by cleanc (in write_sprintf_arg), // so markused may not trace them. for fn_name, _ in g.fn_return_types { if fn_name.starts_with('Array_') && fn_name.ends_with('_str') { g.force_emit_fn_names[fn_name] = true } } } fn (mut g Gen) add_force_emit_str_fn(type_name string) { // Primitive types don't need force-emitting (already in builtin) if type_name in ['string', 'int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'bool', 'rune', 'voidptr'] { return } if str_fn := g.get_str_fn_for_type(type_name) { g.force_emit_fn_names[str_fn] = true } } // emit_map_str_functions generates Map_K_V_str functions for all map types. fn (mut g Gen) emit_map_str_functions() { mut map_names := g.map_aliases.keys() map_names.sort() for name in map_names { // Skip if the user already defined this function if '${name}_str' in g.fn_return_types { continue } // Skip map aliases whose typedef wasn't emitted (late-registered aliases // that reference unresolved short-name types). Without a typedef, the // generated helper would reference an undeclared type. if 'alias_${name}' !in g.emitted_types { continue } // Parse key and value types from Map_K_V name key_type, value_type := g.map_alias_key_value_types(name) if key_type == '' || value_type == '' { continue } // Skip generic placeholder types (single uppercase letters like K, V, T) if key_type.len == 1 && key_type[0] >= `A` && key_type[0] <= `Z` { continue } // Skip types that can't be safely used as local variables (fixed arrays, etc.) if !g.can_emit_map_str_for_type(key_type) || !g.can_emit_map_str_for_type(value_type) { // Emit a fallback that returns the type name g.sb.writeln('') g.sb.writeln('string ${name}_str(${name} m) {') g.sb.writeln('\treturn (string){.str = "${name}", .len = ${name.len}, .is_lit = 1};') g.sb.writeln('}') continue } g.sb.writeln('') g.sb.writeln('string ${name}_str(${name} m) {') g.sb.writeln('\tstrings__Builder sb = strings__new_builder(2 + m.key_values.len * 10);') g.sb.writeln('\tstrings__Builder__write_string(&sb, (string){.str = "{", .len = 1, .is_lit = 1});') g.sb.writeln('\tbool is_first = true;') g.sb.writeln('\tfor (int i = 0; i < m.key_values.len; ++i) {') g.sb.writeln('\t\tif (!DenseArray__has_index(&m.key_values, i)) continue;') g.sb.writeln('\t\tif (!is_first) strings__Builder__write_string(&sb, (string){.str = ", ", .len = 2, .is_lit = 1});') // Key if key_type.starts_with('Array_fixed_') { elem_type, arr_len := g.parse_fixed_array_type(key_type) g.sb.writeln('\t\t${elem_type}* key_ptr = (${elem_type}*)DenseArray__key(&m.key_values, i);') g.emit_fixed_array_str_write('key_ptr', elem_type, arr_len) } else { g.sb.writeln('\t\t${key_type} key = *(${key_type}*)DenseArray__key(&m.key_values, i);') g.emit_map_str_write_val('key', key_type) } // Separator g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = ": ", .len = 2, .is_lit = 1});') // Value if value_type.starts_with('Array_fixed_') { // Fixed arrays can't be assigned to local variables; use pointer elem_type, arr_len := g.parse_fixed_array_type(value_type) g.sb.writeln('\t\t${elem_type}* val_ptr = (${elem_type}*)DenseArray__value(&m.key_values, i);') g.emit_fixed_array_str_write('val_ptr', elem_type, arr_len) } else { g.sb.writeln('\t\t${value_type} val = *(${value_type}*)DenseArray__value(&m.key_values, i);') g.emit_map_str_write_val('val', value_type) } g.sb.writeln('\t\tis_first = false;') g.sb.writeln('\t}') g.sb.writeln('\tstrings__Builder__write_string(&sb, (string){.str = "}", .len = 1, .is_lit = 1});') g.sb.writeln('\treturn strings__Builder__str(&sb);') g.sb.writeln('}') } } fn (g &Gen) can_emit_map_str_for_type(type_name string) bool { // Primitive types are always OK if type_name in ['string', 'int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'bool', 'rune', 'voidptr'] { return true } // Fixed arrays — need special handling but can be emitted if type_name.starts_with('Array_fixed_') { return true } // Pointer-like types that may not be valid C type names if type_name in ['intptr', 'charptr', 'byteptr'] || type_name.ends_with('ptr') { return false } // Check if a str function exists for this type if '${type_name}_str' in g.fn_return_types || '${type_name}__str' in g.fn_return_types { return true } // Map types can call their own str function (recursive) if type_name.starts_with('Map_') { return true } // Array types — check if Array_T_str exists if type_name.starts_with('Array_') { return '${type_name}_str' in g.fn_return_types } // For other types (structs, enums, etc.), check if str function exists return '${type_name}_str' in g.fn_return_types || '${type_name}__str' in g.fn_return_types } fn (mut g Gen) emit_map_str_write_val(var_name string, type_name string) { if type_name == 'string' { g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = "\'", .len = 1, .is_lit = 1});') g.sb.writeln('\t\tstrings__Builder__write_string(&sb, ${var_name});') g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = "\'", .len = 1, .is_lit = 1});') } else if type_name == 'rune' { g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = "`", .len = 1, .is_lit = 1});') g.sb.writeln('\t\tstrings__Builder__write_string(&sb, rune__str(${var_name}));') g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = "`", .len = 1, .is_lit = 1});') } else if type_name in ['int', 'i8', 'i16', 'i32', 'i64'] { g.sb.writeln('\t\tstrings__Builder__write_string(&sb, int__str((int)(${var_name})));') } else if type_name in ['u8', 'u16', 'u32', 'u64'] { g.sb.writeln('\t\tstrings__Builder__write_string(&sb, u64__str((u64)(${var_name})));') } else if type_name == 'f32' { g.sb.writeln('\t\tstrings__Builder__write_string(&sb, f32__str(${var_name}));') } else if type_name == 'f64' { g.sb.writeln('\t\tstrings__Builder__write_string(&sb, f64__str(${var_name}));') } else if type_name == 'bool' { g.sb.writeln('\t\tstrings__Builder__write_string(&sb, ${var_name} ? (string){.str = "true", .len = 4, .is_lit = 1} : (string){.str = "false", .len = 5, .is_lit = 1});') } else if type_name == 'voidptr' { g.sb.writeln('\t\t{ uintptr_t _addr = (uintptr_t)(${var_name}); char _buf[2 + sizeof(uintptr_t) * 2]; _buf[0] = \'0\'; _buf[1] = \'x\'; for (int _i = 0; _i < (int)(sizeof(uintptr_t) * 2); ++_i) { int _shift = (int)((sizeof(uintptr_t) * 2 - 1 - _i) * 4); _buf[2 + _i] = "0123456789abcdef"[(_addr >> _shift) & 0xf]; } strings__Builder__write_string(&sb, (string){.str = (u8*)_buf, .len = (int)sizeof(_buf), .is_lit = 0}); }') } else { // For custom types, pick whichever str symbol exists (`Type_str` or `Type__str`). if str_fn := g.get_str_fn_for_type(type_name) { // Check if the str function expects a pointer receiver ptr_params := g.fn_param_is_ptr[str_fn] or { []bool{} } if ptr_params.len > 0 && ptr_params[0] { g.sb.writeln('\t\tstrings__Builder__write_string(&sb, ${str_fn}(&${var_name}));') } else { g.sb.writeln('\t\tstrings__Builder__write_string(&sb, ${str_fn}(${var_name}));') } } else { // Keep the legacy fallback so missing str support still fails loudly in C. g.sb.writeln('\t\tstrings__Builder__write_string(&sb, ${type_name}_str(${var_name}));') } } } // parse_fixed_array_type parses "Array_fixed_f64_2" into ("f64", 2). fn (g &Gen) parse_fixed_array_type(type_name string) (string, int) { // Format: Array_fixed__ without_prefix := type_name.all_after('Array_fixed_') // The last _N is the length last_underscore := without_prefix.last_index('_') or { return '', 0 } elem_type := without_prefix[..last_underscore] arr_len := without_prefix[last_underscore + 1..].int() return elem_type, arr_len } // emit_fixed_array_str_write generates code to format a fixed array as "[e1, e2, ...]". fn (mut g Gen) emit_fixed_array_str_write(ptr_var string, elem_type string, arr_len int) { g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = "[", .len = 1, .is_lit = 1});') for j in 0 .. arr_len { if j > 0 { g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = ", ", .len = 2, .is_lit = 1});') } g.emit_map_str_write_val('${ptr_var}[${j}]', elem_type) } g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = "]", .len = 1, .is_lit = 1});') } // emit_map_eq_functions generates Map_K_V_map_eq functions for all map types. fn (mut g Gen) emit_map_eq_functions() { // Generic fallback for when the specific map type is not known. // Only emit if not already provided via the V source (builtin/map.v) // or via the cache (builtin.o). if 'map_map_eq' !in g.fn_return_types && g.cached_init_calls.len == 0 { g.sb.writeln('') g.sb.writeln('bool map_map_eq(map a, map b) {') g.sb.writeln('\tif (a.len != b.len) return false;') g.sb.writeln('\tfor (int i = 0; i < a.key_values.len; ++i) {') g.sb.writeln('\t\tif (!DenseArray__has_index(&a.key_values, i)) continue;') g.sb.writeln('\t\tvoid* k = DenseArray__key(&a.key_values, i);') g.sb.writeln('\t\tif (!map__exists(&b, k)) return false;') g.sb.writeln('\t\tvoid* va = DenseArray__value(&a.key_values, i);') g.sb.writeln('\t\tvoid* vb_p = map__get(&b, k, va);') g.sb.writeln('\t\tif (memcmp(va, vb_p, a.value_bytes) != 0) return false;') g.sb.writeln('\t}') g.sb.writeln('\treturn true;') g.sb.writeln('}') } mut map_names := g.map_aliases.keys() map_names.sort() for name in map_names { // Skip late-registered map aliases without a typedef (see emit_map_str_functions). if 'alias_${name}' !in g.emitted_types { continue } key_type, value_type := g.map_alias_key_value_types(name) if key_type == '' || value_type == '' { g.sb.writeln('') g.sb.writeln('bool ${name}_map_eq(${name} a, ${name} b) { return map_map_eq(a, b); }') continue } // Skip generic placeholder types if value_type.len == 1 && value_type[0] >= `A` && value_type[0] <= `Z` { g.sb.writeln('') g.sb.writeln('bool ${name}_map_eq(${name} a, ${name} b) { return map_map_eq(a, b); }') continue } g.sb.writeln('') g.sb.writeln('bool ${name}_map_eq(${name} a, ${name} b) {') g.sb.writeln('\tif (a.len != b.len) return false;') g.sb.writeln('\tfor (int i = 0; i < a.key_values.len; ++i) {') g.sb.writeln('\t\tif (!DenseArray__has_index(&a.key_values, i)) continue;') g.sb.writeln('\t\tvoid* k = DenseArray__key(&a.key_values, i);') g.sb.writeln('\t\tif (!map__exists(&b, k)) return false;') // Compare values based on value type if value_type == 'string' { g.sb.writeln('\t\tstring va = *(string*)map__get(&a, k, &(string){0});') g.sb.writeln('\t\tstring vb = *(string*)map__get(&b, k, &(string){0});') g.sb.writeln('\t\tif (!string__eq(va, vb)) return false;') } else if value_type in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'bool', 'rune', 'voidptr'] { g.sb.writeln('\t\t${value_type} va = *(${value_type}*)map__get(&a, k, &(${value_type}){0});') g.sb.writeln('\t\t${value_type} vb = *(${value_type}*)map__get(&b, k, &(${value_type}){0});') g.sb.writeln('\t\tif (va != vb) return false;') } else if value_type in ['intptr', 'u8ptr', 'charptr', 'byteptr'] || value_type.ends_with('ptr') { // Pointer-like types (V aliases and module-qualified pointer types): compare as void* g.sb.writeln('\t\tvoid* va = *(void**)map__get(&a, k, &(void*){0});') g.sb.writeln('\t\tvoid* vb = *(void**)map__get(&b, k, &(void*){0});') g.sb.writeln('\t\tif (va != vb) return false;') } else if value_type.starts_with('Array_fixed_') { // Fixed arrays: use memcmp (they are plain C arrays) g.sb.writeln('\t\tvoid* va = map__get(&a, k, &(${value_type}){0});') g.sb.writeln('\t\tvoid* vb = map__get(&b, k, &(${value_type}){0});') g.sb.writeln('\t\tif (memcmp(va, vb, sizeof(${value_type})) != 0) return false;') } else if value_type == 'array' || value_type.starts_with('Array_') { // Array values: use __v2_array_eq for deep comparison g.sb.writeln('\t\tarray va = *(array*)map__get(&a, k, &(${value_type}){0});') g.sb.writeln('\t\tarray vb = *(array*)map__get(&b, k, &(${value_type}){0});') g.sb.writeln('\t\tif (!__v2_array_eq(va, vb)) return false;') } else if value_type.starts_with('Map_') { // Map values: use the map's own eq function for deep comparison g.sb.writeln('\t\t${value_type} va = *(${value_type}*)map__get(&a, k, &(${value_type}){0});') g.sb.writeln('\t\t${value_type} vb = *(${value_type}*)map__get(&b, k, &(${value_type}){0});') g.sb.writeln('\t\tif (!${value_type}_map_eq(va, vb)) return false;') } else { // Try to look up struct fields for deep comparison struct_type := g.lookup_struct_type_by_c_name(value_type) if struct_type.fields.len > 0 { g.sb.writeln('\t\t${value_type} va = *(${value_type}*)map__get(&a, k, &(${value_type}){0});') g.sb.writeln('\t\t${value_type} vb = *(${value_type}*)map__get(&b, k, &(${value_type}){0});') g.emit_struct_field_eq(struct_type, 'va', 'vb') } else { // Fallback: use memcmp on the value g.sb.writeln('\t\tvoid* va = map__get(&a, k, &(${value_type}){0});') g.sb.writeln('\t\tvoid* vb = map__get(&b, k, &(${value_type}){0});') g.sb.writeln('\t\tif (memcmp(va, vb, sizeof(${value_type})) != 0) return false;') } } g.sb.writeln('\t}') g.sb.writeln('\treturn true;') g.sb.writeln('}') } } // lookup_struct_type_by_c_name resolves a C type name to a types.Struct by searching // all known module scopes. C names may be like "MValue" (current module) or "os__File". // Returns empty Struct for sum types/aliases (their merged fields aren't safe for field access). fn (mut g Gen) lookup_struct_type_by_c_name(c_name string) types.Struct { if g.env == unsafe { nil } { return types.Struct{} } cache_key := '${g.cur_module}|${c_name}' if cached := g.struct_type_lookup_cache[cache_key] { return cached } if cache_key in g.struct_type_lookup_miss { return types.Struct{} } // Try extracting module from mangled name (e.g. "os__File" -> module "os", name "File") mut mod_name := '' mut struct_name := c_name if idx := c_name.index('__') { mod_name = c_name[..idx] struct_name = c_name[idx + 2..] } if mod_name != '' { if mut scope := g.env_scope(mod_name) { if obj := scope.lookup_parent(struct_name, 0) { typ := obj.typ() // Skip sum types - their merged fields aren't safe for direct access if typ is types.Alias || typ is types.SumType { g.struct_type_lookup_miss[cache_key] = true return types.Struct{} } if typ is types.Struct { g.struct_type_lookup_cache[cache_key] = typ return typ } } } } // Bare C type names belong to main/builtin. Non-main module types are // emitted with a module prefix, so do not let the current module shadow them. mut tried := map[string]bool{} cur_mod := if g.cur_module != '' { g.cur_module } else { 'main' } mut modules_to_try := []string{} if mod_name == '' && cur_mod != 'main' && cur_mod != 'builtin' { modules_to_try << 'main' modules_to_try << 'builtin' } modules_to_try << cur_mod if 'main' !in modules_to_try { modules_to_try << 'main' } if 'builtin' !in modules_to_try { modules_to_try << 'builtin' } for try_mod in modules_to_try { if tried[try_mod] { continue } tried[try_mod] = true if mut scope := g.env_scope(try_mod) { if obj := scope.lookup_parent(struct_name, 0) { typ := obj.typ() if typ is types.Alias { g.struct_type_lookup_miss[cache_key] = true return types.Struct{} } if typ is types.Struct { g.struct_type_lookup_cache[cache_key] = typ return typ } } } } // Try all module scopes from files if g.has_flat() { for i in 0 .. g.flat.files.len { file_mod := flat_file_module_name(g.flat.file_cursor(i)) if tried[file_mod] { continue } tried[file_mod] = true if mut scope := g.env_scope(file_mod) { if obj := scope.lookup_parent(struct_name, 0) { typ := obj.typ() if typ is types.Alias { g.struct_type_lookup_miss[cache_key] = true return types.Struct{} } if typ is types.Struct { g.struct_type_lookup_cache[cache_key] = typ return typ } } } } } else { for file in g.files { file_mod := file.mod if tried[file_mod] { continue } tried[file_mod] = true if mut scope := g.env_scope(file_mod) { if obj := scope.lookup_parent(struct_name, 0) { typ := obj.typ() if typ is types.Alias { g.struct_type_lookup_miss[cache_key] = true return types.Struct{} } if typ is types.Struct { g.struct_type_lookup_cache[cache_key] = typ return typ } } } } } g.struct_type_lookup_miss[cache_key] = true return types.Struct{} } // emit_struct_field_eq generates field-by-field comparison code for a struct type. fn (mut g Gen) emit_struct_field_eq(s types.Struct, va string, vb string) { for field in s.fields { fname := escape_c_keyword(field.name) ftype := field.typ match ftype { types.String { g.sb.writeln('\t\tif (!string__eq(${va}.${fname}, ${vb}.${fname})) return false;') } types.Map { c_type := g.types_type_to_c(ftype) if c_type.starts_with('Map_') { g.sb.writeln('\t\tif (!${c_type}_map_eq(${va}.${fname}, ${vb}.${fname})) return false;') } else { g.sb.writeln('\t\tif (!map_map_eq(${va}.${fname}, ${vb}.${fname})) return false;') } } types.Array { g.sb.writeln('\t\tif (!__v2_array_eq(${va}.${fname}, ${vb}.${fname})) return false;') } types.Primitive { g.sb.writeln('\t\tif (${va}.${fname} != ${vb}.${fname}) return false;') } types.Pointer { g.sb.writeln('\t\tif (${va}.${fname} != ${vb}.${fname}) return false;') } types.Rune { g.sb.writeln('\t\tif (${va}.${fname} != ${vb}.${fname}) return false;') } types.Enum { g.sb.writeln('\t\tif (${va}.${fname} != ${vb}.${fname}) return false;') } types.Struct { c_type := g.types_type_to_c(ftype) if g.struct_has_ref_fields(ftype) { g.emit_struct_field_eq(ftype, '${va}.${fname}', '${vb}.${fname}') } else { g.sb.writeln('\t\tif (memcmp(&${va}.${fname}, &${vb}.${fname}, sizeof(${c_type})) != 0) return false;') } } else { c_type := g.types_type_to_c(ftype) g.sb.writeln('\t\tif (memcmp(&${va}.${fname}, &${vb}.${fname}, sizeof(${c_type})) != 0) return false;') } } } } fn (mut g Gen) map_alias_key_value_types(name string) (string, string) { if info := g.ensure_map_type_info(name) { if info.key_c_type != '' && info.value_c_type != '' { return info.key_c_type, info.value_c_type } } without_prefix := name.all_after('Map_') return g.parse_map_kv_types(without_prefix) } // parse_map_kv_types parses key and value types from a "key_value" string. fn (g &Gen) parse_map_kv_types(kv_str string) (string, string) { // Try simple primitive types first (they're unambiguous since they don't // contain underscores — except for the separator underscore) simple_types := ['string', 'int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'bool', 'rune', 'voidptr', 'charptr', 'byteptr'] for st in simple_types { if kv_str.starts_with('${st}_') { return st, kv_str.all_after('${st}_') } } // For compound key types: try each underscore position and check if the // key type is a known type alias/struct. Keep the longest match: generic // struct names can have their base type emitted too, e.g. // `ApiResponse_T_Array_FileInfo_Array_int`. mut best_key := '' mut best_value := '' for i := 1; i < kv_str.len; i++ { if kv_str[i] == `_` { key := kv_str[..i] value := kv_str[i + 1..] if value == '' { continue } // Verify key is a known type if g.is_known_map_key_type(key, simple_types) { best_key = key best_value = value } } } if best_key != '' { return best_key, best_value } // Last resort: split on last underscore last_idx := kv_str.last_index('_') or { return '', '' } if last_idx > 0 && last_idx < kv_str.len - 1 { return kv_str[..last_idx], kv_str[last_idx + 1..] } return '', '' } fn (g &Gen) is_known_map_key_type(key string, simple_types []string) bool { if key.starts_with('Array_fixed_') { _, arr_len := g.parse_fixed_array_type(key) return arr_len > 0 } return key in g.map_aliases || key in g.array_aliases || key in g.emitted_types || 'body_${key}' in g.emitted_types || 'alias_${key}' in g.emitted_types || 'enum_${key}' in g.emitted_types || key in simple_types }