From 2798aa9200ebae8bc604b6bba5a6c2fb3d9cc2f0 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 20 Aug 2026 18:31:27 +0300 Subject: [PATCH 01/32] v3: add direct fastc backend --- cmd/v/macos_v3_args.c.v | 26 +- cmd/v/macos_v3_darwin.c.v | 6 +- cmd/v/macos_v3_default.c.v | 4 +- cmd/v/macos_v3_test.v | 25 ++ vlib/v/pref/pref.v | 9 +- vlib/v/pref/pref_test.v | 10 + vlib/v3/README.md | 39 +- vlib/v3/driver/driver.v | 168 +++++++- vlib/v3/gen/fastc/fastc.v | 605 +++++++++++++++++++++++++++++ vlib/v3/gen/fastc/fastc_test.v | 61 +++ vlib/v3/tests/fastc_backend_test.v | 83 ++++ 11 files changed, 1009 insertions(+), 27 deletions(-) create mode 100644 vlib/v3/gen/fastc/fastc.v create mode 100644 vlib/v3/gen/fastc/fastc_test.v create mode 100644 vlib/v3/tests/fastc_backend_test.v diff --git a/cmd/v/macos_v3_args.c.v b/cmd/v/macos_v3_args.c.v index 801e3b8ced1408..e80b21bb1ddfb2 100644 --- a/cmd/v/macos_v3_args.c.v +++ b/cmd/v/macos_v3_args.c.v @@ -22,10 +22,12 @@ fn macos_v3_non_compilation_command(command string) bool { // macos_v3_force_requested reports whether `-new-compiler` should hand this // invocation to the embedded V3 compiler. It gates on `-old-compiler` // precedence, options/modes V3 cannot honor yet, and whether the command is an -// actual compilation command (never `test`, external tools, or the `cmd/v` / -// `vlib/v3/v3.v` bootstrap). Both the Darwin dispatcher (where it overrides the -// default heuristic) and the non-macOS dispatcher (where it is the sole gate) -// rely on it, so it must stay platform neutral. +// actual compilation command (never `test` or external tools). Compiler bootstrap +// targets normally stay on V1, but explicit `-b fastc` owns those targets too: its +// direct emitter falls back to V3's checked C backend for the full compiler source. +// Both the Darwin dispatcher (where it overrides the default heuristic) and the +// non-macOS dispatcher (where it is the sole gate) rely on it, so it must stay +// platform neutral. @[markused] fn macos_v3_force_requested(command string, prefs &pref.Preferences) bool { if !prefs.new_compiler || prefs.old_compiler { @@ -42,15 +44,27 @@ fn macos_v3_force_requested(command string, prefs &pref.Preferences) bool { return false } normalized_path := prefs.path.replace('\\', '/').trim_right('/') - if normalized_path == 'cmd/v' || normalized_path.starts_with('cmd/v/') + compiler_bootstrap := normalized_path == 'cmd/v' || normalized_path.starts_with('cmd/v/') || normalized_path.contains('/cmd/v/') || normalized_path.ends_with('/cmd/v') - || normalized_path == 'vlib/v3/v3.v' || normalized_path.ends_with('/vlib/v3/v3.v') { + || normalized_path == 'vlib/v3/v3.v' || normalized_path.ends_with('/vlib/v3/v3.v') + if compiler_bootstrap && !macos_v3_fastc_requested(prefs) { return false } return command in ['run', 'build'] || prefs.is_script || os.is_dir(prefs.path) || normalized_path.ends_with('.v') || normalized_path.ends_with('.vsh') } +fn macos_v3_fastc_requested(prefs &pref.Preferences) bool { + mut selected_backend := '' + for option in prefs.build_options { + parts := option.fields() + if parts.len == 2 && parts[0] in ['-b', '-backend'] { + selected_backend = parts[1] + } + } + return selected_backend == 'fastc' +} + // These helpers are shared by the native Darwin dispatcher and the default // implementation selected while generating cross-platform VC sources, so this // file has to stay platform neutral (no `_darwin.c.v` suffix). Keep them outside diff --git a/cmd/v/macos_v3_darwin.c.v b/cmd/v/macos_v3_darwin.c.v index c74f70a116dd24..9579059c10f512 100644 --- a/cmd/v/macos_v3_darwin.c.v +++ b/cmd/v/macos_v3_darwin.c.v @@ -39,7 +39,7 @@ fn maybe_delegate_to_macos_v3(command string, prefs &pref.Preferences) ?MacosV3C } all_args := util.join_env_vflags_and_os_args() forwarded_args := all_args[1..] - if !is_macos_v3_default_executable(os.executable()) { + if !macos_v3_executable_can_dispatch(os.executable(), prefs) { trace_macos_v3_skip('non-default compiler executable `${os.executable()}`') return none } @@ -66,6 +66,10 @@ fn is_macos_v3_default_executable(vexe string) bool { return os.base(vexe) in ['v', 'v.exe', 'vnew', 'vnew.exe'] } +fn macos_v3_executable_can_dispatch(vexe string, prefs &pref.Preferences) bool { + return is_macos_v3_default_executable(vexe) || macos_v3_fastc_requested(prefs) +} + fn is_macos_v3_relevant_command(command string, prefs &pref.Preferences) bool { if prefs.old_compiler { return false diff --git a/cmd/v/macos_v3_default.c.v b/cmd/v/macos_v3_default.c.v index c00810b40de21b..441607be026724 100644 --- a/cmd/v/macos_v3_default.c.v +++ b/cmd/v/macos_v3_default.c.v @@ -8,8 +8,8 @@ import v.util // compiler normally. `-new-compiler` opts into the embedded V3 driver and runs // it in THIS process, the same way V3 runs by default on macOS. It applies the // same gating as the Darwin dispatcher: `-old-compiler` wins, V1-only options -// are rejected, and only actual compilation commands are taken over (never -// `fmt`, `version`, `test`, external tools, or the compiler bootstrap). +// are rejected, and only actual compilation commands are taken over. Compiler +// bootstrap targets are included only when the V3-only fastc backend is explicit. fn maybe_delegate_to_macos_v3(command string, prefs &pref.Preferences) ?MacosV3CErrorReport { if !prefs.new_compiler || prefs.old_compiler { // V1 is the default here, and `-old-compiler` takes precedence. diff --git a/cmd/v/macos_v3_test.v b/cmd/v/macos_v3_test.v index 9af213264bcd55..18113513cd5f87 100644 --- a/cmd/v/macos_v3_test.v +++ b/cmd/v/macos_v3_test.v @@ -1573,6 +1573,10 @@ fn test_macos_v3_default_executable_excludes_temporary_self_hosted_compilers() { assert !is_macos_v3_default_executable('/tmp/v2') assert !is_macos_v3_default_executable('/tmp/vstrict1') assert !is_macos_v3_default_executable('/tmp/vp') + assert !macos_v3_executable_can_dispatch('/tmp/v2', &pref.Preferences{}) + assert macos_v3_executable_can_dispatch('/tmp/v2', &pref.Preferences{ + build_options: ['-b fastc'] + }) } } @@ -1687,3 +1691,24 @@ fn test_macos_v3_new_compiler_routing_and_precedence() { path: 'main.v' }) } + +fn test_macos_v3_fastc_routes_compiler_selfhost_targets() { + for target in ['cmd/v', 'cmd/v/v.v', 'vlib/v3/v3.v'] { + assert macos_v3_force_requested('build', &pref.Preferences{ + new_compiler: true + path: target + build_options: ['-b fastc'] + }) + assert !macos_v3_force_requested('build', &pref.Preferences{ + new_compiler: true + path: target + build_options: ['-b c'] + }) + } + assert macos_v3_fastc_requested(&pref.Preferences{ + build_options: ['-backend fastc'] + }) + assert !macos_v3_fastc_requested(&pref.Preferences{ + build_options: ['-b fastc', '-b c'] + }) +} diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index b51b50017a6670..3dad6a780f0256 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -1131,7 +1131,12 @@ fn parse_args_impl(known_external_commands []string, args []string, show_output sbackend := cmdline.option(args[i..], arg, 'c') res.build_options << '${arg} ${sbackend}' b := backend_from_string(sbackend) or { - eprintln_exit('Unknown V backend: ${sbackend}\nValid -backend choices are: c, js, js_node, js_browser, js_freestanding, wasm') + eprintln_exit('Unknown V backend: ${sbackend}\nValid -backend choices are: c, fastc, js, js_node, js_browser, js_freestanding, wasm') + } + if sbackend == 'fastc' { + // FastC belongs to the embedded V3 driver. Keep V1's backend enum on C + // solely so cmd/v can finish parsing and forward the original arguments. + res.new_compiler = true } if b == .wasm { res.compile_defines << 'wasm' @@ -1439,7 +1444,7 @@ pub fn backend_from_string(s string) !Backend { // TODO: unify the "different js backend" options into a single `-b js` // + a separate option, to choose the wanted JS output. return match s { - 'c' { .c } + 'c', 'fastc' { .c } 'eval', 'interpret' { eprintln_exit('The eval backend has been removed.') } 'js', 'js_node' { .js_node } 'js_browser' { .js_browser } diff --git a/vlib/v/pref/pref_test.v b/vlib/v/pref/pref_test.v index bce52c06d2d13f..de5415c6b0f04a 100644 --- a/vlib/v/pref/pref_test.v +++ b/vlib/v/pref/pref_test.v @@ -553,6 +553,16 @@ fn test_new_compiler_flag_is_accepted() { assert '-new-compiler' !in prefs.build_options } +fn test_fastc_backend_selects_v3_driver() { + target := os.join_path(vroot, 'examples', 'hello_world.v') + prefs, command := pref.parse_args_and_show_errors([], ['-b', 'fastc', target], false) + assert command == target + assert prefs.backend == .c + assert prefs.backend_set_by_flag + assert prefs.new_compiler + assert prefs.build_options.contains('-b fastc') +} + fn test_v3_checker_fixture_flag_is_accepted() { target := os.join_path(vroot, 'examples', 'hello_world.v') for flag in ['-checker-fixture', '-macos-v3-compat-c99'] { diff --git a/vlib/v3/README.md b/vlib/v3/README.md index 7ff588c19a7fda..e0eb66a08765ef 100644 --- a/vlib/v3/README.md +++ b/vlib/v3/README.md @@ -3,11 +3,11 @@ Clean rewrite of the V compiler. Reuses v2's scanner, uses a flat AST parser with Pratt parsing, a structured type system with sum-type variants, lexical scoping, a transformer for AST simplification, a shared type-checking phase, a -markused pass for dead-code elimination, recursive import resolution, and three -backends: a direct flat-AST-to-C backend, a native ARM64 backend via SSA IR with -a built-in linker, and a direct flat-AST-to-WebAssembly backend. With `-prod`, -the ARM64 backend runs SSA optimization, MIR lowering, and instruction -selection. +markused pass for dead-code elimination, recursive import resolution, and +backends: a direct flat-AST-to-C backend, a scanner-to-C fast path, a native +ARM64 backend via SSA IR with a built-in linker, and a direct +flat-AST-to-WebAssembly backend. With `-prod`, the ARM64 backend runs SSA +optimization, MIR lowering, and instruction selection. Imports all `vlib/builtin/` V source files, both pure V (`.v`) and C-interop (`.c.v`), for struct, enum, type alias, interface, C function declarations, and @@ -88,6 +88,32 @@ Stage rows recorded at pipeline boundaries report sampled peak RSS and the proce breakdowns reconstructed after a stage omit the sampled peak. On macOS each row also prints physical footprint immediately after RSS. +## Fast C backend + +`-b fastc` selects the embedded V3 driver and a speculative single-file backend for the shortest +edit-run cycle. It scans the source once and emits GNU C while consuming tokens. This path does +not create a flat AST and does not run imports, type checking, transform, type annotation, or +mark-used. Bundled TinyCC compiles the emitted translation unit immediately. + +FastC currently emits primitive functions and parameters, inferred local declarations, ordinary +expressions, `if`/`else`, and condition, C-style, infinite, and integer-range `for` loops. GNU +`typeof` carries `:=` declarations into C without V type inference. Unsupported syntax silently +selects the normal C backend. A TinyCC error is also discarded and the original V source is +reparsed by the normal C backend, so the user receives V parser or type-checker diagnostics rather +than a speculative C diagnostic. A successfully compiled `run` program keeps its exit status and +is never retried. + +The direct path is limited to host-target, non-production, non-test, non-shared single-file builds. +Other modes select the normal C backend before source scanning. `-o file.c` emits the standalone +fast C translation unit without invoking TinyCC. + +`v self -b fastc` and direct compiler builds such as `v -b fastc -o v2 cmd/v` are routed to V3. +The compiler source is outside the direct subset, so fastc immediately selects the checked V3 C +backend for that build. The resulting self-hosted compiler retains fastc and can use the direct +path for supported user programs, including when its output has a custom filename in the V checkout. +The fastc integration test self-hosts the standalone V3 compiler through five successive generations +and verifies that the fifth generation still compiles and runs a direct-fastc program. + Generated C represents `thread` values with a typed wrapper around `pthread_t`. `spawn` and detached standard-library workers use the target's default thread stack (8 MiB on 64-bit targets and 2 MiB on 32-bit targets); `-thread-stack-size ` overrides it. Thread allocation, @@ -169,6 +195,9 @@ the plan and run the complete diagnostic and generation pipeline normally. ## Architecture ``` +source -> fastc scanner/emitter -> TinyCC + \-> on unsupported syntax or TinyCC error: normal pipeline below + source + vlib/builtin -> scanner -> flat parser -> flat AST -> imports -> check -> transform -> annotate types -> markused -> gen C -> cc \-> SSA build -> ARM64 gen -> link diff --git a/vlib/v3/driver/driver.v b/vlib/v3/driver/driver.v index 5a169204311703..54a4a842f0ec51 100644 --- a/vlib/v3/driver/driver.v +++ b/vlib/v3/driver/driver.v @@ -13,6 +13,7 @@ import v3.flat import v3.fixturetest import v3.gen.c as cgen import v3.gen.c.naming +import v3.gen.fastc import v3.markused import v3.modulecache import v3.parser @@ -2194,7 +2195,7 @@ fn v3_crun_build_identity(state &V3ModuleCacheState, prefs &pref.Preferences, us fn cli_usage() string { return 'usage: v3 [run|test] [options]\n' + ' -o output binary or C file\n' + - ' -b backend\n' + + ' -b backend\n' + ' -os -arch target platform\n' + ' -cc C compiler executable\n' + ' -thread-stack-size spawned-thread stack size\n' + @@ -6386,6 +6387,59 @@ fn add_v3_profile_used_fns(mut used_fns map[string]bool) { } } +struct V3FastCCompileResult { + success bool + command string + output string +} + +fn compile_v3_fastc_source(source string, bin_file string, prefs &pref.Preferences, environment_c_flags []string, user_c_flags []string, environment_ld_flags []string, is_debug bool) V3FastCCompileResult { + tcc_dir := os.join_path(prefs.vroot, 'thirdparty', 'tcc') + tcc_path := os.join_path_single(tcc_dir, 'tcc.exe') + if !os.is_executable(tcc_path) { + return V3FastCCompileResult{} + } + build_dir := os.join_path_single(os.dir(os.real_path(bin_file)), + '.${os.file_name(bin_file)}.fastc.${tempname.unique_token()}') + os.mkdir_all(build_dir) or { return V3FastCCompileResult{} } + defer { + cleanup_c_build_dir(build_dir) + } + source_file := os.join_path_single(build_dir, 'src.c') + staged_binary := os.join_path_single(build_dir, 'out') + os.write_file(source_file, source) or { return V3FastCCompileResult{} } + tcc_lib_dir := os.join_path_single(tcc_dir, 'lib') + mut cc_args := environment_c_flags.clone() + cc_args << ['-std=gnu11', '-I${os.join_path_single(tcc_lib_dir, 'include')}', '-L${tcc_lib_dir}', + '-w'] + if is_debug { + cc_args << '-g' + } + cc_args << ['-o', 'out', 'src.c'] + cc_args << user_c_flags + cc_args << '-lm' + cc_args << environment_ld_flags + command := cmdexec.display(tcc_path, cc_args) + result := cmdexec.run_in(tcc_path, cc_args, build_dir) + if result.exit_code != 0 || !os.is_file(staged_binary) { + return V3FastCCompileResult{ + command: command + output: result.output + } + } + os.mv(staged_binary, bin_file) or { + return V3FastCCompileResult{ + command: command + output: err.msg() + } + } + return V3FastCCompileResult{ + success: true + command: command + output: result.output + } +} + // run executes the V3 compiler driver with `args`. @[markused] pub fn run(args []string) { @@ -6488,6 +6542,7 @@ pub fn run(args []string) { mut is_debug := false mut is_c_debug := false mut c99 := false + mut c99_explicit := false mut thread_stack_size := 0 mut thread_stack_size_set := false mut all_backends := false @@ -6605,8 +6660,11 @@ pub fn run(args []string) { i++ } else if args[i] in ['-c99', '--c99', macos_v3_compat_c99_flag] { c99 = true - if args[i] != macos_v3_compat_c99_flag && 'c99' !in user_defines { - user_defines << 'c99' + if args[i] != macos_v3_compat_c99_flag { + c99_explicit = true + if 'c99' !in user_defines { + user_defines << 'c99' + } } i++ } else if args[i] in ['-strict', '-cstrict'] { @@ -6911,13 +6969,13 @@ pub fn run(args []string) { // `-no-bounds-checking`, matching the established parser contract. user_defines = user_defines.filter(it.all_before('=').trim_space() != 'no_bounds_checking') } - if is_prof && backend != 'c' { + if is_prof && backend !in ['c', 'fastc'] { eprintln('option `-profile` is only supported by the C backend') exit(1) } should_run = should_run && !skip_running - if is_o && (backend != 'c' || !explicit_output || (!output_file.ends_with('.c') - && !output_file.ends_with('.o'))) { + if is_o && (backend !in ['c', 'fastc'] || !explicit_output + || (!output_file.ends_with('.c') && !output_file.ends_with('.o'))) { eprintln('option `-is_o` requires the C backend and an explicit `.c` or `.o` output file') exit(1) } @@ -7042,8 +7100,8 @@ pub fn run(args []string) { eprintln('ownership support is not compiled into this v3 executable') exit(1) } - if backend !in ['c', 'arm64', 'wasm', 'eval'] { - eprintln('unknown backend `${backend}`; expected c, arm64, wasm, or eval') + if backend !in ['c', 'fastc', 'arm64', 'wasm', 'eval'] { + eprintln('unknown backend `${backend}`; expected c, fastc, arm64, wasm, or eval') exit(1) } if backend == 'arm64' && target_os != 'macos' && 'no_gettid' !in user_defines { @@ -7133,13 +7191,13 @@ pub fn run(args []string) { } else if backend == 'wasm' { // Honor the exact -o path; the wasm backend writes output_file directly. bin_file = output_file.all_before_last('.wasm') - } else if backend == 'c' && output_file == '-' { + } else if backend in ['c', 'fastc'] && output_file == '-' { c_only = true c_to_stdout = true bin_file = '' output_file = os.join_path_single(os.vtmp_dir(), 'v3_stdout_${os.getpid()}_${tempname.unique_token()}.c') - } else if backend == 'c' && output_file.ends_with('.c') { + } else if backend in ['c', 'fastc'] && output_file.ends_with('.c') { c_only = true bin_file = output_file.all_before_last('.c') } else { @@ -7149,7 +7207,7 @@ pub fn run(args []string) { } output_file = bin_file + '.c' } - if backend == 'c' { + if backend in ['c', 'fastc'] { target_bin_file := c_executable_bin_file_for_target(bin_file, target.os, is_shared, is_o, c_only) if target_bin_file != bin_file { @@ -7283,6 +7341,94 @@ pub fn run(args []string) { eprintln('v.pref.lookup_path: ${os.join_path(prefs.vroot, 'vlib')}') } prefs.supports_inline_asm = is_checker_fixture + if backend == 'fastc' { + fastc_host := pref.host_target() + fastc_eligible := input_file.ends_with('.v') && os.is_file(input_file) && file_list.len == 0 + && target.os == fastc_host.os && target.arch == fastc_host.arch && !is_test_command + && !is_checker_fixture && !is_prod && !is_shared && !is_livemain && !is_liveshared + && !is_o && !is_prof && coverage_dir.len == 0 && !ownership_mode && !only_check_syntax + && !check_only && print_fn_names.len == 0 && !print_v_files && !print_watched_files + && dump_c_flags.len == 0 && generate_c_project.len == 0 && !c99_explicit + && !c_compiler_explicit && !no_builtin && !no_preludes && !check_overflow + && !translated_mode && !is_repl + mut generated_fastc := false + mut fastc_source := '' + if fastc_eligible { + source := os.read_file(input_file) or { '' } + if source.len > 0 { + fastc_source = fastc.generate(source, input_file, prefs) or { '' } + generated_fastc = fastc_source.len > 0 + } + } + if generated_fastc { + b.step('fastc') + if c_only { + if c_to_stdout { + print(fastc_source) + } else { + os.write_file(output_file, fastc_source) or { + eprintln('error writing fastc output ${output_file}: ${err.msg()}') + exit(1) + } + } + b.metric('generated C size', fastc_source.len, 'bytes') + clear_macos_v3_compiler_error_fallback(macos_v3_fallback_file) + b.print_report() + return + } + fastc_result := compile_v3_fastc_source(fastc_source, bin_file, prefs, + environment_c_flags, user_c_flags, environment_ld_flags, is_debug) + if !silent || show_cc { + if fastc_result.command.len > 0 { + println(' > ${fastc_result.command}') + } + } + if show_c_output && fastc_result.output.len > 0 { + header := '======== Output of TinyCC fastc ========' + println(header) + println(fastc_result.output.trim_space()) + println('='.repeat(header.len)) + } + if fastc_result.success { + b.step('tcc') + b.metric('generated C size', fastc_source.len, 'bytes') + if backend_explicit { + os.write_file(bin_file + '.c', fastc_source) or { + eprintln('failed to retain generated fastc output ${bin_file}.c: ${err.msg()}') + exit(1) + } + } + if keep_c { + keep_c_file := keep_c_output_file(bin_file) + os.write_file(keep_c_file, fastc_source) or { + eprintln('failed to retain generated fastc output ${keep_c_file}: ${err.msg()}') + exit(1) + } + } + clear_macos_v3_compiler_error_fallback(macos_v3_fallback_file) + if should_run { + run_result := run_binary(bin_file, run_args) + if remove_binary_after_run { + os.rm(bin_file) or {} + } + if run_result != 0 { + exit(run_result) + } + b.step('run') + } + b.print_report() + return + } + b.step('tcc (fallback)') + } else { + b.step('fastc (fallback)') + } + // FastC deliberately reports no parser or TinyCC diagnostics. The checked C + // backend below reparses the original source and owns all user-facing errors. + clear_macos_v3_compiler_error_fallback(macos_v3_fallback_file) + backend = 'c' + prefs.backend = 'c' + } minimal_literal_output := !is_prof && input_uses_minimal_literal_output_builtin(input_file, prefs, is_test_command, is_checker_fixture) host_target := pref.host_target() diff --git a/vlib/v3/gen/fastc/fastc.v b/vlib/v3/gen/fastc/fastc.v new file mode 100644 index 00000000000000..5449f15fdacc19 --- /dev/null +++ b/vlib/v3/gen/fastc/fastc.v @@ -0,0 +1,605 @@ +module fastc + +import strings +import v3.pref +import v3.scanner +import v3.token + +const c_preamble = r'#include +#include +#include +#include + +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef intptr_t isize; +typedef uintptr_t usize; +typedef unsigned char byte; +typedef int32_t rune; +typedef float f32; +typedef double f64; +typedef const char *string; +typedef void *voidptr; +typedef unsigned char *byteptr; +typedef char *charptr; + +static void v_fastc_print_string(const char *value) { fputs(value, stdout); } +static void v_fastc_print_bool(bool value) { fputs(value ? "true" : "false", stdout); } +static void v_fastc_print_char(char value) { fputc(value, stdout); } +static void v_fastc_print_signed(long long value) { printf("%lld", value); } +static void v_fastc_print_unsigned(unsigned long long value) { printf("%llu", value); } +static void v_fastc_print_float(double value) { printf("%g", value); } +static void v_fastc_println_string(const char *value) { puts(value); } +static void v_fastc_println_bool(bool value) { puts(value ? "true" : "false"); } +static void v_fastc_println_char(char value) { fputc(value, stdout); fputc(10, stdout); } +static void v_fastc_println_signed(long long value) { printf("%lld\n", value); } +static void v_fastc_println_unsigned(unsigned long long value) { printf("%llu\n", value); } +static void v_fastc_println_float(double value) { printf("%g\n", value); } + +#define V_FASTC_PRINT_SELECT(value, string_fn, bool_fn, char_fn, signed_fn, unsigned_fn, float_fn) _Generic((value), char *: string_fn, const char *: string_fn, bool: bool_fn, char: char_fn, signed char: signed_fn, short: signed_fn, int: signed_fn, long: signed_fn, long long: signed_fn, unsigned char: unsigned_fn, unsigned short: unsigned_fn, unsigned int: unsigned_fn, unsigned long: unsigned_fn, unsigned long long: unsigned_fn, float: float_fn, double: float_fn)(value) +#define print(value) V_FASTC_PRINT_SELECT(value, v_fastc_print_string, v_fastc_print_bool, v_fastc_print_char, v_fastc_print_signed, v_fastc_print_unsigned, v_fastc_print_float) +#define println(value) V_FASTC_PRINT_SELECT(value, v_fastc_println_string, v_fastc_println_bool, v_fastc_println_char, v_fastc_println_signed, v_fastc_println_unsigned, v_fastc_println_float) +#define assert(value) do { if (!(value)) { fprintf(stderr, "assertion failed: %s\n", #value); abort(); } } while (0) + +' + +struct DirectGen { + path string +mut: + s scanner.Scanner + tok token.Token + lit string + out strings.Builder + protos strings.Builder + indent int +} + +// generate scans V source and emits C as each declaration and statement is consumed. It does +// not construct a flat AST or invoke semantic type checking. Unsupported syntax is returned as +// an error so the driver can retry the source with the normal C backend. +pub fn generate(source string, path string, prefs &pref.Preferences) !string { + mut file_set := token.FileSet.new() + mut file := file_set.add_file(path, source.len) + file.index_lines(source) + mut gen := DirectGen{ + path: path + s: scanner.new_scanner(prefs, .normal) + out: strings.new_builder(source.len) + protos: strings.new_builder(256) + } + gen.s.init(file, source) + return gen.run() +} + +fn (mut g DirectGen) run() !string { + g.next() + for g.tok != .eof { + g.skip_semicolons() + if g.tok == .eof { + break + } + if g.tok == .key_module { + g.parse_module()! + continue + } + if g.tok == .key_pub || g.tok == .key_static { + g.next() + } + if g.tok == .key_fn { + g.parse_function()! + continue + } + return g.unsupported('top-level `${g.token_source()}`') + } + mut result := strings.new_builder(c_preamble.len + g.protos.len + g.out.len + 2) + result.write_string(c_preamble) + result.write_string(g.protos.str()) + result.writeln('') + result.write_string(g.out.str()) + return result.str() +} + +fn (mut g DirectGen) next() { + g.tok = g.s.scan() + g.lit = g.s.lit +} + +fn (mut g DirectGen) skip_semicolons() { + for g.tok == .semicolon { + g.next() + } +} + +fn (g &DirectGen) unsupported(feature string) IError { + return error('fastc does not directly emit ${feature} in ${g.path}') +} + +fn (mut g DirectGen) expect(expected token.Token) ! { + if g.tok != expected { + return g.unsupported('`${expected.str()}` after `${g.token_source()}`') + } + g.next() +} + +fn (mut g DirectGen) parse_module() ! { + g.next() + if g.tok != .name { + return g.unsupported('module declaration') + } + // A direct single-file unit has no module namespace to resolve. `main` is + // accepted and discarded; every other module falls through to normal cgen. + if g.lit != 'main' { + return g.unsupported('module `${g.lit}`') + } + g.next() + g.skip_semicolons() +} + +fn (mut g DirectGen) parse_function() ! { + g.next() + if g.tok == .lpar { + return g.unsupported('methods') + } + if g.tok != .name { + return g.unsupported('function declaration') + } + name := g.lit + g.next() + if g.tok == .lsbr { + return g.unsupported('generic functions') + } + g.expect(.lpar)! + params := g.parse_parameters()! + mut return_type := 'void' + if g.tok != .lcbr { + return_type = g.parse_type()! + } + g.expect(.lcbr)! + is_main := name == 'main' && params.len == 0 + c_return_type := if is_main { 'int' } else { return_type } + c_params := if params.len == 0 { 'void' } else { params.join(', ') } + g.protos.writeln('${c_return_type} ${name}(${c_params});') + g.write_line('${c_return_type} ${name}(${c_params}) {') + g.indent++ + g.parse_block_body()! + if is_main { + g.write_line('return 0;') + } + g.indent-- + g.write_line('}') + g.out.writeln('') +} + +fn (mut g DirectGen) parse_parameters() ![]string { + mut params := []string{} + g.skip_semicolons() + for g.tok != .rpar { + if g.tok in [.key_mut, .key_shared] { + return g.unsupported('mutable or shared parameters') + } + if g.tok != .name { + return g.unsupported('function parameters') + } + name := g.lit + g.next() + if g.tok == .comma { + return g.unsupported('grouped parameter names') + } + type_name := g.parse_type()! + params << '${type_name} ${name}' + if g.tok == .comma { + g.next() + g.skip_semicolons() + continue + } + if g.tok != .rpar { + return g.unsupported('function parameter separator') + } + } + g.next() + return params +} + +fn (mut g DirectGen) parse_type() !string { + mut pointers := 0 + for g.tok == .amp || g.tok == .mul { + pointers++ + g.next() + } + if g.tok != .name { + return g.unsupported('type `${g.token_source()}`') + } + raw_type := g.lit + g.next() + if g.tok in [.dot, .lsbr, .question, .not] { + return g.unsupported('compound type `${raw_type}`') + } + base := match raw_type { + 'bool' { 'bool' } + 'byte' { 'byte' } + 'char' { 'char' } + 'f32' { 'f32' } + 'f64' { 'f64' } + 'i8' { 'i8' } + 'i16' { 'i16' } + 'i32' { 'i32' } + 'i64' { 'i64' } + 'int' { 'int' } + 'isize' { 'isize' } + 'rune' { 'rune' } + 'string' { 'string' } + 'u8' { 'u8' } + 'u16' { 'u16' } + 'u32' { 'u32' } + 'u64' { 'u64' } + 'uint' { 'unsigned int' } + 'usize' { 'usize' } + 'voidptr' { 'voidptr' } + 'byteptr' { 'byteptr' } + 'charptr' { 'charptr' } + else { raw_type } + } + return base + '*'.repeat(pointers) +} + +fn (mut g DirectGen) parse_block_body() ! { + g.skip_semicolons() + for g.tok != .rcbr { + if g.tok == .eof { + return g.unsupported('unfinished block') + } + g.parse_statement()! + g.skip_semicolons() + } + g.next() + g.skip_semicolons() +} + +fn (mut g DirectGen) parse_statement() ! { + match g.tok { + .key_if { + g.parse_if()! + } + .key_for { + g.parse_for()! + } + .key_return { + g.parse_return()! + } + .key_break { + g.next() + g.consume_statement_end() + g.write_line('break;') + } + .key_continue { + g.next() + g.consume_statement_end() + g.write_line('continue;') + } + .key_mut { + g.parse_mutable_declaration()! + } + .key_unsafe { + g.next() + g.expect(.lcbr)! + g.parse_block_body()! + } + else { + g.parse_simple_statement()! + } + } +} + +fn (mut g DirectGen) parse_if() ! { + g.next() + condition := g.read_expression([token.Token.lcbr])! + if condition.len == 0 { + return g.unsupported('empty if condition') + } + g.expect(.lcbr)! + g.write_line('if (${condition}) {') + g.indent++ + g.parse_block_body()! + g.indent-- + if g.tok != .key_else { + g.write_line('}') + return + } + g.next() + if g.tok == .key_if { + g.write_line('} else {') + g.indent++ + g.parse_if()! + g.indent-- + g.write_line('}') + return + } + g.expect(.lcbr)! + g.write_line('} else {') + g.indent++ + g.parse_block_body()! + g.indent-- + g.write_line('}') +} + +fn (mut g DirectGen) parse_for() ! { + g.next() + if g.tok == .lcbr { + g.next() + g.write_line('for (;;) {') + g.indent++ + g.parse_block_body()! + g.indent-- + g.write_line('}') + return + } + if g.tok == .name { + name := g.lit + g.next() + if g.tok == .key_in { + g.next() + start := g.read_expression([token.Token.dotdot])! + g.expect(.dotdot)! + end := g.read_expression([token.Token.lcbr])! + g.expect(.lcbr)! + g.write_line('for (__typeof__((${start})) ${name} = (${start}); ${name} < (${end}); ${name}++) {') + g.indent++ + g.parse_block_body()! + g.indent-- + g.write_line('}') + return + } + if g.tok == .decl_assign { + g.next() + initial := g.read_expression([token.Token.semicolon])! + g.expect(.semicolon)! + condition := g.read_expression([token.Token.semicolon])! + g.expect(.semicolon)! + update := g.read_expression([token.Token.lcbr])! + g.expect(.lcbr)! + g.write_line('for (__typeof__((${initial})) ${name} = (${initial}); ${condition}; ${update}) {') + g.indent++ + g.parse_block_body()! + g.indent-- + g.write_line('}') + return + } + condition := g.read_expression_with_prefix(name, [token.Token.lcbr])! + g.expect(.lcbr)! + g.write_line('while (${condition}) {') + g.indent++ + g.parse_block_body()! + g.indent-- + g.write_line('}') + return + } + condition := g.read_expression([token.Token.lcbr])! + g.expect(.lcbr)! + g.write_line('while (${condition}) {') + g.indent++ + g.parse_block_body()! + g.indent-- + g.write_line('}') +} + +fn (mut g DirectGen) parse_return() ! { + g.next() + if g.tok == .semicolon || g.tok == .rcbr { + g.consume_statement_end() + g.write_line('return;') + return + } + expression := g.read_expression([token.Token.semicolon, token.Token.rcbr])! + g.consume_statement_end() + g.write_line('return ${expression};') +} + +fn (mut g DirectGen) parse_mutable_declaration() ! { + g.next() + if g.tok != .name { + return g.unsupported('mutable declaration') + } + name := g.lit + g.next() + if g.tok != .decl_assign { + return g.unsupported('`mut` statement without `:=`') + } + g.parse_declaration_after_name(name)! +} + +fn (mut g DirectGen) parse_simple_statement() ! { + if g.tok == .key_assert { + g.next() + expression := g.read_expression([token.Token.semicolon, token.Token.rcbr])! + g.consume_statement_end() + g.write_line('assert(${expression});') + return + } + if g.tok == .name { + name := g.lit + g.next() + if g.tok == .decl_assign { + g.parse_declaration_after_name(name)! + return + } + expression := + g.read_expression_with_prefix(name, [token.Token.semicolon, token.Token.rcbr])! + g.consume_statement_end() + g.write_line('${expression};') + return + } + expression := g.read_expression([token.Token.semicolon, token.Token.rcbr])! + if expression.len == 0 { + return g.unsupported('statement `${g.token_source()}`') + } + g.consume_statement_end() + g.write_line('${expression};') +} + +fn (mut g DirectGen) parse_declaration_after_name(name string) ! { + g.next() + expression := g.read_expression([token.Token.semicolon, token.Token.rcbr])! + if expression.len == 0 { + return g.unsupported('empty declaration') + } + g.consume_statement_end() + // GNU typeof is unevaluated and is supported by bundled TinyCC. It lets the + // direct path preserve V's `:=` without running any inference or type checker. + if expression.starts_with('"') { + // C's typeof preserves a literal's array type instead of applying the usual + // pointer decay. The spelling alone is enough to lower this case. + g.write_line('string ${name} = (${expression});') + } else { + g.write_line('__typeof__((${expression})) ${name} = (${expression});') + } +} + +fn (mut g DirectGen) consume_statement_end() { + if g.tok == .semicolon { + g.next() + } +} + +fn (mut g DirectGen) read_expression(stops []token.Token) !string { + return g.read_expression_with_prefix('', stops) +} + +fn (mut g DirectGen) read_expression_with_prefix(prefix string, stops []token.Token) !string { + mut result := strings.new_builder(64) + if prefix.len > 0 { + result.write_string(prefix) + } + mut paren_depth := 0 + mut bracket_depth := 0 + for g.tok != .eof { + if paren_depth == 0 && bracket_depth == 0 && g.tok in stops { + break + } + if g.tok in [.lcbr, .rcbr, .str_dollar, .key_match, .key_or, .key_as, .key_is, .not_is, + .key_in, .not_in, .arrow, .power, .right_shift_unsigned] { + return g.unsupported('expression token `${g.token_source()}`') + } + piece := g.expression_token()! + if result.len > 0 && fastc_needs_space(result.last(), piece) { + result.write_u8(` `) + } + result.write_string(piece) + match g.tok { + .lpar { + paren_depth++ + } + .rpar { + if paren_depth == 0 { + break + } + paren_depth-- + } + .lsbr { + bracket_depth++ + } + .rsbr { + if bracket_depth == 0 { + break + } + bracket_depth-- + } + else {} + } + g.next() + } + if paren_depth != 0 || bracket_depth != 0 { + return g.unsupported('unbalanced expression') + } + return result.str().trim_space() +} + +fn (g &DirectGen) expression_token() !string { + return match g.tok { + .name { g.lit } + .number { g.lit.replace('_', '') } + .string { fastc_c_string(g.lit)! } + .char { fastc_c_char(g.lit)! } + .key_true { 'true' } + .key_false { 'false' } + .key_nil { 'NULL' } + .key_sizeof { 'sizeof' } + .key_likely, .key_unlikely { '' } + .semicolon { ';' } + else { g.tok.str() } + } +} + +fn (g &DirectGen) token_source() string { + if g.lit.len > 0 { + return g.lit + } + return g.tok.str() +} + +fn (mut g DirectGen) write_line(line string) { + for _ in 0 .. g.indent { + g.out.write_u8(`\t`) + } + g.out.writeln(line) +} + +fn fastc_needs_space(last u8, next string) bool { + if next.len == 0 { + return false + } + return (last.is_alnum() || last == `_`) && (next[0].is_alnum() || next[0] == `_`) +} + +fn fastc_c_string(literal string) !string { + if literal.len < 2 { + return error('invalid fastc string literal') + } + mut raw := literal + mut is_raw := false + if raw[0] == `r` && raw.len >= 3 { + is_raw = true + raw = raw[1..] + } + quote := raw[0] + if quote !in [`'`, `"`] || raw[raw.len - 1] != quote { + return error('interpolated or unfinished fastc string literal') + } + mut result := strings.new_builder(raw.len + 2) + result.write_u8(`"`) + mut i := 1 + for i < raw.len - 1 { + c := raw[i] + if c == `\\` && !is_raw && i + 1 < raw.len - 1 { + result.write_u8(c) + result.write_u8(raw[i + 1]) + i += 2 + continue + } else if c == `"` { + result.write_string('\\"') + } else if c == `\\` && is_raw { + result.write_string('\\\\') + } else { + result.write_u8(c) + } + i++ + } + result.write_u8(`"`) + return result.str() +} + +fn fastc_c_char(literal string) !string { + mut value := literal + if value.starts_with('c:') { + value = value[2..] + } + if value.len == 0 || value.contains("'") { + return error('unsupported fastc character literal') + } + return "'${value}'" +} diff --git a/vlib/v3/gen/fastc/fastc_test.v b/vlib/v3/gen/fastc/fastc_test.v new file mode 100644 index 00000000000000..f69492595812a4 --- /dev/null +++ b/vlib/v3/gen/fastc/fastc_test.v @@ -0,0 +1,61 @@ +module fastc + +import os +import v3.cmdexec +import v3.pref + +fn test_generate_and_compile_without_flat_ast() { + source := 'module main + +fn main() { + mut total := 0 + label := "total=" + for i := 0; i < 3; i++ { + total += twice(i) + } + if total == 6 { + print(label) + println(total) + } else { + println(0) + } +} + +fn twice(value int) int { + return value * 2 +} +' + prefs := pref.new_preferences() + c_source := generate(source, 'fastc_test.v', prefs) or { panic(err) } + assert c_source.contains('__typeof__((0)) total = (0);') + assert c_source.contains('string label = ("total=");') + assert c_source.contains('for (__typeof__((0)) i = (0); i<3; i++) {') + assert c_source.contains('int twice(int value);') + assert !c_source.contains('v3.flat') + + root := os.join_path(os.vtmp_dir(), 'v3_fastc_${os.getpid()}') + os.rmdir_all(root) or {} + os.mkdir_all(root) or { panic(err) } + defer { + os.rmdir_all(root) or {} + } + c_file := os.join_path(root, 'program.c') + bin_file := os.join_path(root, 'program') + os.write_file(c_file, c_source) or { panic(err) } + tcc := os.join_path(prefs.vroot, 'thirdparty', 'tcc', 'tcc.exe') + compile_result := cmdexec.run(tcc, ['-std=gnu11', '-o', bin_file, c_file]) + assert compile_result.exit_code == 0, compile_result.output + run_result := cmdexec.run(bin_file, []) + assert run_result.exit_code == 0, run_result.output + assert run_result.output.trim_space() == 'total=6' +} + +fn test_unsupported_import_requests_normal_backend() { + prefs := pref.new_preferences() + mut failed := false + _ := generate('module main\nimport os\nfn main() {}\n', 'imports.v', prefs) or { + failed = true + '' + } + assert failed +} diff --git a/vlib/v3/tests/fastc_backend_test.v b/vlib/v3/tests/fastc_backend_test.v new file mode 100644 index 00000000000000..2cedeb111f2bd4 --- /dev/null +++ b/vlib/v3/tests/fastc_backend_test.v @@ -0,0 +1,83 @@ +import os +import v3.cmdexec + +const fastc_backend_v3_dir = os.dir(os.dir(@FILE)) +const fastc_backend_vlib_dir = os.dir(fastc_backend_v3_dir) +const fastc_backend_v3_source = os.join_path(fastc_backend_v3_dir, 'v3.v') + +fn test_fastc_backend_and_checked_fallback() { + root := os.join_path(os.vtmp_dir(), 'v3_fastc_backend_${os.getpid()}') + os.rmdir_all(root) or {} + os.mkdir_all(root) or { panic(err) } + defer { + os.rmdir_all(root) or {} + } + v3_bin := os.join_path(root, 'v3') + build := cmdexec.run(@VEXE, ['-gc', 'none', '-path', '${fastc_backend_vlib_dir}|@vlib|@vmodules', + '-o', v3_bin, fastc_backend_v3_source]) + assert build.exit_code == 0, build.output + + valid_source := os.join_path(root, 'valid.v') + os.write_file(valid_source, 'module main + +fn twice(value int) int { + return value * 2 +} + +fn main() { + value := twice(21) + println(value) +} +') or { + panic(err) + } + valid_binary := os.join_path(root, 'valid') + valid_compile := cmdexec.run(v3_bin, ['-macos-v3-compat-c99', '-b', 'fastc', '-o', valid_binary, + valid_source]) + assert valid_compile.exit_code == 0, valid_compile.output + assert valid_compile.output.contains('fastc') + assert !valid_compile.output.contains(' parse '), valid_compile.output + retained_c := os.read_file(valid_binary + '.c') or { panic(err) } + assert retained_c.contains('__typeof__((twice(21))) value = (twice(21));') + assert !retained_c.contains('builtin__builtin_init') + valid_run := cmdexec.run(valid_binary, []) + assert valid_run.exit_code == 0, valid_run.output + assert valid_run.output.trim_space() == '42' + + invalid_source := os.join_path(root, 'invalid.v') + os.write_file(invalid_source, 'module main + +fn main() { + value := missing_name + println(value) +} +') or { + panic(err) + } + invalid_compile := cmdexec.run(v3_bin, ['-silent', '-b', 'fastc', '-o', + os.join_path(root, 'invalid'), invalid_source]) + assert invalid_compile.exit_code != 0 + assert invalid_compile.output.contains('undefined variable: `missing_name`'), invalid_compile.output + assert !invalid_compile.output.to_lower().contains('tcc:'), invalid_compile.output + + old_vjobs := os.getenv('VJOBS') + os.setenv('VJOBS', '4', true) + mut selfhosted_v3 := v3_bin + for level in 1 .. 6 { + next_v3 := os.join_path(root, 'v3_selfhosted_${level}') + selfhost := cmdexec.run(selfhosted_v3, ['-silent', '-nocache', '-no-memory-limit', + '-selfhost', '-b', 'fastc', '-o', next_v3, fastc_backend_v3_source]) + assert selfhost.exit_code == 0, 'fastc self-host level ${level}: ${selfhost.output}' + assert os.is_executable(next_v3) + selfhosted_v3 = next_v3 + } + os.setenv('VJOBS', old_vjobs, true) + + selfhosted_binary := os.join_path(root, 'selfhosted_valid') + selfhosted_compile := cmdexec.run(selfhosted_v3, ['-silent', '-b', 'fastc', '-o', + selfhosted_binary, valid_source]) + assert selfhosted_compile.exit_code == 0, selfhosted_compile.output + selfhosted_run := cmdexec.run(selfhosted_binary, []) + assert selfhosted_run.exit_code == 0, selfhosted_run.output + assert selfhosted_run.output.trim_space() == '42' +} From b21338c6359fac1ababb1fee1d0673703382321b Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 22 Aug 2026 03:33:09 +0300 Subject: [PATCH 02/32] v3: add full fastc backend --- cmd/v/macos_v3_args.c.v | 16 +- cmd/v/macos_v3_darwin.c.v | 7 +- cmd/v/macos_v3_test.v | 20 + cmd/v/v.v | 5 + vlib/v3/README.md | 32 +- vlib/v3/driver/driver.v | 280 +- vlib/v3/gen/fastc/array.v | 1824 ++ vlib/v3/gen/fastc/cleanc.v | 21834 ++++++++++++++++ vlib/v3/gen/fastc/coverage.v | 131 + vlib/v3/gen/fastc/fastc.v | 5 +- vlib/v3/gen/fastc/fn.v | 16507 ++++++++++++ .../gen/fastc/fn_parallel_d_v3_no_parallel.v | 72 + .../fastc/fn_parallel_notd_v3_no_parallel.v | 2948 +++ vlib/v3/gen/fastc/for.v | 746 + vlib/v3/gen/fastc/if.v | 830 + vlib/v3/gen/fastc/interface.v | 2456 ++ vlib/v3/gen/fastc/names.v | 328 + vlib/v3/gen/fastc/naming/naming.v | 380 + vlib/v3/gen/fastc/output_nix.c.v | 80 + vlib/v3/gen/fastc/profile.v | 196 + vlib/v3/gen/fastc/stmt.v | 7907 ++++++ vlib/v3/gen/fastc/str_intp.v | 398 + vlib/v3/gen/fastc/struct.v | 6018 +++++ vlib/v3/gen/fastc/types.v | 1933 ++ vlib/v3/markused/markused.v | 19 +- vlib/v3/transform/for.v | 6 +- 26 files changed, 64865 insertions(+), 113 deletions(-) create mode 100644 vlib/v3/gen/fastc/array.v create mode 100644 vlib/v3/gen/fastc/cleanc.v create mode 100644 vlib/v3/gen/fastc/coverage.v create mode 100644 vlib/v3/gen/fastc/fn.v create mode 100644 vlib/v3/gen/fastc/fn_parallel_d_v3_no_parallel.v create mode 100644 vlib/v3/gen/fastc/fn_parallel_notd_v3_no_parallel.v create mode 100644 vlib/v3/gen/fastc/for.v create mode 100644 vlib/v3/gen/fastc/if.v create mode 100644 vlib/v3/gen/fastc/interface.v create mode 100644 vlib/v3/gen/fastc/names.v create mode 100644 vlib/v3/gen/fastc/naming/naming.v create mode 100644 vlib/v3/gen/fastc/output_nix.c.v create mode 100644 vlib/v3/gen/fastc/profile.v create mode 100644 vlib/v3/gen/fastc/stmt.v create mode 100644 vlib/v3/gen/fastc/str_intp.v create mode 100644 vlib/v3/gen/fastc/struct.v create mode 100644 vlib/v3/gen/fastc/types.v diff --git a/cmd/v/macos_v3_args.c.v b/cmd/v/macos_v3_args.c.v index e80b21bb1ddfb2..f7e218f4309bb7 100644 --- a/cmd/v/macos_v3_args.c.v +++ b/cmd/v/macos_v3_args.c.v @@ -24,7 +24,7 @@ fn macos_v3_non_compilation_command(command string) bool { // precedence, options/modes V3 cannot honor yet, and whether the command is an // actual compilation command (never `test` or external tools). Compiler bootstrap // targets normally stay on V1, but explicit `-b fastc` owns those targets too: its -// direct emitter falls back to V3's checked C backend for the full compiler source. +// complete lane uses V3's checked frontend and full fastc generator for the compiler source. // Both the Darwin dispatcher (where it overrides the default heuristic) and the // non-macOS dispatcher (where it is the sole gate) rely on it, so it must stay // platform neutral. @@ -36,7 +36,9 @@ fn macos_v3_force_requested(command string, prefs &pref.Preferences) bool { if v3_has_v1_only_preferences(prefs) || (prefs.gc_set_by_flag && prefs.gc_mode != .no_gc) { return false } - if prefs.autofree && prefs.is_run { + if prefs.autofree && prefs.is_run && !macos_v3_fastc_requested(prefs) { + // V1 still owns the established `v -autofree run ...` orchestration. + // FastC promotes autofree programs to its checked C lane. return false } if prefs.path == '' || command == 'test' || macos_v3_non_compilation_command(command) @@ -65,6 +67,16 @@ fn macos_v3_fastc_requested(prefs &pref.Preferences) bool { return selected_backend == 'fastc' } +// macos_v3_test_ownership_uses_v1 keeps ownership/autofree test binaries on +// V1. vtest marks its per-file compilations with `-skip-running`; compiling an +// autofree test through the ownership-enabled V3 tool can consume far more +// memory than the test itself. Direct V3 ownership builds remain available; +// fastc uses the same checked ownership lane as the C backend. +fn macos_v3_test_ownership_uses_v1(prefs &pref.Preferences, args []string) bool { + return prefs.skip_running && !macos_v3_fastc_requested(prefs) + && (prefs.autofree || '-ownership' in args) +} + // These helpers are shared by the native Darwin dispatcher and the default // implementation selected while generating cross-platform VC sources, so this // file has to stay platform neutral (no `_darwin.c.v` suffix). Keep them outside diff --git a/cmd/v/macos_v3_darwin.c.v b/cmd/v/macos_v3_darwin.c.v index 9579059c10f512..27467705d37c5e 100644 --- a/cmd/v/macos_v3_darwin.c.v +++ b/cmd/v/macos_v3_darwin.c.v @@ -39,6 +39,10 @@ fn maybe_delegate_to_macos_v3(command string, prefs &pref.Preferences) ?MacosV3C } all_args := util.join_env_vflags_and_os_args() forwarded_args := all_args[1..] + if macos_v3_test_ownership_uses_v1(prefs, forwarded_args) { + trace_macos_v3_skip('vtest ownership/autofree compilation') + return take_macos_v3_c_error_report() + } if !macos_v3_executable_can_dispatch(os.executable(), prefs) { trace_macos_v3_skip('non-default compiler executable `${os.executable()}`') return none @@ -80,9 +84,10 @@ fn is_macos_v3_relevant_command(command string, prefs &pref.Preferences) bool { // dispatch, but it must not prevent V3 from being the default compiler. return false } - if prefs.autofree && prefs.is_run { + if prefs.autofree && prefs.is_run && !macos_v3_fastc_requested(prefs) { // V1 still owns the established `v -autofree run ...` orchestration. // Direct autofree builds are selected earlier by the ownership dispatcher. + // FastC promotes autofree programs to its checked C lane. return false } if command == 'test' { diff --git a/cmd/v/macos_v3_test.v b/cmd/v/macos_v3_test.v index 18113513cd5f87..59143b04701d9b 100644 --- a/cmd/v/macos_v3_test.v +++ b/cmd/v/macos_v3_test.v @@ -1711,4 +1711,24 @@ fn test_macos_v3_fastc_routes_compiler_selfhost_targets() { assert !macos_v3_fastc_requested(&pref.Preferences{ build_options: ['-b fastc', '-b c'] }) + assert macos_v3_force_requested('run', &pref.Preferences{ + new_compiler: true + autofree: true + is_run: true + path: 'main.v' + build_options: ['-b fastc'] + }) +} + +fn test_macos_v3_vtest_ownership_modes_use_v1_except_fastc() { + mut prefs := &pref.Preferences{ + skip_running: true + autofree: true + } + assert macos_v3_test_ownership_uses_v1(prefs, ['-skip-running', '-autofree', 'main.v']) + prefs.autofree = false + assert macos_v3_test_ownership_uses_v1(prefs, ['-skip-running', '-ownership', 'main.v']) + prefs.build_options = ['-b fastc'] + assert !macos_v3_test_ownership_uses_v1(prefs, ['-skip-running', '-ownership', '-b', + 'fastc', 'main.v']) } diff --git a/cmd/v/v.v b/cmd/v/v.v index 7151595a6dfe05..affb5fba022683 100644 --- a/cmd/v/v.v +++ b/cmd/v/v.v @@ -219,6 +219,11 @@ fn invoke_help_and_exit(remaining []string) { fn maybe_delegate_to_ownership(command string, prefs &pref.Preferences, merged_args []string) { is_ownership := '-ownership' in merged_args is_autofree := prefs.autofree + $if macos { + if macos_v3_test_ownership_uses_v1(prefs, merged_args) { + return + } + } if !ownership_delegation_is_requested(is_ownership, is_autofree, prefs.old_compiler, os.user_os()) { return diff --git a/vlib/v3/README.md b/vlib/v3/README.md index e0eb66a08765ef..4dff0bfa2a7e27 100644 --- a/vlib/v3/README.md +++ b/vlib/v3/README.md @@ -95,24 +95,28 @@ edit-run cycle. It scans the source once and emits GNU C while consuming tokens. not create a flat AST and does not run imports, type checking, transform, type annotation, or mark-used. Bundled TinyCC compiles the emitted translation unit immediately. -FastC currently emits primitive functions and parameters, inferred local declarations, ordinary -expressions, `if`/`else`, and condition, C-style, infinite, and integer-range `for` loops. GNU -`typeof` carries `:=` declarations into C without V type inference. Unsupported syntax silently -selects the normal C backend. A TinyCC error is also discarded and the original V source is -reparsed by the normal C backend, so the user receives V parser or type-checker diagnostics rather -than a speculative C diagnostic. A successfully compiled `run` program keeps its exit status and -is never retried. +FastC's direct lane currently emits primitive functions and parameters, inferred local +declarations, ordinary expressions, `if`/`else`, and condition, C-style, infinite, and +integer-range `for` loops. GNU +`typeof` carries `:=` declarations into C without V type inference. Unsupported syntax promotes +the source to fastc's complete lane, which uses the parser, checker, transformer, and mark-used +pass, then emits C with its own `v3.gen.fastc.FlatGen` backend. That backend is a full fork of the +V3 C generator rather than an alias or a runtime switch to `v3.gen.c`. A TinyCC error is also +discarded and the original V source is compiled by the checked fastc lane, so the user receives V +parser or type-checker diagnostics rather than a speculative C diagnostic. The complete lane has +the same language and ownership/autofree coverage as the C backend. A successfully compiled `run` +program keeps its exit status and is never retried. The direct path is limited to host-target, non-production, non-test, non-shared single-file builds. -Other modes select the normal C backend before source scanning. `-o file.c` emits the standalone -fast C translation unit without invoking TinyCC. +Compiler/self-host and other non-direct modes enter the complete lane before source scanning. +`-o file.c` emits the standalone fast C translation unit when the direct lane supports the input; +otherwise it emits the complete `v3.gen.fastc` translation unit. `v self -b fastc` and direct compiler builds such as `v -b fastc -o v2 cmd/v` are routed to V3. -The compiler source is outside the direct subset, so fastc immediately selects the checked V3 C -backend for that build. The resulting self-hosted compiler retains fastc and can use the direct -path for supported user programs, including when its output has a custom filename in the V checkout. -The fastc integration test self-hosts the standalone V3 compiler through five successive generations -and verifies that the fifth generation still compiles and runs a direct-fastc program. +Self-host builds enter fastc's complete lane directly. The checked frontend feeds the independent +`v3.gen.fastc.FlatGen` implementation, so the resulting compiler supports the full V3 source tree +and retains both fastc lanes for user programs, including when its output has a custom filename in +the V checkout. The fastc integration test exercises five successive self-host generations. Generated C represents `thread` values with a typed wrapper around `pthread_t`. `spawn` and detached standard-library workers use the target's default thread stack (8 MiB on 64-bit targets diff --git a/vlib/v3/driver/driver.v b/vlib/v3/driver/driver.v index 54a4a842f0ec51..3d731dd9cfed5c 100644 --- a/vlib/v3/driver/driver.v +++ b/vlib/v3/driver/driver.v @@ -6491,6 +6491,7 @@ pub fn run(args []string) { mut explicit_output := false mut backend := 'c' mut backend_explicit := false + mut fastc_full_codegen := false mut target_os := os.user_os() mut target_os_explicit := false mut target_arch := pref.host_arch() @@ -7345,12 +7346,12 @@ pub fn run(args []string) { fastc_host := pref.host_target() fastc_eligible := input_file.ends_with('.v') && os.is_file(input_file) && file_list.len == 0 && target.os == fastc_host.os && target.arch == fastc_host.arch && !is_test_command - && !is_checker_fixture && !is_prod && !is_shared && !is_livemain && !is_liveshared - && !is_o && !is_prof && coverage_dir.len == 0 && !ownership_mode && !only_check_syntax - && !check_only && print_fn_names.len == 0 && !print_v_files && !print_watched_files - && dump_c_flags.len == 0 && generate_c_project.len == 0 && !c99_explicit - && !c_compiler_explicit && !no_builtin && !no_preludes && !check_overflow - && !translated_mode && !is_repl + && !building_v && !is_selfhost && !is_checker_fixture && !is_prod && !is_shared + && !is_livemain && !is_liveshared && !is_o && !is_prof && coverage_dir.len == 0 + && !ownership_mode && !only_check_syntax && !check_only && print_fn_names.len == 0 + && !print_v_files && !print_watched_files && dump_c_flags.len == 0 + && generate_c_project.len == 0 && !c99_explicit && !c_compiler_explicit && !no_builtin + && !no_preludes && !check_overflow && !translated_mode && !is_repl mut generated_fastc := false mut fastc_source := '' if fastc_eligible { @@ -7419,13 +7420,19 @@ pub fn run(args []string) { b.print_report() return } - b.step('tcc (fallback)') + b.step('tcc (checked)') } else { - b.step('fastc (fallback)') + b.step('fastc (checked)') } - // FastC deliberately reports no parser or TinyCC diagnostics. The checked C - // backend below reparses the original source and owns all user-facing errors. + // FastC's complete lane uses the checked frontend and its own FlatGen backend. + // Only the speculative direct lane above skips semantic analysis. + // It deliberately reports no scanner-emitter or TinyCC diagnostics before + // promoting the source so user-facing errors still come from the V frontend. clear_macos_v3_compiler_error_fallback(macos_v3_fallback_file) + fastc_full_codegen = true + // The shared frontend uses C source suffixes and C compile-time conditions for + // both C-emitting backends. Keep that source-selection identity independent + // from the FlatGen implementation selected below. backend = 'c' prefs.backend = 'c' } @@ -9105,50 +9112,98 @@ pub fn run(args []string) { if !cgen_cache_hit && scope_prealloc_cgen && test_files.len == 0 { cgen_parse_cache_enabled := pre_tc.type_cache_parse_enabled() cgen_scope := prealloc_scope_begin_for_v3() - mut g := cgen.FlatGen.new() - g.set_initial_c_flags(user_c_flags) - g.set_c99_mode(prefs.c99) - g.set_ccompiler(prefs.ccompiler) - g.set_prod(prefs.is_prod) - g.set_check_overflow(check_overflow) - g.set_force_bounds_checking(prefs.force_bounds_checking) - g.set_prealloc('prealloc' in prefs.user_defines) - g.set_skip_generics(skip_transform_generics) - g.set_skip_enum_autostr(trivial_literal_output) - g.set_compiler_vexe(prefs.vexe) - g.set_compiler_vexe_env_setup(!pref.has_macos_v3_caller_environment()) - g.set_target(prefs.target) - g.set_thread_stack_size(prefs.thread_stack_size) - g.set_show_test_stats(show_test_stats) - g.set_show_test_summary(is_test_command) - g.set_test_run_only(run_only) - g.set_print_fn_names(print_fn_names) - g.set_profile(profile_file, profile_no_inline, profile_fns) - g.set_shared(prefs.is_shared) - g.set_object_file_mode(is_o) - g.set_suppress_main('no_main' in prefs.user_defines) - g.set_coverage(coverage_dir, args.join(' ')) - g.set_compile_values(prefs.compile_values) - g.set_cache_split(cache_state.manager.enabled) - g.set_cache_native_input_paths(cache_scoped_native_input_paths(cache_state)) - g.set_program_body_only(generic_cache_hit) - g.set_cache_program_files(user_files) - g.set_incremental_fn_names(incremental_changed_names) - g.set_cached_support_declarations(incremental_known_declarations) - g.set_scope_parallel_workers(!generic_cache_hit) + mut scoped_generated_c_flags := []string{} generated_path := if cache_state.manager.enabled { cache_plan_file } else { cc_src } - g.gen_to_file_with_used_test_options(generated_path, a, cgen_used_fns, &pre_tc, - cache_no_parallel_cgen, test_files) or { - eprintln('error writing ${generated_path}: ${err}') - cleanup_c_build_dir(cc_dir) - exit(1) + if fastc_full_codegen { + mut g := fastc.FlatGen.new() + g.set_initial_c_flags(user_c_flags) + g.set_c99_mode(prefs.c99) + g.set_ccompiler(prefs.ccompiler) + g.set_prod(prefs.is_prod) + g.set_check_overflow(check_overflow) + g.set_force_bounds_checking(prefs.force_bounds_checking) + g.set_prealloc('prealloc' in prefs.user_defines) + g.set_skip_generics(skip_transform_generics) + g.set_skip_enum_autostr(trivial_literal_output) + g.set_compiler_vexe(prefs.vexe) + g.set_compiler_vexe_env_setup(!pref.has_macos_v3_caller_environment()) + g.set_target(prefs.target) + g.set_thread_stack_size(prefs.thread_stack_size) + g.set_show_test_stats(show_test_stats) + g.set_show_test_summary(is_test_command) + g.set_test_run_only(run_only) + g.set_print_fn_names(print_fn_names) + g.set_profile(profile_file, profile_no_inline, profile_fns) + g.set_shared(prefs.is_shared) + g.set_object_file_mode(is_o) + g.set_suppress_main('no_main' in prefs.user_defines) + g.set_coverage(coverage_dir, args.join(' ')) + g.set_compile_values(prefs.compile_values) + g.set_cache_split(cache_state.manager.enabled) + g.set_cache_native_input_paths(cache_scoped_native_input_paths(cache_state)) + g.set_program_body_only(generic_cache_hit) + g.set_cache_program_files(user_files) + g.set_incremental_fn_names(incremental_changed_names) + g.set_cached_support_declarations(incremental_known_declarations) + g.set_scope_parallel_workers(!generic_cache_hit) + g.gen_to_file_with_used_test_options(generated_path, a, cgen_used_fns, &pre_tc, + cache_no_parallel_cgen, test_files) or { + eprintln('error writing ${generated_path}: ${err}') + cleanup_c_build_dir(cc_dir) + exit(1) + } + cgen_was_parallel = g.was_parallel() + if !incremental_cache_hit { + scoped_generated_c_flags = g.c_flags() + } + g.free_parallel_worker_scopes() + } else { + mut g := cgen.FlatGen.new() + g.set_initial_c_flags(user_c_flags) + g.set_c99_mode(prefs.c99) + g.set_ccompiler(prefs.ccompiler) + g.set_prod(prefs.is_prod) + g.set_check_overflow(check_overflow) + g.set_force_bounds_checking(prefs.force_bounds_checking) + g.set_prealloc('prealloc' in prefs.user_defines) + g.set_skip_generics(skip_transform_generics) + g.set_skip_enum_autostr(trivial_literal_output) + g.set_compiler_vexe(prefs.vexe) + g.set_compiler_vexe_env_setup(!pref.has_macos_v3_caller_environment()) + g.set_target(prefs.target) + g.set_thread_stack_size(prefs.thread_stack_size) + g.set_show_test_stats(show_test_stats) + g.set_show_test_summary(is_test_command) + g.set_test_run_only(run_only) + g.set_print_fn_names(print_fn_names) + g.set_profile(profile_file, profile_no_inline, profile_fns) + g.set_shared(prefs.is_shared) + g.set_object_file_mode(is_o) + g.set_suppress_main('no_main' in prefs.user_defines) + g.set_coverage(coverage_dir, args.join(' ')) + g.set_compile_values(prefs.compile_values) + g.set_cache_split(cache_state.manager.enabled) + g.set_cache_native_input_paths(cache_scoped_native_input_paths(cache_state)) + g.set_program_body_only(generic_cache_hit) + g.set_cache_program_files(user_files) + g.set_incremental_fn_names(incremental_changed_names) + g.set_cached_support_declarations(incremental_known_declarations) + g.set_scope_parallel_workers(!generic_cache_hit) + g.gen_to_file_with_used_test_options(generated_path, a, cgen_used_fns, &pre_tc, + cache_no_parallel_cgen, test_files) or { + eprintln('error writing ${generated_path}: ${err}') + cleanup_c_build_dir(cc_dir) + exit(1) + } + cgen_was_parallel = g.was_parallel() + if !incremental_cache_hit { + scoped_generated_c_flags = g.c_flags() + } + g.free_parallel_worker_scopes() } - cgen_was_parallel = g.was_parallel() - scoped_c_flags := g.c_flags() - g.free_parallel_worker_scopes() prealloc_scope_leave_for_v3(cgen_scope) if !incremental_cache_hit { - generated_c_flags = clone_string_list(scoped_c_flags) + generated_c_flags = clone_string_list(scoped_generated_c_flags) } // Cgen's synchronous type queries memoize through the shared checker. // Reattach empty parent-owned interners and caches before releasing its @@ -9157,46 +9212,89 @@ pub fn run(args []string) { pre_tc.set_fresh_type_cache(cgen_parse_cache_enabled) prealloc_scope_free_for_v3(cgen_scope) } else if !cgen_cache_hit { - mut g := cgen.FlatGen.new() - g.set_initial_c_flags(user_c_flags) - g.set_c99_mode(prefs.c99) - g.set_ccompiler(prefs.ccompiler) - g.set_prod(prefs.is_prod) - g.set_check_overflow(check_overflow) - g.set_force_bounds_checking(prefs.force_bounds_checking) - g.set_prealloc('prealloc' in prefs.user_defines) - g.set_skip_generics(skip_transform_generics) - g.set_skip_enum_autostr(trivial_literal_output) - g.set_compiler_vexe(prefs.vexe) - g.set_compiler_vexe_env_setup(!pref.has_macos_v3_caller_environment()) - g.set_target(prefs.target) - g.set_thread_stack_size(prefs.thread_stack_size) - g.set_show_test_stats(show_test_stats) - g.set_show_test_summary(is_test_command) - g.set_test_run_only(run_only) - g.set_print_fn_names(print_fn_names) - g.set_profile(profile_file, profile_no_inline, profile_fns) - g.set_shared(prefs.is_shared) - g.set_object_file_mode(is_o) - g.set_suppress_main('no_main' in prefs.user_defines) - g.set_coverage(coverage_dir, args.join(' ')) - g.set_compile_values(prefs.compile_values) - g.set_cache_split(cache_state.manager.enabled) - g.set_cache_native_input_paths(cache_scoped_native_input_paths(cache_state)) - g.set_program_body_only(generic_cache_hit) - g.set_cache_program_files(user_files) - g.set_incremental_fn_names(incremental_changed_names) - g.set_cached_support_declarations(incremental_known_declarations) generated_path := if cache_state.manager.enabled { cache_plan_file } else { cc_src } - g.gen_to_file_with_used_test_options(generated_path, a, cgen_used_fns, &pre_tc, - cache_no_parallel_cgen, test_files) or { - eprintln('error writing ${generated_path}: ${err}') - cleanup_c_build_dir(cc_dir) - exit(1) - } - cgen_was_parallel = g.was_parallel() - if !incremental_cache_hit { - generated_c_flags = g.c_flags() + if fastc_full_codegen { + mut g := fastc.FlatGen.new() + g.set_initial_c_flags(user_c_flags) + g.set_c99_mode(prefs.c99) + g.set_ccompiler(prefs.ccompiler) + g.set_prod(prefs.is_prod) + g.set_check_overflow(check_overflow) + g.set_force_bounds_checking(prefs.force_bounds_checking) + g.set_prealloc('prealloc' in prefs.user_defines) + g.set_skip_generics(skip_transform_generics) + g.set_skip_enum_autostr(trivial_literal_output) + g.set_compiler_vexe(prefs.vexe) + g.set_compiler_vexe_env_setup(!pref.has_macos_v3_caller_environment()) + g.set_target(prefs.target) + g.set_thread_stack_size(prefs.thread_stack_size) + g.set_show_test_stats(show_test_stats) + g.set_show_test_summary(is_test_command) + g.set_test_run_only(run_only) + g.set_print_fn_names(print_fn_names) + g.set_profile(profile_file, profile_no_inline, profile_fns) + g.set_shared(prefs.is_shared) + g.set_object_file_mode(is_o) + g.set_suppress_main('no_main' in prefs.user_defines) + g.set_coverage(coverage_dir, args.join(' ')) + g.set_compile_values(prefs.compile_values) + g.set_cache_split(cache_state.manager.enabled) + g.set_cache_native_input_paths(cache_scoped_native_input_paths(cache_state)) + g.set_program_body_only(generic_cache_hit) + g.set_cache_program_files(user_files) + g.set_incremental_fn_names(incremental_changed_names) + g.set_cached_support_declarations(incremental_known_declarations) + g.gen_to_file_with_used_test_options(generated_path, a, cgen_used_fns, &pre_tc, + cache_no_parallel_cgen, test_files) or { + eprintln('error writing ${generated_path}: ${err}') + cleanup_c_build_dir(cc_dir) + exit(1) + } + cgen_was_parallel = g.was_parallel() + if !incremental_cache_hit { + generated_c_flags = g.c_flags() + } + } else { + mut g := cgen.FlatGen.new() + g.set_initial_c_flags(user_c_flags) + g.set_c99_mode(prefs.c99) + g.set_ccompiler(prefs.ccompiler) + g.set_prod(prefs.is_prod) + g.set_check_overflow(check_overflow) + g.set_force_bounds_checking(prefs.force_bounds_checking) + g.set_prealloc('prealloc' in prefs.user_defines) + g.set_skip_generics(skip_transform_generics) + g.set_skip_enum_autostr(trivial_literal_output) + g.set_compiler_vexe(prefs.vexe) + g.set_compiler_vexe_env_setup(!pref.has_macos_v3_caller_environment()) + g.set_target(prefs.target) + g.set_thread_stack_size(prefs.thread_stack_size) + g.set_show_test_stats(show_test_stats) + g.set_show_test_summary(is_test_command) + g.set_test_run_only(run_only) + g.set_print_fn_names(print_fn_names) + g.set_profile(profile_file, profile_no_inline, profile_fns) + g.set_shared(prefs.is_shared) + g.set_object_file_mode(is_o) + g.set_suppress_main('no_main' in prefs.user_defines) + g.set_coverage(coverage_dir, args.join(' ')) + g.set_compile_values(prefs.compile_values) + g.set_cache_split(cache_state.manager.enabled) + g.set_cache_native_input_paths(cache_scoped_native_input_paths(cache_state)) + g.set_program_body_only(generic_cache_hit) + g.set_cache_program_files(user_files) + g.set_incremental_fn_names(incremental_changed_names) + g.set_cached_support_declarations(incremental_known_declarations) + g.gen_to_file_with_used_test_options(generated_path, a, cgen_used_fns, &pre_tc, + cache_no_parallel_cgen, test_files) or { + eprintln('error writing ${generated_path}: ${err}') + cleanup_c_build_dir(cc_dir) + exit(1) + } + cgen_was_parallel = g.was_parallel() + if !incremental_cache_hit { + generated_c_flags = g.c_flags() + } } } if incremental_cache_hit { @@ -9225,7 +9323,9 @@ pub fn run(args []string) { } } a.close_workers() - if cgen_cache_hit { + if fastc_full_codegen { + b.step_parallel('fastc gen', cgen_was_parallel) + } else if cgen_cache_hit { b.step('cgen (cached)') } else if incremental_cache_hit { b.step_parallel('cgen (incremental)', cgen_was_parallel) diff --git a/vlib/v3/gen/fastc/array.v b/vlib/v3/gen/fastc/array.v new file mode 100644 index 00000000000000..d36e4c1a5db94a --- /dev/null +++ b/vlib/v3/gen/fastc/array.v @@ -0,0 +1,1824 @@ +module fastc + +import v3.flat +import v3.gen.fastc.naming +import v3.types +import strings + +// array_like_type supports array like type handling for c. +fn array_like_type(t types.Type) ?types.Array { + if t is types.Array { + return t + } + if t is types.Alias { + base := t.base_type + if base is types.Array { + return base + } + } + return none +} + +// array_fixed_type supports array fixed type handling for c. +fn array_fixed_type(t types.Type) ?types.ArrayFixed { + if t is types.ArrayFixed { + return t + } + if t is types.Alias { + base := t.base_type + if base is types.ArrayFixed { + return base + } + } + return none +} + +fn fixed_array_pointer_type(t types.Type) ?types.ArrayFixed { + clean := if t is types.Alias { t.base_type } else { t } + if clean is types.Pointer { + return array_fixed_type(clean.base_type) + } + return none +} + +fn fixed_array_index_info(t types.Type) (bool, bool, types.ArrayFixed) { + if fixed := array_fixed_type(t) { + return true, false, fixed + } + if t is types.Pointer { + if fixed := array_fixed_type(t.base_type) { + return true, true, fixed + } + } + return false, false, types.ArrayFixed{} +} + +fn (g &FlatGen) fixed_array_type_from_alias_text(type_name string) ?types.ArrayFixed { + mut cur := trimmed_space(type_name) + for _ in 0 .. 16 { + if cur.len == 0 { + return none + } + if fixed := array_fixed_type(g.tc.parse_type(cur)) { + return fixed + } + mut next := g.tc.type_aliases[cur] or { '' } + if next.len == 0 { + qname := g.tc.qualify_name(cur) + next = g.tc.type_aliases[qname] or { '' } + } + if next.len == 0 || next == cur { + return none + } + cur = next + } + return none +} + +fn runtime_array_struct_type(t types.Type) bool { + if t is types.Struct { + return t.name == 'array' || t.name == 'Array' + } + if t is types.Alias { + return runtime_array_struct_type(t.base_type) + } + return false +} + +fn runtime_array_struct_index_info(t types.Type) (bool, bool) { + if runtime_array_struct_type(t) { + return true, false + } + if t is types.Pointer { + if runtime_array_struct_type(t.base_type) { + return true, true + } + } + return false, false +} + +// gen_array_literal_value emits array literal value output for c. +fn (mut g FlatGen) gen_array_literal_value(node flat.Node, elem_type types.Type) { + c_elem := g.value_c_type(elem_type) + sizeof_elem := g.value_sizeof_target(elem_type) + count := node.children_count + if count == 0 { + g.write('array_new(sizeof(${sizeof_elem}), 0, 0)') + return + } + new_fn := if count == 1 && array_literal_elem_can_use_noscan(elem_type) { + 'new_array_from_c_array_noscan' + } else { + 'new_array_from_c_array' + } + g.write('${new_fn}(${count}, ${count}, sizeof(${sizeof_elem}), (${c_elem}[]){') + for i in 0 .. count { + if i > 0 { + g.write(', ') + } + // Emit each element against the concrete element type, not the enclosing + // `expected_expr_type` (which is the whole array). A bare generic struct element + // (`Box{..}` in a `[]Box[int]` literal) otherwise sees the array type, fails the + // `generic_struct_init_instance_type` array skip, and is emitted as the bare `Box` + // while the array storage is `Box_int` — incompatible C. + child_id := g.a.child(&node, i) + if fixed := array_fixed_type(elem_type) { + initializer := g.fixed_array_initializer_string(child_id, fixed) + if initializer.len > 0 { + g.write(initializer) + continue + } + } + g.gen_expr_with_expected_type(child_id, elem_type) + } + g.write('})') +} + +fn array_literal_elem_can_use_noscan(elem_type types.Type) bool { + clean := cgen_unalias_type(elem_type) + return clean is types.Primitive || clean is types.Char || clean is types.Rune + || clean is types.ISize || clean is types.USize || clean is types.Enum +} + +fn (mut g FlatGen) gen_array_equality_literal_arg(names []string, arg_idx int, arg_id flat.NodeId, node flat.Node) bool { + if arg_idx !in [0, 1] || node.kind != .array_literal + || !names.any(it in ['array_eq_raw', 'array_eq_string', 'array_eq_array']) { + return false + } + if arr := array_like_type(g.usable_expr_type(arg_id)) { + g.gen_array_literal_value(node, arr.elem_type) + return true + } + if arr := array_like_type(g.parse_node_type(&node)) { + g.gen_array_literal_value(node, arr.elem_type) + return true + } + return false +} + +fn (mut g FlatGen) gen_array_literal_ptr_arg(node flat.Node, elem_type types.Type) { + c_elem := g.value_c_type(elem_type) + g.write('(${c_elem}[]){') + for i in 0 .. node.children_count { + if i > 0 { + g.write(', ') + } + g.gen_expr_with_expected_type(g.a.child(&node, i), elem_type) + } + if node.children_count == 0 { + g.write('0') + } + g.write('}') +} + +fn (mut g FlatGen) gen_fixed_array_literal_value(node flat.Node, fixed types.ArrayFixed) { + c_elem, dims := g.fixed_array_decl_parts(fixed) + g.write('(${c_elem}${dims}){') + for i in 0 .. node.children_count { + if i > 0 { + g.write(', ') + } + child_id := g.a.child(&node, i) + if elem_fixed := array_fixed_type(fixed.elem_type) { + initializer := g.fixed_array_initializer_string(child_id, elem_fixed) + if initializer.len > 0 { + g.write(initializer) + continue + } + } + g.gen_expr_with_expected_type(child_id, fixed.elem_type) + } + g.write('}') +} + +fn (mut g FlatGen) gen_pointer_arg_from_array_literal(node flat.Node, expected types.Type) bool { + if node.kind != .array_literal { + return false + } + if expected is types.Pointer { + g.gen_array_literal_ptr_arg(node, expected.base_type) + return true + } + return false +} + +// gen_fixed_array_data_arg emits fixed array data arg output for c. +fn (mut g FlatGen) gen_fixed_array_data_arg(id flat.NodeId, arr types.ArrayFixed) { + node := g.a.nodes[int(id)] + if node.kind == .cast_expr && node.value in ['voidptr', 'builtin.voidptr'] + && node.children_count > 0 { + g.gen_fixed_array_data_arg(g.a.child(&node, 0), arr) + return + } + if node.kind == .prefix && node.op == .amp && node.children_count > 0 { + child_id := g.a.child(&node, 0) + child := g.a.nodes[int(child_id)] + if child.kind == .ident { + if param_type := g.current_param_type(child.value) { + if array_fixed_type(param_type) != none { + g.gen_expr(child_id) + return + } + } + } + } + if node.kind in [.block, .expr_stmt] && node.children_count == 1 { + g.gen_fixed_array_data_arg(g.a.child(&node, 0), arr) + return + } + if node.kind == .array_literal { + if elem_fixed := array_fixed_type(arr.elem_type) { + mut needs_runtime_copy := false + for i in 0 .. node.children_count { + if g.fixed_array_initializer_string(g.a.child(&node, i), elem_fixed).len == 0 { + needs_runtime_copy = true + break + } + } + if needs_runtime_copy { + g.gen_nested_fixed_array_literal_copy(node, arr, elem_fixed) + return + } + } + c_elem := g.value_c_type(arr.elem_type) + g.write('(${c_elem}[]){') + for i in 0 .. node.children_count { + if i > 0 { + g.write(', ') + } + child_id := g.a.child(&node, i) + if elem_fixed := array_fixed_type(arr.elem_type) { + initializer := g.fixed_array_initializer_string(child_id, elem_fixed) + if initializer.len > 0 { + g.write(initializer) + continue + } + } + g.gen_expr(child_id) + } + g.write('}') + return + } + if node.kind in [.array_init, .struct_init] && node.children_count == 0 { + c_elem, dims := g.fixed_array_decl_parts(arr) + g.write('(${c_elem}${dims})') + g.write(g.empty_fixed_array_initializer_string(arr)) + return + } + if node.kind == .paren && node.children_count > 0 { + g.gen_fixed_array_data_arg(g.a.child(&node, 0), arr) + return + } + if node.kind == .dump_expr && node.children_count > 0 { + g.gen_fixed_array_data_arg(g.a.child(&node, 0), arr) + return + } + if node.kind in [.cast_expr, .as_expr] && node.children_count > 0 { + child_id := g.a.child(&node, 0) + child := g.a.nodes[int(child_id)] + if child.kind == .array_literal { + g.gen_fixed_array_data_arg(child_id, arr) + return + } + if child.kind == .postfix && child.children_count > 0 { + post_child_id := g.a.child(&child, 0) + if g.a.nodes[int(post_child_id)].kind == .array_literal { + g.gen_fixed_array_data_arg(post_child_id, arr) + return + } + } + } + if node.kind == .postfix && node.children_count > 0 { + child_id := g.a.child(&node, 0) + child := g.a.nodes[int(child_id)] + if child.kind == .array_literal { + g.gen_fixed_array_data_arg(child_id, arr) + return + } + } + annotated_is_fixed := node.typ.len > 0 && array_fixed_type(g.parse_node_type(&node)) != none + if !annotated_is_fixed + && fixed_array_option_payload_type(types.unwrap_pointer(g.usable_expr_type(id))) != none { + g.gen_expr(id) + g.write('.value') + return + } + // A fixed-array value (e.g. `[4]u8` color) is sometimes represented as a dynamic + // `Array`; a C fixed-array parameter decays to `elem*`, so pass the data pointer. + if types.unwrap_pointer(g.usable_expr_type(id)) is types.Array { + elem_ct := g.fixed_array_elem_c_type(arr.elem_type) + g.write('(${elem_ct}*)(') + g.gen_expr(id) + g.write(').data') + return + } + g.gen_expr(id) +} + +fn (mut g FlatGen) gen_new_array_fixed_data_arg(call flat.Node, arg_start int, arg_idx int, arg_id flat.NodeId, names []string) bool { + if arg_idx != 3 || !names.any(it in ['new_array_from_c_array', 'array__new_array_from_c_array']) + || arg_start + 2 >= call.children_count { + return false + } + sizeof_id := g.a.child(&call, arg_start + 2) + sizeof_node := g.a.nodes[int(sizeof_id)] + if sizeof_node.kind != .sizeof_expr || sizeof_node.value.len == 0 { + return false + } + elem_type := g.tc.parse_type(sizeof_node.value) + g.gen_fixed_array_data_arg(arg_id, types.ArrayFixed{ + elem_type: elem_type + }) + return true +} + +// gen_nested_fixed_array_literal_copy materializes nested fixed-array elements that cannot be +// represented as C initializers, such as calls returning fixed-array wrapper structs. +fn (mut g FlatGen) gen_nested_fixed_array_literal_copy(node flat.Node, arr types.ArrayFixed, elem_fixed types.ArrayFixed) { + c_elem, dims := g.fixed_array_decl_parts(arr) + tmp := g.tmp_name() + g.write('({ ${c_elem} ${tmp}${dims} = {0}; ') + for i in 0 .. node.children_count { + g.write('memmove(${tmp}[${i}], ') + child_id := g.a.child(&node, i) + literal := g.fixed_array_compound_literal_expr(child_id, elem_fixed) + if trimmed_space(literal).len > 0 { + g.write(literal) + } else { + g.gen_fixed_array_data_arg(child_id, elem_fixed) + } + g.write(', sizeof(${tmp}[${i}])); ') + } + g.write('${tmp}; })') +} + +fn fixed_array_option_payload_type(typ types.Type) ?types.ArrayFixed { + if typ is types.OptionType { + return array_fixed_type(typ.base_type) + } + if typ is types.ResultType { + return array_fixed_type(typ.base_type) + } + return none +} + +fn (mut g FlatGen) gen_fixed_array_init_expr(init_id flat.NodeId, elem_type types.Type, index int, uses_index bool) { + if !uses_index { + g.gen_expr_with_expected_type(init_id, elem_type) + return + } + g.write('({ int index = ${index}; ') + g.gen_expr_with_expected_type(init_id, elem_type) + g.write('; })') +} + +fn (g &FlatGen) node_contains_ident(id flat.NodeId, name string) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(id)] + if node.kind == .ident && node.value == name { + return true + } + for i in 0 .. node.children_count { + if g.node_contains_ident(g.a.child(&node, i), name) { + return true + } + } + return false +} + +fn (g &FlatGen) array_init_field_value(node flat.Node, field_name string) ?flat.NodeId { + for i in 0 .. node.children_count { + field := g.a.child_node(&node, i) + if field.kind == .field_init && field.value == field_name && field.children_count > 0 { + return g.a.child(field, 0) + } + } + return none +} + +fn (mut g FlatGen) gen_fixed_array_pointer_lvalue_arg(id flat.NodeId, expected types.Type) bool { + if _ := fixed_array_pointer_type(expected) { + // handled below + } else { + return false + } + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(id)] + mut actual := g.tc.resolve_type(id) + if node.kind == .ident { + if param_type := g.current_param_type(node.value) { + actual = param_type + } + } + if actual is types.Pointer { + return false + } + if _ := array_fixed_type(actual) { + if !g.expr_is_addressable(id) { + return false + } + g.write('&') + g.gen_expr(id) + return true + } + return false +} + +// gen_array_push_many_stmt emits array push many stmt output for c. +fn (mut g FlatGen) gen_array_push_many_stmt(lhs_id flat.NodeId, rhs_id flat.NodeId) { + lhs_is_ptr := g.tc.resolve_type(lhs_id) is types.Pointer + amp := if lhs_is_ptr { '' } else { '&' } + rhs_type := types.unwrap_pointer(g.tc.resolve_type(rhs_id)) + if rhs_fixed := array_fixed_type(rhs_type) { + g.write('array_push_many_ptr(${amp}') + gen_expr_lvalue(mut g, lhs_id) + g.write(', ') + g.gen_fixed_array_data_arg(rhs_id, rhs_fixed) + len_expr := g.fixed_array_len_value(rhs_fixed) + g.writeln(', ${len_expr});') + return + } + tmp := g.tmp_name() + g.write('{ Array ${tmp} = ') + g.gen_expr(rhs_id) + g.writeln(';') + g.write('array__push_many(${amp}') + gen_expr_lvalue(mut g, lhs_id) + g.writeln(', ${tmp}.data, ${tmp}.len); }') +} + +// gen_slice_expr emits slice expr output for c. +fn (mut g FlatGen) gen_slice_expr(node flat.Node, base_id flat.NodeId, base_type types.Type) { + start_node := g.a.child_node(&node, 1) + has_start := start_node.kind != .empty + has_end := node.children_count > 2 + base_str := g.expr_to_string(base_id) + base_unptr := types.unwrap_pointer(base_type) + base_is_string := base_unptr is types.String + base_value_str := if base_type is types.Pointer && base_is_string { + '*(${base_str})' + } else { + base_str + } + is_array, is_ptr, _ := array_index_info(base_type) + is_fixed_array, fixed_is_ptr, fixed := fixed_array_index_info(base_type) + start_str := if has_start { g.expr_to_string(g.a.child(&node, 1)) } else { '0' } + end_str := if has_end { + g.expr_to_string(g.a.child(&node, 2)) + } else if is_fixed_array { + g.fixed_array_len_value(fixed) + } else if is_array && is_ptr { + '(${base_str})->len' + } else { + '(${base_value_str}).len' + } + gated := node.op == .gated_index + if base_is_string { + if gated { + g.write('string__substr_ni(${base_value_str}, ${start_str}, ${end_str})') + } else { + g.write('string__substr(${base_value_str}, ${start_str}, ${end_str})') + } + } else if is_fixed_array { + c_elem := g.fixed_array_elem_c_type(fixed.elem_type) + mut data_str := if fixed_is_ptr { '(*${base_str})' } else { base_str } + base_node := g.a.nodes[int(base_id)] + local_fixed_array := base_node.kind == .ident + && g.const_ref_name_from_node(base_node).len == 0 + literal := if local_fixed_array { + '' + } else { + g.fixed_array_compound_literal_expr(base_id, fixed) + } + if trimmed_space(literal).len > 0 { + data_str = literal + } + if gated { + // Route gated fixed-array slices through the clamped slice_ni on a + // heap copy of the fixed data, matching dynamic-array semantics. + len_val := g.fixed_array_len_value(fixed) + g.write('array__slice_ni(new_array_from_c_array(${len_val}, ${len_val}, sizeof(${c_elem}), &(${data_str})[0]), ${start_str}, ${end_str})') + return + } + // Evaluate the slice bounds once so side-effecting expressions such as + // `arr[i++..limit()]` are not run multiple times in the generated C. + start_tmp := g.tmp_name() + count_tmp := g.tmp_name() + g.write('({ int ${start_tmp} = (${start_str}); int ${count_tmp} = (${end_str}) - ${start_tmp}; new_array_from_c_array(${count_tmp}, ${count_tmp}, sizeof(${c_elem}), &(${data_str})[${start_tmp}]); })') + } else if is_array { + arr_str := if is_ptr { '*${base_str}' } else { base_str } + if gated { + g.write('array__slice_ni(${arr_str}, ${start_str}, ${end_str})') + } else { + g.write('array_slice(${arr_str}, ${start_str}, ${end_str})') + } + } else { + if gated { + g.write('string__substr_ni(${base_str}, ${start_str}, ${end_str})') + } else { + g.write('string__substr(${base_str}, ${start_str}, ${end_str})') + } + } +} + +// gen_array_method_call emits array method call output for c. +fn (mut g FlatGen) gen_array_method_call(node flat.Node, fn_node &flat.Node, arr types.Array) { + base_id := g.a.child(fn_node, 0) + mut elem_type := arr.elem_type + base_expr_type := g.usable_expr_type(base_id) + receiver_type0 := if g.a.nodes[int(base_id)].kind == .call { + declared := g.declared_call_return_type(base_id) + if declared !is types.Unknown && declared !is types.Void { + declared + } else { + base_expr_type + } + } else { + base_expr_type + } + receiver_type := types.unwrap_pointer(receiver_type0) + if receiver_arr := array_like_type(receiver_type) { + elem_type = receiver_arr.elem_type + } + c_elem := g.value_c_type(elem_type) + base_node := g.a.nodes[int(base_id)] + // A receiver already yields a pointer (e.g. `arc.Arc[[]T].get()` returns + // `&[]T`) for any expression kind, not just idents. Detect it uniformly so + // `clone` does not take the address of a pointer rvalue (`array__clone(&p)`) + // and element accessors dereference correctly. + is_ptr := g.usable_expr_type(base_id) is types.Pointer + dot := if is_ptr { '->' } else { '.' } + match fn_node.value { + 'clone' { + g.write('array__clone(') + if !is_ptr { + g.write('&') + } + g.gen_expr(base_id) + g.write(')') + } + 'last' { + g.write('*(${c_elem}*)array_get(') + if is_ptr { + g.write('*') + } + g.gen_expr(base_id) + g.write(', ') + g.gen_expr(base_id) + g.write('${dot}len - 1)') + } + 'first' { + g.write('*(${c_elem}*)array_get(') + if is_ptr { + g.write('*') + } + g.gen_expr(base_id) + g.write(', 0)') + } + 'delete_last' { + g.write('array__delete_last(') + if !is_ptr { + g.write('&') + } + g.gen_expr(base_id) + g.write(')') + } + 'pop' { + amp := if is_ptr { '' } else { '&' } + g.write('*(${c_elem}*)array__pop(${amp}') + g.gen_expr(base_id) + g.write(')') + } + 'pop_left' { + amp := if is_ptr { '' } else { '&' } + g.write('*(${c_elem}*)array__pop_left(${amp}') + g.gen_expr(base_id) + g.write(')') + } + 'clear' { + amp := if is_ptr { '' } else { '&' } + g.write('array__clear(${amp}') + g.gen_expr(base_id) + g.write(')') + } + 'push_many' { + amp := if is_ptr { '' } else { '&' } + g.write('array_push_many_ptr(${amp}') + g.gen_expr(base_id) + g.write(', ') + g.gen_expr(g.a.child(&node, 1)) + g.write(', ') + g.gen_expr(g.a.child(&node, 2)) + g.write(')') + } + 'repeat' { + g.write('array__repeat_to_depth(') + g.gen_expr(base_id) + g.write(', ') + g.gen_expr(g.a.child(&node, 1)) + g.write(', 0)') + } + 'repeat_to_depth' { + g.write('array__repeat_to_depth(') + g.gen_expr(base_id) + g.write(', ') + g.gen_expr(g.a.child(&node, 1)) + g.write(', ') + g.gen_expr(g.a.child(&node, 2)) + g.write(')') + } + 'trim' { + amp := if is_ptr { '' } else { '&' } + g.write('array__trim(${amp}') + g.gen_expr(base_id) + g.write(', ') + g.gen_expr(g.a.child(&node, 1)) + g.write(')') + } + 'ensure_cap' { + amp := if is_ptr { '' } else { '&' } + g.write('array_ensure_cap(${amp}') + g.gen_expr(base_id) + g.write(', ') + g.gen_expr(g.a.child(&node, 1)) + g.write(')') + } + 'delete' { + amp := if is_ptr { '' } else { '&' } + g.write('array_delete(${amp}') + g.gen_expr(base_id) + g.write(', ') + g.gen_expr(g.a.child(&node, 1)) + g.write(')') + } + 'prepend' { + amp := if is_ptr { '' } else { '&' } + g.write('array__prepend(${amp}') + g.gen_expr(base_id) + g.write(', &(${c_elem}[]){') + g.gen_expr(g.a.child(&node, 1)) + g.write('})') + } + 'free' { + g.write('array__free(') + if !is_ptr { + g.write('&') + } + g.gen_expr(base_id) + g.write(')') + } + 'pointers' { + g.gen_array_pointers_expr(base_id, is_ptr) + } + 'str' { + amp := if is_ptr { '' } else { '&' } + g.write('strings__Builder__str(${amp}') + g.gen_expr(base_id) + g.write(')') + } + 'join' { + g.write('Array_string__join(') + g.gen_expr_with_expected_type(base_id, types.Type(arr)) + g.write(', ') + g.gen_expr(g.a.child(&node, 1)) + g.write(')') + } + 'bytestr' { + g.write('u8__vstring_with_len((u8*)') + g.gen_expr(base_id) + g.write('${dot}data, ') + g.gen_expr(base_id) + g.write('${dot}len)') + } + 'to_fixed_size' { + if base_node.kind == .array_literal { + g.gen_array_literal_ptr_arg(base_node, elem_type) + } else { + if is_ptr { + g.write('(${c_elem}*)') + } + g.gen_expr(base_id) + g.write('${dot}data') + } + } + 'contains' { + contains_fn := 'array_contains_${array_lookup_suffix(arr.elem_type)}' + g.write('${contains_fn}(') + g.gen_expr(base_id) + g.write(', ') + g.gen_expr(g.a.child(&node, 1)) + g.write(')') + } + 'index' { + index_fn := 'array_index_${array_lookup_suffix(arr.elem_type)}' + g.write('${index_fn}(') + g.gen_expr(base_id) + g.write(', ') + g.gen_expr(g.a.child(&node, 1)) + g.write(')') + } + 'last_index' { + if suffix := array_last_index_suffix(arr.elem_type) { + index_fn := 'array_last_index_${suffix}' + g.write('${index_fn}(') + g.gen_expr(base_id) + g.write(', ') + g.gen_expr(g.a.child(&node, 1)) + g.write(')') + } else { + g.write('array_last_index_raw(') + g.gen_expr(base_id) + g.write(', &(${c_elem}[]){') + g.gen_expr_with_expected_type(g.a.child(&node, 1), arr.elem_type) + g.write('})') + } + } + 'hex' { + g.write('Array_u8__hex(') + if base_node.kind == .array_literal { + g.gen_expr_with_expected_type(base_id, types.Type(types.Array{ + elem_type: types.Type(types.u8_) + })) + } else { + g.gen_expr(base_id) + } + g.write(')') + } + 'wait' { + // Only a thread array supports `.wait()` (joining every spawned thread and, + // for non-void payloads, collecting their return values into a fresh `[]T`). + // The element carries the thread payload in its name (`thread`/`thread T`). + // Any other element type is not a thread, so route it through the normal + // method fallback instead of joining arbitrary array data as pthread_t handles. + mut is_thread := false + elem := arr.elem_type + if elem is types.Struct { + tn := trimmed_space(elem.name) + is_thread = tn == 'thread' || tn.starts_with('thread ') + } + if is_thread { + g.gen_thread_array_wait(base_id, is_ptr, arr.elem_type) + } else { + g.gen_array_method_call_fallback(node, fn_node.value, base_id, is_ptr, arr) + } + } + else { + g.gen_array_method_call_fallback(node, fn_node.value, base_id, is_ptr, arr) + } + } +} + +fn (mut g FlatGen) to_fixed_size_call_fixed_type(id flat.NodeId) ?types.ArrayFixed { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return none + } + node := g.a.nodes[int(id)] + if node.kind != .call || node.children_count == 0 { + return none + } + fn_node := g.a.child_node(&node, 0) + if fn_node.kind != .selector || fn_node.value != 'to_fixed_size' || fn_node.children_count == 0 { + return none + } + if node.typ.len > 0 { + if fixed := array_fixed_type(g.parse_node_type(&node)) { + return fixed + } + } + base_id := g.a.child(fn_node, 0) + base := g.a.nodes[int(base_id)] + base_type := types.unwrap_pointer(g.usable_expr_type(base_id)) + arr := array_like_type(base_type) or { return none } + len := if base.kind == .array_literal { + int(base.children_count) + } else { + 0 + } + if len <= 0 { + return none + } + return types.ArrayFixed{ + elem_type: arr.elem_type + len: len + len_expr: '${len}' + } +} + +// gen_array_method_call_fallback emits a call for an array method that has no dedicated +// codegen arm: it resolves a `[]T.method` function when one is registered, and otherwise +// emits the selector itself as a direct call. Shared by the catch-all `else` arm and by +// `.wait()` on non-thread arrays (which is unsupported and falls through here rather than +// joining elements as thread handles). +fn (mut g FlatGen) gen_array_method_call_fallback(node flat.Node, mname string, base_id flat.NodeId, is_ptr bool, arr types.Array) { + best_mname := g.array_method_fallback_for_receiver(mname, base_id, arr) + if best_mname.len > 0 { + g.write(g.cname(best_mname)) + g.write('(') + ptypes := g.tc.fn_param_types[best_mname] + wants_ptr := ptypes.len > 0 && ptypes[0] is types.Pointer + if wants_ptr && !is_ptr { + g.write('&') + } else if !wants_ptr && is_ptr { + g.write('*') + } + g.gen_expr(base_id) + for i in 1 .. node.children_count { + g.write(', ') + g.gen_expr(g.a.child(&node, i)) + } + g.write(')') + } else { + g.gen_expr(g.a.child(&node, 0)) + g.write('(') + g.gen_expr(base_id) + g.write(')') + } +} + +// gen_array_pointers_expr emits `array.pointers()` without compiling the erased +// raw `array` builtin body, which has no concrete element type in v3 Cgen. +fn (mut g FlatGen) gen_array_pointers_expr(base_id flat.NodeId, is_ptr bool) { + base_type := types.unwrap_pointer(g.tc.resolve_type(base_id)) + if fixed := array_fixed_type(base_type) { + g.gen_fixed_array_pointers_expr(base_id, is_ptr, fixed) + return + } + tmp := g.tmp_count + g.tmp_count++ + src_name := '__arr_ptrs_src_${tmp}' + res_name := '__arr_ptrs_res_${tmp}' + idx_name := '__arr_ptrs_i_${tmp}' + g.write('({ Array ${src_name} = ') + if is_ptr { + g.write('*') + } + g.gen_expr(base_id) + g.write('; Array ${res_name} = array_new(sizeof(voidptr), ${src_name}.len, ${src_name}.len); for (int ${idx_name} = 0; ${idx_name} < ${src_name}.len; ${idx_name}++) { ((voidptr*)${res_name}.data)[${idx_name}] = (voidptr)((u8*)${src_name}.data + ((u64)${idx_name} * (u64)${src_name}.element_size)); } ${res_name}; })') +} + +fn (mut g FlatGen) gen_fixed_array_pointers_expr(base_id flat.NodeId, is_ptr bool, fixed types.ArrayFixed) { + tmp := g.tmp_count + g.tmp_count++ + src_name := '__arr_ptrs_fixed_src_${tmp}' + res_name := '__arr_ptrs_res_${tmp}' + idx_name := '__arr_ptrs_i_${tmp}' + len_text := g.fixed_array_len_value(fixed) + if !is_ptr && !g.expr_is_addressable(base_id) { + panic('fixed array .pointers receiver should be addressable after checking') + } + g.write('({ ') + g.write('typeof(') + if is_ptr { + g.gen_expr(base_id) + } else { + g.write('&(') + g.gen_expr(base_id) + g.write(')') + } + g.write(') ${src_name} = ') + if is_ptr { + g.gen_expr(base_id) + } else { + g.write('&(') + g.gen_expr(base_id) + g.write(')') + } + g.write('; Array ${res_name} = array_new(sizeof(voidptr), ${len_text}, ${len_text}); for (int ${idx_name} = 0; ${idx_name} < ${len_text}; ${idx_name}++) { ((voidptr*)${res_name}.data)[${idx_name}] = (voidptr)&((*${src_name})[${idx_name}]); } ${res_name}; })') +} + +// gen_thread_array_wait emits a call to the (lazily generated) wait function for a +// `[]thread T` receiver. The element type carries the thread's return type in its +// name (`thread T`); a bare `thread` denotes a void payload. +fn (mut g FlatGen) gen_thread_array_wait(base_id flat.NodeId, is_ptr bool, elem_type types.Type) { + mut ret_name := '' + if elem_type is types.Struct { + trimmed := trimmed_space(elem_type.name) + if trimmed != 'thread' && trimmed.starts_with('thread ') { + ret_name = trimmed_space(trimmed[7..]) + } + } + fn_name := g.ensure_thread_arr_wait_fn(ret_name) + g.write('${fn_name}(') + if is_ptr { + g.write('*') + } + g.gen_expr(base_id) + g.write(')') +} + +// ensure_thread_arr_wait_fn registers (once per payload type) a function that joins +// every thread handle in the array and, for a non-void payload, copies each thread's +// heap-returned value into a result `[]T` (freeing the per-thread allocation). +fn (mut g FlatGen) ensure_thread_arr_wait_fn(ret_name string) string { + is_void := ret_name.len == 0 + if !is_void { + ret_type := g.tc.parse_type(ret_name) + if ret_type is types.OptionType || ret_type is types.ResultType { + return g.ensure_thread_optional_arr_wait_fn(ret_type) + } + } + // Match the ABI return type the spawn wrapper stores (gen_spawn_expr): an + // option/result payload is `Optional_T`, a fixed-array payload its `_v_ret_*` + // wrapper — not the bare `c_type`, or the malloc'd and read-back layouts diverge. + ret_ct := if is_void { 'void' } else { g.fn_return_type_name(g.tc.parse_type(ret_name)) } + key := 'threadwait|${ret_ct}' + if name := g.spawn_wrapper_names[key] { + return name + } + // Sanitize the payload C type (`Foo*`, `void*`, ...) into an identifier fragment + // — `c_name` does not strip `*`, so a raw pointer return type would otherwise put + // an asterisk in the helper symbol. + name := g.cname('__v_thread_arr_wait_${naming.type_name_part(ret_ct)}') + g.spawn_wrapper_names[key] = name + if is_void { + g.add_spawn_wrapper_def('static void ${name}(Array a) { for (int __i = 0; __i < a.len; __i++) { __v_thread __t = ((__v_thread*)a.data)[__i]; if (!__t.handle) continue; void* __r = __v_thread_join(__t); if (__r) free(__r); } }') + } else { + g.add_spawn_wrapper_def('static Array ${name}(Array a) { Array __res = array_new(sizeof(${ret_ct}), a.len, a.len); for (int __i = 0; __i < a.len; __i++) { __v_thread __t = ((__v_thread*)a.data)[__i]; if (!__t.handle) continue; void* __r = __v_thread_join(__t); if (__r) { ((${ret_ct}*)__res.data)[__i] = *(${ret_ct}*)__r; free(__r); } } return __res; }') + } + return name +} + +fn (mut g FlatGen) ensure_thread_optional_arr_wait_fn(ret_type types.Type) string { + ret_ct := g.fn_return_type_name(ret_type) + key := 'threadwait_optional|${ret_ct}' + if name := g.spawn_wrapper_names[key] { + return name + } + name := g.cname('__v_thread_arr_wait_${naming.type_name_part(ret_ct)}_array') + g.spawn_wrapper_names[key] = name + mut base_type := types.Type(types.void_) + mut array_result_type := types.Type(types.void_) + if ret_type is types.OptionType { + base_type = ret_type.base_type + if ret_type.base_type !is types.Void { + array_result_type = types.Type(types.OptionType{ + base_type: types.Type(types.Array{ + elem_type: ret_type.base_type + }) + }) + } else { + array_result_type = ret_type + } + } else if ret_type is types.ResultType { + base_type = ret_type.base_type + if ret_type.base_type !is types.Void { + array_result_type = types.Type(types.ResultType{ + base_type: types.Type(types.Array{ + elem_type: ret_type.base_type + }) + }) + } else { + array_result_type = ret_type + } + } + result_ct := g.optional_type_name(array_result_type) + if base_type is types.Void { + g.add_spawn_wrapper_def('static ${result_ct} ${name}(Array a) { bool __failed = false; IError __err; memset(&__err, 0, sizeof(__err)); for (int __i = 0; __i < a.len; __i++) { __v_thread __t = ((__v_thread*)a.data)[__i]; if (!__t.handle) continue; void* __r = __v_thread_join(__t); ${ret_ct} __item; if (__r) { __item = *((${ret_ct}*)__r); free(__r); } else { memset(&__item, 0, sizeof(__item)); } if (!__item.ok) { if (!__failed) { __failed = true; __err = __item.err; } } } if (__failed) return (${result_ct}){.ok = false, .err = __err}; return (${result_ct}){.ok = true}; }') + return name + } + value_ct := g.optional_payload_c_type(base_type) + value_assign := if _ := array_fixed_type(base_type) { + 'memmove(&(((${value_ct}*)__res.data)[__i]), __item.value, sizeof(${value_ct}));' + } else { + '((${value_ct}*)__res.data)[__i] = __item.value;' + } + g.add_spawn_wrapper_def('static ${result_ct} ${name}(Array a) { Array __res = array_new(sizeof(${value_ct}), a.len, a.len); bool __failed = false; IError __err; memset(&__err, 0, sizeof(__err)); for (int __i = 0; __i < a.len; __i++) { __v_thread __t = ((__v_thread*)a.data)[__i]; if (!__t.handle) continue; void* __r = __v_thread_join(__t); ${ret_ct} __item; if (__r) { __item = *((${ret_ct}*)__r); free(__r); } else { memset(&__item, 0, sizeof(__item)); } if (!__item.ok) { if (!__failed) { __failed = true; __err = __item.err; } continue; } ${value_assign} } if (__failed) return (${result_ct}){.ok = false, .err = __err}; return (${result_ct}){.ok = true, .value = __res}; }') + return name +} + +// array_lookup_suffix supports array lookup suffix handling for c. +fn array_lookup_suffix(elem_type types.Type) string { + if elem_type is types.String { + return 'string' + } + if elem_type is types.Primitive { + if elem_type.props.has(.unsigned) && elem_type.size == 8 { + return 'u8' + } + } + return 'int' +} + +fn array_last_index_suffix(elem_type types.Type) ?string { + elem_name := elem_type.name() + return match elem_name { + 'string' { 'string' } + 'u8', 'byte' { 'u8' } + 'int' { 'int' } + else { none } + } +} + +// array_method_fallback supports array method fallback handling for FlatGen. +fn (mut g FlatGen) array_method_fallback(method string) string { + if method in g.array_method_cache { + return g.array_method_cache[method] + } + suffix := '.${method}' + mut best_mname := '' + for mname, _ in g.tc.fn_param_types { + if mname.ends_with(suffix) { + if best_mname.len == 0 || mname.len > best_mname.len { + best_mname = mname + } + } + } + g.array_method_cache[method] = best_mname + return best_mname +} + +fn (mut g FlatGen) array_method_fallback_for_receiver(method string, base_id flat.NodeId, arr types.Array) string { + receiver_type := types.unwrap_pointer(g.usable_expr_type(base_id)) + receiver_name := receiver_type.name() + key := '${receiver_name}|${arr.elem_type.name()}.${method}' + if key in g.array_method_cache { + return g.array_method_cache[key] + } + // Preserve custom methods on aliases such as `strings.Builder = []u8`. + // Falling back through the erased array type can select an unrelated method + // that happens to have the same short name. + if receiver_type is types.Alias { + alias_method := g.resolve_method_name(receiver_name, method) + if alias_method.len > 0 { + g.array_method_cache[key] = alias_method + return alias_method + } + } + suffix := '.${method}' + mut best_mname := '' + for mname, ptypes in g.tc.fn_param_types { + if !mname.ends_with(suffix) || ptypes.len == 0 { + continue + } + recv := types.unwrap_pointer(ptypes[0]) + recv_array := if recv is types.Array { + recv + } else if recv is types.Alias && recv.base_type is types.Array { + recv.base_type + } else { + types.Array{} + } + if recv_array.elem_type !is types.Void + && g.array_elem_type_matches(recv_array.elem_type, arr.elem_type) { + if best_mname.len == 0 || mname.len > best_mname.len { + best_mname = mname + } + } + } + if best_mname.len == 0 { + best_mname = g.array_method_fallback(method) + } + g.array_method_cache[key] = best_mname + return best_mname +} + +fn (g &FlatGen) array_elem_type_matches(expected types.Type, actual types.Type) bool { + if expected.name() == actual.name() { + return true + } + return g.tc.c_type(expected) == g.tc.c_type(actual) +} + +// gen_map_ref_arg emits map ref arg output for c. +fn (mut g FlatGen) gen_map_ref_arg(base_id flat.NodeId, base_type types.Type) { + if base_type is types.Pointer { + g.gen_expr(base_id) + } else { + g.write('&') + g.gen_expr(base_id) + } +} + +fn (mut g FlatGen) gen_index_overload_call(node flat.Node, base_id flat.NodeId, base_type types.Type, info types.CallInfo) { + g.write(g.cname(info.name)) + g.write('(') + g.gen_index_overload_receiver_arg(base_id, base_type, info) + if info.params.len > 1 { + g.write(', ') + g.gen_index_overload_index_arg(node, info.params[1]) + } + g.write(')') +} + +fn (mut g FlatGen) gen_index_overload_set(node flat.Node, lhs flat.Node, base_id flat.NodeId, base_type types.Type, info types.CallInfo) { + if info.params.len < 3 { + g.gen_assign(node) + return + } + rhs_id := g.a.child(&node, 1) + if op := compound_assign_to_infix_op(node.op) { + if getter := g.tc.index_overload_call_info(base_type, false) { + g.gen_index_overload_compound_set(lhs, base_id, base_type, info, getter, node.op, op, + rhs_id) + return + } + } + g.write(g.cname(info.name)) + g.write('(') + g.gen_index_overload_receiver_arg(base_id, base_type, info) + g.write(', ') + g.gen_index_overload_index_arg(lhs, info.params[1]) + g.write(', ') + if op := compound_assign_to_infix_op(node.op) { + g.write('(') + if getter := g.tc.index_overload_call_info(base_type, false) { + g.gen_index_overload_call(lhs, base_id, base_type, getter) + } else { + g.gen_expr(g.a.child(&node, 0)) + } + g.write(' ${g.op_str(op)} ') + g.gen_expr_with_expected_type(rhs_id, info.params[2]) + g.write(')') + } else { + g.gen_expr_with_expected_type(rhs_id, info.params[2]) + } + g.writeln(');') +} + +fn (mut g FlatGen) gen_index_overload_compound_set(lhs flat.Node, base_id flat.NodeId, base_type types.Type, setter types.CallInfo, getter types.CallInfo, assign_op flat.Op, infix_op flat.Op, rhs_id flat.NodeId) { + recv_tmp := g.tmp_name() + index_tmp := g.tmp_name() + recv_type := if base_type is types.Pointer { base_type.base_type } else { base_type } + recv_ct := g.value_c_type(recv_type) + index_ct := g.value_c_type(setter.params[1]) + g.write('{ ${recv_ct}* ${recv_tmp} = ') + if base_type is types.Pointer { + g.gen_expr(base_id) + } else { + g.write('&') + g.gen_expr(base_id) + } + g.write('; ${index_ct} ${index_tmp} = ') + g.gen_index_overload_index_arg(lhs, setter.params[1]) + g.write('; ${g.cname(setter.name)}(') + g.gen_index_overload_cached_receiver_arg(recv_tmp, setter) + g.write(', ${index_tmp}, ') + g.gen_index_overload_compound_value_expr(recv_tmp, index_tmp, setter, getter, assign_op, + infix_op, rhs_id) + g.writeln('); }') +} + +fn (mut g FlatGen) gen_index_overload_compound_value_expr(recv_tmp string, index_tmp string, setter types.CallInfo, getter types.CallInfo, assign_op flat.Op, infix_op flat.Op, rhs_id flat.NodeId) { + if infix_op == .power { + if setter.params.len > 2 { + if method_name := g.assign_struct_operator_method(getter.return_type, assign_op) { + g.write('${g.cname(method_name)}(') + g.gen_index_overload_cached_getter_call(recv_tmp, index_tmp, getter) + g.write(', ') + g.gen_expr(rhs_id) + g.write(')') + return + } + } + lhs_text := g.index_overload_cached_getter_call_string(recv_tmp, index_tmp, getter) + g.gen_power_expr_from_lhs_text(lhs_text, rhs_id, getter.return_type) + return + } + if infix_op == .plus && (index_overload_compound_type_is_string(getter.return_type) + || (setter.params.len > 2 && index_overload_compound_type_is_string(setter.params[2]))) { + g.write('string__plus(') + g.gen_index_overload_cached_getter_call(recv_tmp, index_tmp, getter) + g.write(', ') + g.gen_expr_as_string(rhs_id) + g.write(')') + return + } + if setter.params.len > 2 { + if method_name := g.assign_struct_operator_method(getter.return_type, assign_op) { + g.write('${g.cname(method_name)}(') + g.gen_index_overload_cached_getter_call(recv_tmp, index_tmp, getter) + g.write(', ') + g.gen_expr(rhs_id) + g.write(')') + return + } + } + g.write('(') + g.gen_index_overload_cached_getter_call(recv_tmp, index_tmp, getter) + g.write(' ${g.op_str(infix_op)} ') + if setter.params.len > 2 { + g.gen_expr_with_expected_type(rhs_id, setter.params[2]) + } else { + g.gen_expr(rhs_id) + } + g.write(')') +} + +fn (mut g FlatGen) gen_index_overload_cached_getter_call(recv_tmp string, index_tmp string, getter types.CallInfo) { + g.write(g.cname(getter.name)) + g.write('(') + g.gen_index_overload_cached_receiver_arg(recv_tmp, getter) + g.write(', ${index_tmp})') +} + +fn (mut g FlatGen) index_overload_cached_getter_call_string(recv_tmp string, index_tmp string, getter types.CallInfo) string { + orig := g.sb + orig_line_start := g.line_start + g.sb = strings.new_builder(64) + g.line_start = false + g.gen_index_overload_cached_getter_call(recv_tmp, index_tmp, getter) + result := g.sb.str() + g.sb = orig + g.line_start = orig_line_start + return result +} + +fn index_overload_compound_type_is_string(typ types.Type) bool { + clean := if typ is types.Alias { typ.base_type } else { typ } + return clean is types.String +} + +fn (mut g FlatGen) gen_index_overload_receiver_arg(base_id flat.NodeId, base_type types.Type, info types.CallInfo) { + if info.params.len > 0 && info.params[0] is types.Pointer && base_type !is types.Pointer { + g.write('&') + g.gen_expr(base_id) + return + } + if info.params.len > 0 { + g.gen_expr_with_expected_type(base_id, info.params[0]) + return + } + g.gen_expr(base_id) +} + +fn (mut g FlatGen) gen_index_overload_cached_receiver_arg(recv_tmp string, info types.CallInfo) { + if info.params.len > 0 && info.params[0] is types.Pointer { + g.write(recv_tmp) + return + } + g.write('*${recv_tmp}') +} + +fn (mut g FlatGen) gen_index_overload_index_arg(node flat.Node, expected types.Type) { + if cgen_is_builtin_slice_index_type(expected) { + if node.value == 'range' { + g.gen_slice_index_value_from_index_range(node) + } else if node.children_count > 1 { + g.gen_slice_index_value(g.a.child(&node, 1)) + } else { + g.gen_default_value_for_type(expected) + } + return + } + if elem := cgen_builtin_slice_index_array_elem(expected) { + g.gen_slice_index_array_value(node, elem) + return + } + if node.children_count > 1 { + g.gen_expr_with_expected_type(g.a.child(&node, 1), expected) + } else { + g.gen_default_value_for_type(expected) + } +} + +fn (mut g FlatGen) gen_slice_index_array_value(node flat.Node, elem_type types.Type) { + ct := g.value_c_type(elem_type) + count := if node.value == 'range' { + 1 + } else if node.children_count > 0 { + int(node.children_count) - 1 + } else { + 0 + } + g.write('new_array_from_c_array(${count}, ${count}, sizeof(${ct}), (${ct}[]){') + if node.value == 'range' { + g.gen_slice_index_value_from_index_range(node) + } else { + for i in 1 .. node.children_count { + if i > 1 { + g.write(', ') + } + g.gen_slice_index_value(g.a.child(&node, i)) + } + } + g.write('})') +} + +fn (mut g FlatGen) gen_slice_index_value_from_index_range(node flat.Node) { + slice_type := g.tc.parse_type('SliceIndex') + ct := g.value_c_type(slice_type) + g.write('(${ct}){.is_range = true') + if node.children_count > 1 { + low_id := g.a.child(&node, 1) + low := g.a.nodes[int(low_id)] + if low.kind != .empty { + g.write(', .low = ') + g.gen_expr(low_id) + g.write(', .has_low = true') + } + } + if node.children_count > 2 { + high_id := g.a.child(&node, 2) + high := g.a.nodes[int(high_id)] + if high.kind != .empty { + g.write(', .high = ') + g.gen_expr(high_id) + g.write(', .has_high = true') + } + } + g.write('}') +} + +fn (mut g FlatGen) gen_slice_index_value(id flat.NodeId) { + slice_type := g.tc.parse_type('SliceIndex') + ct := g.value_c_type(slice_type) + if int(id) < 0 { + g.write('(${ct}){0}') + return + } + node := g.a.nodes[int(id)] + if node.kind != .range { + g.write('(${ct}){.value = ') + g.gen_expr(id) + g.write('}') + return + } + g.write('(${ct}){.is_range = true') + if node.children_count > 0 { + low_id := g.a.child(&node, 0) + low := g.a.nodes[int(low_id)] + if low.kind != .empty { + g.write(', .low = ') + g.gen_expr(low_id) + g.write(', .has_low = true') + } + } + if node.children_count > 1 { + high_id := g.a.child(&node, 1) + high := g.a.nodes[int(high_id)] + if high.kind != .empty { + g.write(', .high = ') + g.gen_expr(high_id) + g.write(', .has_high = true') + } + } + g.write('}') +} + +fn cgen_is_builtin_slice_index_type(typ types.Type) bool { + clean0 := select_receive_unalias_type(typ) + clean := types.unwrap_pointer(clean0) + return clean.name() in ['SliceIndex', 'builtin.SliceIndex'] +} + +fn cgen_builtin_slice_index_array_elem(typ types.Type) ?types.Type { + clean := select_receive_unalias_type(typ) + if clean is types.Array && cgen_is_builtin_slice_index_type(clean.elem_type) { + return clean.elem_type + } + return none +} + +fn (mut g FlatGen) gen_index_operator_get_call(node flat.Node) bool { + if node.value == 'range' || node.children_count != 2 { + return false + } + base_id := g.a.child(&node, 0) + index_id := g.a.child(&node, 1) + base_type := g.usable_expr_type(base_id) + info := g.tc.index_operator_call_info(base_type, '[]') or { return false } + if info.params.len < 2 { + return false + } + g.write(g.cname(g.index_operator_call_name(info, base_type, '[]'))) + g.write('(') + g.gen_index_operator_receiver_arg(base_id, base_type, info.params[0]) + g.write(', ') + g.gen_expr_with_expected_type(index_id, info.params[1]) + g.write(')') + return true +} + +fn (mut g FlatGen) gen_index_operator_assign(node flat.Node, lhs flat.Node, base_type types.Type) bool { + if lhs.value == 'range' || lhs.children_count != 2 { + return false + } + setter := g.tc.index_operator_call_info(base_type, '[]=') or { return false } + if setter.params.len < 3 { + return false + } + if node.op == .assign { + g.gen_index_operator_set_call(lhs, setter, base_type, g.a.child(&node, 1)) + g.writeln(';') + return true + } + getter := g.tc.index_operator_call_info(base_type, '[]') or { return false } + if getter.params.len < 2 { + return false + } + g.gen_index_operator_compound_assign(node, lhs, setter, getter, base_type) + return true +} + +fn (mut g FlatGen) gen_index_operator_set_call(lhs flat.Node, setter types.CallInfo, base_type types.Type, value_id flat.NodeId) { + base_id := g.a.child(&lhs, 0) + index_id := g.a.child(&lhs, 1) + g.write(g.cname(g.index_operator_call_name(setter, base_type, '[]='))) + g.write('(') + g.gen_index_operator_receiver_arg(base_id, base_type, setter.params[0]) + g.write(', ') + g.gen_expr_with_expected_type(index_id, setter.params[1]) + g.write(', ') + g.gen_expr_with_expected_type(value_id, setter.params[2]) + g.write(')') +} + +fn (mut g FlatGen) gen_index_operator_compound_assign(node flat.Node, lhs flat.Node, setter types.CallInfo, getter types.CallInfo, base_type types.Type) { + base_id := g.a.child(&lhs, 0) + index_id := g.a.child(&lhs, 1) + rhs_id := g.a.child(&node, 1) + tmp := g.tmp_count + g.tmp_count += 2 + recv_tmp := '_idx_recv_${tmp}' + index_tmp := '_idx_key_${tmp}' + recv_storage := g.index_operator_receiver_storage_type(base_type, setter.params[0]) + index_storage := getter.params[1] + g.write('{ ${g.tc.c_type(recv_storage)} ${recv_tmp} = ') + g.gen_index_operator_receiver_storage_init(base_id, base_type, recv_storage) + g.write('; ${g.value_c_type(index_storage)} ${index_tmp} = ') + g.gen_expr_with_expected_type(index_id, index_storage) + g.write('; ') + g.write(g.cname(g.index_operator_call_name(setter, base_type, '[]='))) + g.write('(') + g.gen_index_operator_receiver_tmp_arg(recv_tmp, recv_storage, setter.params[0]) + g.write(', ') + g.gen_index_operator_tmp_arg(index_tmp, index_storage, setter.params[1]) + g.write(', ') + if op := compound_assign_to_infix_op(node.op) { + if op == .power { + if method_name := g.index_operator_compound_operator_method(getter.return_type, + setter.params[2], node.op) + { + g.write('${g.cname(method_name)}(') + g.gen_index_operator_get_call_from_temps(getter, recv_tmp, recv_storage, index_tmp, + index_storage) + g.write(', ') + g.gen_expr_with_expected_type(rhs_id, setter.params[2]) + g.write(')') + } else { + lhs_text := g.index_operator_get_call_from_temps_string(getter, recv_tmp, + recv_storage, index_tmp, index_storage) + g.gen_power_expr_from_lhs_text(lhs_text, rhs_id, getter.return_type) + } + } else if op == .plus && (g.index_operator_type_is_string_like(getter.return_type) + || g.index_operator_type_is_string_like(setter.params[2])) { + g.write('string__plus(') + g.gen_index_operator_get_call_from_temps(getter, recv_tmp, recv_storage, index_tmp, + index_storage) + g.write(', ') + g.gen_expr_as_string(rhs_id) + g.write(')') + } else if method_name := g.index_operator_compound_operator_method(getter.return_type, + setter.params[2], node.op) + { + g.write('${g.cname(method_name)}(') + g.gen_index_operator_get_call_from_temps(getter, recv_tmp, recv_storage, index_tmp, + index_storage) + g.write(', ') + g.gen_expr_with_expected_type(rhs_id, setter.params[2]) + g.write(')') + } else { + g.write('(') + g.gen_index_operator_get_call_from_temps(getter, recv_tmp, recv_storage, index_tmp, + index_storage) + g.write(' ${g.op_str(op)} (') + g.gen_expr_with_expected_type(rhs_id, setter.params[2]) + g.write('))') + } + } else { + g.gen_expr_with_expected_type(rhs_id, setter.params[2]) + } + g.writeln('); }') +} + +fn (g &FlatGen) index_operator_type_is_string_like(typ types.Type) bool { + if typ is types.String { + return true + } + if typ is types.Alias { + return g.index_operator_type_is_string_like(typ.base_type) + } + return false +} + +fn (g &FlatGen) index_operator_compound_operator_method(getter_type types.Type, setter_type types.Type, op flat.Op) ?string { + if method_name := g.assign_struct_operator_method(getter_type, op) { + return method_name + } + return g.assign_struct_operator_method(setter_type, op) +} + +fn (mut g FlatGen) gen_index_operator_get_call_from_temps(getter types.CallInfo, recv_tmp string, recv_storage types.Type, index_tmp string, index_storage types.Type) { + g.write(g.cname(g.index_operator_call_name(getter, recv_storage, '[]'))) + g.write('(') + g.gen_index_operator_receiver_tmp_arg(recv_tmp, recv_storage, getter.params[0]) + g.write(', ') + g.gen_index_operator_tmp_arg(index_tmp, index_storage, getter.params[1]) + g.write(')') +} + +fn (mut g FlatGen) index_operator_get_call_from_temps_string(getter types.CallInfo, recv_tmp string, recv_storage types.Type, index_tmp string, index_storage types.Type) string { + orig := g.sb + orig_line_start := g.line_start + g.sb = strings.new_builder(64) + g.line_start = false + g.gen_index_operator_get_call_from_temps(getter, recv_tmp, recv_storage, index_tmp, + index_storage) + result := g.sb.str() + g.sb = orig + g.line_start = orig_line_start + return result +} + +fn (mut g FlatGen) gen_index_operator_tmp_arg(index_tmp string, actual types.Type, expected types.Type) { + if !g.type_names_match(actual, expected) { + g.write('(${g.value_c_type(expected)})') + } + g.write(index_tmp) +} + +fn (g &FlatGen) index_operator_call_name(info types.CallInfo, base_type types.Type, method string) string { + if !info.name.contains('[') { + return info.name + } + receiver_type := types.unwrap_pointer(base_type) + receiver_name := receiver_type.name() + if receiver_name.len == 0 { + return info.name + } + if resolved := g.resolve_concrete_generic_method_name(receiver_name, method) { + return resolved + } + resolved := g.resolve_method_name(receiver_name, method) + if resolved.len > 0 { + return resolved + } + return info.name +} + +fn (g &FlatGen) index_operator_receiver_storage_type(base_type types.Type, expected types.Type) types.Type { + if base_type is types.Pointer { + return base_type + } + if expected is types.Pointer { + return expected + } + return base_type +} + +fn (mut g FlatGen) gen_index_operator_receiver_storage_init(base_id flat.NodeId, base_type types.Type, storage_type types.Type) { + if storage_type is types.Pointer && base_type !is types.Pointer { + g.write('&') + g.gen_expr(base_id) + return + } + g.gen_expr_with_expected_type(base_id, storage_type) +} + +fn (mut g FlatGen) gen_index_operator_receiver_arg(base_id flat.NodeId, actual types.Type, expected types.Type) { + if expected is types.Pointer { + if actual !is types.Pointer && !g.receiver_ident_storage_is_pointer(base_id) { + g.write('&') + } + g.gen_expr(base_id) + return + } + if actual is types.Pointer || g.receiver_ident_storage_is_pointer(base_id) { + g.write('*') + } + g.gen_expr(base_id) +} + +fn (mut g FlatGen) gen_index_operator_receiver_tmp_arg(tmp string, actual types.Type, expected types.Type) { + if expected is types.Pointer { + if actual !is types.Pointer { + g.write('&') + } + g.write(tmp) + return + } + if actual is types.Pointer { + g.write('*') + } + g.write(tmp) +} + +// gen_index_assign emits index assign output for c. +fn (mut g FlatGen) gen_index_assign(node flat.Node) { + lhs_id := g.a.child(&node, 0) + lhs := g.a.nodes[int(lhs_id)] + if lhs.kind == .index { + base_id := g.a.child(&lhs, 0) + base_type := g.usable_expr_type(base_id) + if g.gen_index_operator_assign(node, lhs, base_type) { + return + } + clean_base := types.unwrap_pointer(base_type) + if clean_base is types.Map { + c_key := g.map_key_temp_c_type(clean_base.key_type) + c_val := g.value_c_type(clean_base.value_type) + is_ptr := base_type is types.Pointer + if g.map_loop_copyback_guards.len > 0 { + map_tmp := '__map_set_target_${g.tmp_count}' + g.tmp_count++ + key_tmp := '__map_set_key_${g.tmp_count}' + g.tmp_count++ + g.write('map* ${map_tmp} = ') + if !is_ptr { + g.write('&') + } + g.gen_expr(base_id) + g.writeln(';') + key_id := g.a.child(&lhs, 1) + mut key_ref := '&${key_tmp}' + if key_fixed := array_fixed_type(clean_base.key_type) { + c_elem, dims := g.fixed_array_decl_parts(key_fixed) + g.writeln('${c_elem} ${key_tmp}${dims};') + g.write('memmove(${key_tmp}, ') + g.gen_expr_with_expected_type(key_id, clean_base.key_type) + g.writeln(', sizeof(${key_tmp}));') + key_ref = key_tmp + } else { + g.write('${c_key} ${key_tmp} = ') + g.gen_expr_with_expected_type(key_id, clean_base.key_type) + g.writeln(';') + } + g.gen_map_loop_copyback_dirty_checks(map_tmp, key_ref) + g.write('map__set(${map_tmp}, ${key_ref}, &(${c_val}[]){') + g.gen_expr_with_expected_type(g.a.child(&node, 1), clean_base.value_type) + g.writeln('});') + return + } + if is_ptr { + g.write('map__set(') + } else { + g.write('map__set(&') + } + g.gen_expr(base_id) + g.write(', &(${c_key}[]){') + g.gen_expr_with_expected_type(g.a.child(&lhs, 1), clean_base.key_type) + g.write('}, &(${c_val}[]){') + g.gen_expr_with_expected_type(g.a.child(&node, 1), clean_base.value_type) + g.writeln('});') + return + } + if info := g.tc.index_overload_call_info(base_type, true) { + g.gen_index_overload_set(node, lhs, base_id, base_type, info) + return + } + mut arr_type := types.Array{} + mut is_array_base := false + if arr := array_like_type(base_type) { + arr_type = arr + is_array_base = true + } else if base_type is types.Pointer { + ptr_type := base_type + if arr := array_like_type(ptr_type.base_type) { + arr_type = arr + is_array_base = true + } + } + if is_array_base { + c_elem := g.value_c_type(arr_type.elem_type) + tmp := g.tmp_count + g.tmp_count++ + // Use the public alias here: a source local named `array` hides the + // lowercase C typedef and turns `array* tmp` into an expression. + g.write('{ Array* _a${tmp} = ') + if g.array_assign_base_is_shared_value_selector(base_id) { + g.write('&') + g.gen_expr(base_id) + } else if base_type is types.Pointer { + g.gen_expr(base_id) + } else { + g.write('&') + g.gen_expr(base_id) + } + g.write('; int _i${tmp} = ') + g.gen_expr(g.a.child(&lhs, 1)) + if node.op == .assign { + if fixed := array_fixed_type(arr_type.elem_type) { + g.write('; array__set(_a${tmp}, _i${tmp}, ') + g.gen_fixed_array_data_arg(g.a.child(&node, 1), fixed) + g.writeln('); }') + return + } + } + g.write('; array__set(_a${tmp}, _i${tmp}, &(${c_elem}[]){') + if node.op == .power_assign { + lhs_text := '*(${c_elem}*)array_get(*_a${tmp}, _i${tmp})' + if method_name := g.assign_struct_operator_method(arr_type.elem_type, node.op) { + g.write('${g.cname(method_name)}(${lhs_text}, ') + g.gen_expr_with_expected_type(g.a.child(&node, 1), arr_type.elem_type) + g.write(')') + } else { + g.gen_power_expr_from_lhs_text(lhs_text, g.a.child(&node, 1), + arr_type.elem_type) + } + } else if node.op in [.left_shift_assign, .right_shift_assign, + .right_shift_unsigned_assign] { + shift_op := match node.op { + .left_shift_assign { flat.Op.left_shift } + .right_shift_assign { flat.Op.right_shift } + else { flat.Op.right_shift_unsigned } + } + + lhs_text := '*(${c_elem}*)array_get(*_a${tmp}, _i${tmp})' + g.gen_guarded_shift_from_text(lhs_text, g.a.child(&node, 1), arr_type.elem_type, + shift_op) + } else if op := compound_assign_to_infix_op(node.op) { + if arr_type.elem_type is types.String && op == .plus { + g.write('string__plus(') + g.write('*(string*)array_get(*_a${tmp}, _i${tmp})') + g.write(', ') + g.gen_expr_as_string(g.a.child(&node, 1)) + g.write(')') + } else { + g.write('(') + g.write('*(${c_elem}*)array_get(*_a${tmp}, _i${tmp})') + g.write(' ${g.op_str(op)} ') + g.write('(') + g.gen_expr_with_expected_type(g.a.child(&node, 1), arr_type.elem_type) + g.write('))') + } + } else { + g.gen_expr_with_expected_type(g.a.child(&node, 1), arr_type.elem_type) + } + g.writeln('}); }') + return + } + if base_type is types.Pointer { + ptr_type := base_type + mut expected_type := ptr_type.base_type + base_node := g.a.node(base_id) + explicit_mut_pointer_param := base_node.kind == .ident + && g.current_param_is_mut_pointer(base_node.value) + if fixed := array_fixed_type(ptr_type.base_type) { + g.write('(*') + g.gen_expr(base_id) + g.write(')') + expected_type = fixed.elem_type + } else if ptr_type.base_type is types.Void { + g.write('((u8*)') + g.gen_expr(base_id) + g.write(')') + } else if explicit_mut_pointer_param { + g.write('(*') + g.gen_mut_pointer_slot_expr(base_id) + g.write(')') + } else { + g.write('(') + g.gen_expr(base_id) + g.write(')') + } + g.write('[') + g.gen_expr(g.a.child(&lhs, 1)) + g.write('] ${g.op_str(node.op)} ') + g.gen_expr_with_expected_type(g.a.child(&node, 1), expected_type) + g.writeln(';') + return + } + } + g.gen_assign(node) +} + +fn (g &FlatGen) array_assign_base_is_shared_value_selector(base_id flat.NodeId) bool { + if int(base_id) < 0 || int(base_id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(base_id)] + if node.kind == .ident { + return g.local_ident_is_shared_wrapper(node.value) + } + if node.kind != .selector || node.value != 'val' || node.children_count == 0 { + return false + } + wrapper_id := g.a.child(&node, 0) + if int(wrapper_id) < 0 || int(wrapper_id) >= g.a.nodes.len { + return false + } + wrapper := g.a.nodes[int(wrapper_id)] + return wrapper.kind == .ident && g.local_ident_is_shared_wrapper(wrapper.value) +} + +fn compound_assign_to_infix_op(op flat.Op) ?flat.Op { + match op { + .plus_assign { return flat.Op.plus } + .minus_assign { return flat.Op.minus } + .mul_assign { return flat.Op.mul } + .power_assign { return flat.Op.power } + .div_assign { return flat.Op.div } + .mod_assign { return flat.Op.mod } + .amp_assign { return flat.Op.amp } + .pipe_assign { return flat.Op.pipe } + .xor_assign { return flat.Op.xor } + .left_shift_assign { return flat.Op.left_shift } + .right_shift_assign { return flat.Op.right_shift } + .right_shift_unsigned_assign { return flat.Op.right_shift_unsigned } + else { return none } + } +} diff --git a/vlib/v3/gen/fastc/cleanc.v b/vlib/v3/gen/fastc/cleanc.v new file mode 100644 index 00000000000000..5fac045703dfa4 --- /dev/null +++ b/vlib/v3/gen/fastc/cleanc.v @@ -0,0 +1,21834 @@ +module fastc + +import os +import strings +import time +import v3.cmdexec +import v3.flat +import v3.gen.fastc.naming +import v3.modulecache +import v3.pref +import v3.types +import v3.util + +const spread_index_expected_type_marker = '__v3_spread_index_expected_type' +const source_mut_pointer_deref_marker = '__v3_source_mut_pointer_deref' +const c_inline_header_size_limit = 262_144 +const v1_c_headers_source = $embed_file('../../../v/gen/c/cheaders.v').to_string() +const c_objective_c_bridge_qualifiers = ['__bridge', '__bridge_retained', '__bridge_transfer'] +const c_objective_c_ownership_qualifiers = ['__strong', '__weak', '__autoreleasing', + '__unsafe_unretained', '__kindof'] +const c_objective_c_contextual_types = ['id', 'Class', 'SEL', 'Protocol', 'instancetype'] +const c_objective_c_compatibility_qualifiers = ['__bridge', '__bridge_retained', '__bridge_transfer', + '__strong', '__weak', '__autoreleasing', '__unsafe_unretained', '__kindof', 'id', 'Class', + 'SEL', 'Protocol', 'instancetype'] +const c_common_c_attributes = ['alias', 'aligned', 'always_inline', 'cold', 'const', 'constructor', + 'deprecated', 'destructor', 'format', 'hot', 'malloc', 'may_alias', 'noinline', 'nonnull', + 'noreturn', 'packed', 'pure', 'returns_nonnull', 'section', 'sentinel', 'unused', 'used', + 'visibility', 'warn_unused_result', 'weak'] +const c_has_attribute_predicate = '__has_attribute' +const c_has_attribute_override_key = '@function:__has_attribute' + +// c_short_name_view returns the suffix after the final dot without allocating. +@[direct_array_access; inline] +fn c_short_name_view(name string) string { + for i := name.len - 1; i >= 0; i-- { + if name[i] == `.` { + return unsafe { name.substr_unsafe(i + 1, name.len) } + } + } + return name +} + +fn manual_stdlib_c_headers() string { + start := v1_c_headers_source.index('// c_headers\n') or { return '' } + relative_end := v1_c_headers_source[start..].index('static void v_stable_sort') or { return '' } + // Some platform headers expose formatted I/O and memory functions as fortified + // macros. Undefine those macros before replaying V1's manual declarations. + return + '#ifdef sprintf\n#undef sprintf\n#endif\n#ifdef snprintf\n#undef snprintf\n#endif\n#ifdef vsnprintf\n#undef vsnprintf\n#endif\n#ifdef memcpy\n#undef memcpy\n#endif\n#ifdef memmove\n#undef memmove\n#endif\n#ifdef memset\n#undef memset\n#endif\n' + + v1_c_headers_source[start..start + relative_end] +} + +struct CHeaderTreeSize { +mut: + seen map[string]bool + total_size i64 +} + +fn cgen_worker_scope_begin(enabled bool) voidptr { + $if prealloc { + if enabled { + return unsafe { prealloc_scope_begin() } + } + } + return unsafe { nil } +} + +fn cgen_worker_scope_leave(scope voidptr) { + $if prealloc { + if scope != unsafe { nil } { + unsafe { prealloc_scope_leave(scope) } + } + } +} + +fn cgen_worker_scope_free(scope voidptr) { + $if prealloc { + if scope != unsafe { nil } { + unsafe { prealloc_scope_free_after(scope) } + } + } +} + +fn clone_cgen_string_list(values []string) []string { + mut cloned := []string{cap: values.len} + for value in values { + cloned << value.clone() + } + return cloned +} + +struct ActiveLock { + mutexes_var string + modes_var string + lock_count int + unlock_fn string + scope_id int + loop_depth int + defer_depth int +} + +struct LoopLabelState { + label string +mut: + had_prev bool + prev_depth int + had_prev_defer_start bool + prev_defer_start int +} + +struct LoopControlCopyback { + loop_depth int + stmt string +} + +struct MapLoopCopybackGuard { + map_ref string + key_ref string + dirty_var string +} + +struct FixedStorageConstRefItem { + id flat.NodeId + file string + module string +} + +struct EnumBackingInfo { + c_name string + storage_c_type string +} + +struct SumUniqueFieldInfo { + variant string + typ types.Type +} + +struct ParallelChunkWrapperDefs { + chunk_idx int +mut: + spawn []string + callback []string +} + +// PreseedTypeSeen deduplicates type walks during C generation. It is part of +// FlatGen even in serial-only builds because declaration preseeding is shared. +struct PreseedTypeSeen { +mut: + w0 [4096]u64 + w1 [4096]u64 + seen [4096]bool +} + +// PrepTypTextCache caches type-text preseed verdicts for one generation pass. +struct PrepTypTextCache { +mut: + generation u32 = 1 + ptrs [4096]voidptr + gens [4096]u32 + lens [4096]int + verdicts [4096]bool +} + +@[heap] +struct UsableExprTypeMemo { +mut: + active bool + generation u32 + ids []int + gens []u32 + values []types.Type +} + +fn (mut g FlatGen) begin_usable_expr_type_memo() { + if !g.memo_usable_expr_types { + return + } + if isnil(g.usable_expr_type_memo) { + g.usable_expr_type_memo = &UsableExprTypeMemo{ + ids: []int{len: 16384, init: -1} + gens: []u32{len: 16384} + values: unsafe { []types.Type{len: 16384} } + } + } + mut memo := g.usable_expr_type_memo + memo.generation++ + if memo.generation == 0 { + memo.generation = 1 + } + memo.active = true +} + +fn (mut g FlatGen) end_usable_expr_type_memo() { + if !isnil(g.usable_expr_type_memo) { + g.usable_expr_type_memo.active = false + } +} + +fn (mut g FlatGen) preseed_type_first_seen(typ &types.Type) bool { + mut cache := g.preseed_type_seen + if isnil(cache) { + return true + } + words := unsafe { &u64(voidptr(typ)) } + w0 := unsafe { words[0] } + w1 := unsafe { words[1] } + slot := int((w0 >> 4 ^ w1) & 4095) + if cache.seen[slot] && cache.w0[slot] == w0 && cache.w1[slot] == w1 { + return false + } + cache.w0[slot] = w0 + cache.w1[slot] = w1 + cache.seen[slot] = true + return true +} + +// FlatGen emits flat gen output used by c. +pub struct FlatGen { +mut: + sb strings.Builder + indent int + a &flat.FlatAst = unsafe { nil } + used_fns &map[string]bool = unsafe { nil } + used_fn_names []string + fn_gen_items []FlatFnGenItem + top_level_node_ids []int + ast_string_literals []string + ast_string_literals_ready bool + fn_segs []string + fn_seg_chunk_indexes []int + parallel_chunk_wrapper_defs []ParallelChunkWrapperDefs + parallel_chunk_wrapper_capture int = -1 + parallel_type_decls string + parallel_global_decls string + parallel_forward_decls string + parallel_support_decls string + parallel_enum_str_defs string + parallel_interface_stubs string + parallel_init_defs string + parallel_const_code string + parallel_support_ready bool + test_files map[string]bool + show_test_stats bool + show_test_summary bool + test_run_only []string + assert_expr_overrides map[int]string + print_fn_names []string + profile_file string + profile_no_inline bool + profile_fns []string + profile_counters []ProfileCounterMeta + profile_fn_active bool + profile_fn_restore_enabled bool + is_prod bool + check_overflow bool + ignore_overflow bool + force_bounds_checking bool + is_shared bool + object_file_mode bool + suppress_main bool + coverage_dir string + coverage_build_options string + coverage_files map[string]&CoverageInfo + coverage_counter_count int + cache_program_files map[string]bool + incremental_fn_names map[string]bool + str_lits []string + str_lit_ids map[string]int + str_lits_shared bool + global_types map[string]types.Type + global_raw_type_texts map[string]string + enum_vals map[string]int + enum_value_exprs map[string]string + defers []flat.NodeId + scope_defer_starts []int + fn_defers []flat.NodeId + fn_defer_counts map[int]string + defer_capture_names []string + defer_capture_types map[string]types.Type + interfaces map[string][]string + const_vals map[string]flat.NodeId + const_modules map[string]string + const_files map[string]string // const name -> declaring file (for import-alias type resolution) + const_init_order []string + fixed_storage_consts map[string]bool + global_modules map[string]string + global_files map[string]string // qualified global name -> declaring file (for import-alias type resolution) + global_inits map[string]flat.NodeId // qualified global name -> initializer value node + global_init_order []string // qualified global names, in declaration order + enum_backing_infos map[string]EnumBackingInfo + iface_impls map[string][]string // interface name -> implementing concrete type names + interface_dispatch_required map[string]bool // source/lowered concrete method names required by emitted interface dispatch + iface_type_ids map[string]int // "${iface}::${concrete}" -> 1-based type id + interface_boxed_types map[string]bool + interface_boxed_types_done bool + ierror_method_emit_names map[string]bool // names/lowered names of concrete IError msg/code methods + ierror_stack_pointer_aliases []map[string]bool // scoped local pointer aliases to stack subobjects + ierror_owned_pointer_by_owner map[string]bool // exact scope binding owner -> local owns its pointer allocation + recursive_drop_helpers map[string]string // expansion key -> concrete struct type name + local_pointer_storage_by_owner map[string]bool // exact scope binding owner -> C storage is already a pointer + local_c_type_by_owner map[string]string // exact scope binding owner -> emitted C declaration type + local_mutable_by_owner map[string]bool + local_pointer_alias_by_owner map[string]string // exact scope binding owner -> stack local whose address is stored + local_pointer_alias_mut_param map[string]bool // exact scope binding owner -> alias source is a mut parameter + local_raw_type_by_owner map[string]string // exact scope binding owner -> source-level raw type text + local_shared_storage_by_owner map[string]bool // exact scope binding owner -> C storage is a shared wrapper pointer + local_fn_value_c_name_by_owner map[string]string // exact scope binding owner -> lifted fn-literal C name + sum_name_lookup map[string]string // full/short sum type name -> canonical sum type name + module_init_fns []string // C names of module-level `init()` fns, in source order + module_init_fn_modules map[string]string // C init fn name -> V module name + module_cleanup_fns []string // C names of module-level `cleanup()` fns, in source order + module_cleanup_fn_modules map[string]string // C cleanup fn name -> V module name + module_imports map[string][]string // module -> imported modules + c_directives []CDirective + preinclude_directives []string + postinclude_directives []string + early_c_source_directives map[string]bool + native_source_contexts map[string][]NativeSourceContextDirective + objective_cpp_source_requests []ObjectiveCppSourceRequest + native_source_wrapper_index int + inlined_c_structs map[string]bool + inlined_c_typedef_names map[string]bool + inlined_c_fns map[string]bool + inlined_c_declared_fns map[string]bool + inlined_c_static_fns map[string]bool + cache_omitted_c_fns map[string]bool + preserved_header_files_seen map[string]bool + initial_c_flags []string + c_flags []string + use_system_stdint bool + libc_compat_fns map[string]bool + tc &types.TypeChecker = unsafe { nil } + has_builtins bool + tmp_count int + line_start bool + field_name_set map[string]bool // every struct field's C name (lazy) — for const/field collision checks + modules map[string]string // alias -> full module name + fn_ptr_types map[string]string // fn_ptr:ret|params -> typedef name + used_fn_ptr_types map[string]bool // signatures referenced by emitted C + multi_return_types []types.Type + multi_return_type_names map[string]bool + multi_return_types_ready bool + decl_types_ready bool + optional_types_ready bool + fixed_array_ret_wrappers map[string]bool // bare fixed-array c_type name -> has a return wrapper struct + emitted_fixed_array_typedefs map[string]bool // bare fixed-array typedefs already written (shared across passes) + concrete_optional_abi_fns map[string]bool // emitted fn names whose option/result params use Optional_T ABI + fixed_array_typedefs_needed map[string]FixedArrayTypedefInfo + fixed_array_typedefs_ready bool + fixed_array_map_key_types map[string]types.ArrayFixed + fn_decl_param_types map[string][]types.Type + fn_decl_variadic map[string]bool + fn_decl_variadic_short_counts map[string]int + fn_decl_shared_params map[string][]bool + fn_shared_params_resolved map[string][]bool + has_shared_params bool + fn_decl_mut_receivers map[string]bool + fn_decl_ret_types map[string]types.Type // fn decl name (and qualified variants) -> return type + // Const dependency analysis follows helper calls. Keep declaration indexes so + // resolving each call does not scan the whole flattened AST. + fn_decl_nodes_by_name map[string]flat.NodeId + fn_decl_nodes_by_short map[string]flat.NodeId + fn_decl_nodes_by_module_short map[string]flat.NodeId + // set of `${module}\x01${name}` for every non-generic fn decl, built once in + // precompute_non_generic_fn_index. Replaces the former full-node scan in + // non_generic_fn_decl_exists_in_module (O(nodes) per call, hot in cgen). + non_generic_fn_names_by_module map[string]bool + // indexes over tc.fn_generic_params keys, built once in + // precompute_generic_fn_key_index. They replace the former full-map scan in + // generic_plain_fn_base_for_call (O(generic fns) with two string allocations + // per key, run for nearly every emitted call). The ordinal preserves the + // map's iteration order so multi-match resolution stays byte-identical. + generic_fn_keys_by_short map[string][]string + generic_fn_keys_by_cname map[string][]string + generic_fn_key_ordinal map[string]int + struct_decl_infos map[string]StructDeclInfo + struct_decl_short_infos map[string]StructDeclInfo + decl_attrs map[int][]string + c_decl_abi_names map[string]string + c_extern_global_names map[string]string + shared_type_names map[string]SharedTypeInfo // __shared__ wrapper name -> wrapped type metadata + shared_alias_pointer_shorts map[string]string // alias short name -> shared inner type; '' means ambiguous + needs_shared_runtime bool + const_runtime_inits []string + const_runtime_init_modules []string + runtime_inits []string + runtime_init_modules []string + compiler_vroot string + compiler_vexe string + compiler_vexe_env_setup bool = true + ccompiler string + target pref.Target + thread_stack_size int = 8 * 1024 * 1024 + compile_values map[string]string // explicit `-d` values used by `$d(...)` in `#flag`s + output_path string + output_error string + c99_mode bool + inside_trace_call bool + skip_generics bool + skip_enum_autostr bool + placeholder_check_forced bool + cur_fn_name string + cur_fn_is_specialized bool + cur_fn_assert_continues bool + current_decl_is_mut bool + direct_array_access bool + struct_default_module string + default_value_stack map[string]bool + shadowed_global_locals map[string]bool + cur_param_names []string + cur_param_type_values []types.Type + cur_param_types map[string]types.Type + cur_concrete_optional_params map[string]bool + cur_mut_params map[string]bool + cur_mut_pointer_params map[string]bool + cur_mut_param_owners map[string]types.ScopeBindingOwner + cur_fn_ret types.Type = types.Type(types.void_) + cur_fn_ret_is_optional bool + cur_fn_ret_base types.Type = types.Type(types.void_) + defer_return_tmp_var string + active_locks []ActiveLock + unsafe_depth int + loop_depth int + conditional_branch_scopes []&types.Scope + conditional_branch_depths []int + conditional_branch_depth int + loop_label_depths map[string]int + loop_defer_starts []int + loop_label_defer_starts map[string]int + loop_control_copybacks []LoopControlCopyback + map_loop_copyback_guards []MapLoopCopybackGuard + emitted_loop_break_labels map[string]bool + goto_label_c_names map[string]string + goto_label_count int + goto_label_lock_scopes map[string][]int + pending_loop_label string + // in_return is true only while generating a `return` statement's value, so a bare + // generic literal (`return Box{...}`) may adopt `cur_fn_ret`'s concrete instance — + // but a literal in a local decl / argument elsewhere in the body does not. + in_return bool + cur_return_node_id int = -1 + ownership_return_index int + ownership_seen_return_sources map[string]bool + ownership_propagation_index int + ownership_loop_control_index int + ownership_loop_iteration_index int + ownership_scope_index int + cur_return_drops []types.OwnershipDropEntry + pending_return_scope_drops []types.OwnershipDropEntry + expected_expr_type types.Type = types.Type(types.void_) + expected_enum string + known_expr_type_id int = -1 + known_expr_type types.Type = types.Type(types.void_) + memo_usable_expr_types bool + cache_struct_fields bool + dedup_fn_decl_aliases bool + prefix_param_scan bool + lean_parallel_worker_init bool + lazy_param_abi_merge bool + usable_expr_type_memo &UsableExprTypeMemo = unsafe { nil } + needed_optional_types map[string]string + emitted_optional_types map[string]bool + emitted_fns map[string]bool + array_method_cache map[string]string + param_types_cache map[string][]types.Type // (name|fallback) -> resolved param types + interface_receiver_cache &StringLookupCache = unsafe { nil } + normalize_call_cache &StringLookupCache = unsafe { nil } + flattened_generic_name_cache &StringLookupCache = unsafe { nil } + generic_struct_context_ct_cache &StringLookupCache = unsafe { nil } + struct_cname_cache &StringLookupCache = unsafe { nil } + unique_struct_ct_cache &StringLookupCache = unsafe { nil } + alias_method_cache &StringLookupCache = unsafe { nil } + import_alias_cache &ContextStringLookupCache = unsafe { nil } + enum_selector_cache &ContextStringLookupCache = unsafe { nil } + enum_method_cache &ContextStringLookupCache = unsafe { nil } + qualified_enum_method_cache &ContextStringLookupCache = unsafe { nil } + embedded_fields_by_type map[string][]types.StructField // type name -> its embedded fields (usually empty) + param_types_by_short map[string][]types.Type // method short-name suffix -> param types (fallback index) + generic_method_candidates map[string][]GenericMethodCandidate + spawn_wrapper_names map[string]string + spawn_wrapper_defs []string + spawn_wrapper_defs_seen map[string]bool + callback_wrapper_names map[string]string + callback_wrapper_defs []string + callback_wrapper_defs_seen map[string]bool + parallel_used bool + c_name_cache &CNameCache = unsafe { nil } + emitted_fn_ptr_typedefs map[string]bool + c_extern_refs map[string]bool + c_extern_refs_ready bool + parallel_prepared bool + scoped_fn_items_scope voidptr + scoped_fn_output_path string + scoped_fn_output_paths []string + const_short_index &ConstShortIndex = unsafe { nil } + mut_recv_facts &FnNameFactCache = unsafe { nil } + local_typedef_shadow_facts &FnNameFactCache = unsafe { nil } + local_global_shadow_facts &ContextNameFactCache = unsafe { nil } + local_global_suffix_names map[string]bool + local_global_suffix_names_ready bool + generic_app_cache &GenericAppCache = unsafe { nil } + want_parallel_prep bool + want_parallel_c_extern_prep bool + // Set while selected items' C-extern refs still have to be collected: the + // fused prep walk defers them to the parallel exact-cost pass, which falls + // back to a serial top-up when it cannot run. + prep_externs_pending bool + prep_costs_pending bool + prep_typ_text_cache &PrepTypTextCache = unsafe { nil } + prep_alias_short_names map[string]bool + preseed_type_seen &PreseedTypeSeen = unsafe { nil } + // Separate seen-cache for the declaration preseed family + // (preseed_fn_ptr_type has optional-typedef side effects the body-walk + // preseed does not); armed only for the contiguous pre-dispatch preseed + // block, while no arena scope can be freed and reused. + preseed_sig_type_seen &PreseedTypeSeen = unsafe { nil } + struct_decl_pref_cache &StructDeclPrefCache = unsafe { nil } + unused_param_seen &UnusedParamSeen = unsafe { nil } + cache_split bool + cache_native_input_paths map[string]bool + program_body_only bool + cached_support_identifiers map[string]bool + // Set when the target is built with -prealloc / -d prealloc: the bump + // arena's base block pointer must be thread-local (matching V1's cgen), + // or every spawned thread would race on the same arena. + prealloc bool + scope_parallel_workers bool + worker_scope voidptr + parallel_worker_scopes []voidptr +} + +struct FixedArrayTypedefInfo { + arr types.ArrayFixed + module string +} + +struct FixedArrayTypeSeen { +mut: + w0 [4096]u64 + w1 [4096]u64 + modules [4096]string + seen [4096]bool +} + +struct FixedArrayTextSeen { +mut: + ptrs [4096]voidptr + lens [4096]int + modules [4096]string +} + +struct CDirective { + module string + text string + before_import bool + late bool +} + +struct NativeSourceContextDirective { + text string + before_import bool +} + +struct ObjectiveCppSourceRequest { + module string + source_path string + local_context []NativeSourceContextDirective + source_macros_possible bool +} + +struct CInlineHeader { + text string + preserved_directives []string + preserved_c_fns []string + preserved_c_structs []string + preserved_headers []CPreservedHeader +} + +struct CPreservedHeader { + include_arg string + source_file string +} + +// was_parallel reports whether the last fn codegen actually ran across threads. +pub fn (g &FlatGen) was_parallel() bool { + return g.parallel_used +} + +fn (g &FlatGen) timing_profile(message string) { + if !isnil(g.tc) && g.tc.verbose { + eprintln(message) + } +} + +@[inline] +fn (g &FlatGen) parse_node_type(node &flat.Node) types.Type { + return g.tc.parse_type_ref(node.typ, node.type_text_id()) +} + +pub fn (g &FlatGen) c_flags() []string { + return g.c_flags.clone() +} + +// set_initial_c_flags makes command-line C flags available while collecting directives. +pub fn (mut g FlatGen) set_initial_c_flags(flags []string) { + g.initial_c_flags = flags.clone() +} + +// set_c99_mode configures whether generated C should support strict C99 builds. +pub fn (mut g FlatGen) set_c99_mode(enabled bool) { + g.c99_mode = enabled +} + +// set_ccompiler records the selected C compiler for compiler-specific output constraints. +pub fn (mut g FlatGen) set_ccompiler(name string) { + g.ccompiler = name +} + +// set_prod controls production-only code generation such as removing assertions. +pub fn (mut g FlatGen) set_prod(enabled bool) { + g.is_prod = enabled +} + +// set_check_overflow enables runtime checks for integer addition, subtraction, and multiplication. +pub fn (mut g FlatGen) set_check_overflow(enabled bool) { + g.check_overflow = enabled +} + +// set_force_bounds_checking ignores direct-array-access attributes so every +// generated array access retains its runtime bounds check. +pub fn (mut g FlatGen) set_force_bounds_checking(enabled bool) { + g.force_bounds_checking = enabled +} + +// set_prealloc marks the build as using the -prealloc bump arena. +pub fn (mut g FlatGen) set_prealloc(on bool) { + g.prealloc = on +} + +// set_skip_generics removes generic-only metadata work when reachability proved +// that the generated program has no generic instantiations. +pub fn (mut g FlatGen) set_skip_generics(on bool) { + g.skip_generics = on +} + +// set_skip_enum_autostr omits synthesized enum string helpers when reachability +// proves the program cannot format an enum. +pub fn (mut g FlatGen) set_skip_enum_autostr(on bool) { + g.skip_enum_autostr = on +} + +fn (mut g FlatGen) push_scope() { + g.tc.push_scope() + g.ierror_stack_pointer_aliases << map[string]bool{} + g.scope_defer_starts << g.defers.len +} + +fn (mut g FlatGen) pop_scope() { + g.tc.pop_scope() + if g.ierror_stack_pointer_aliases.len > 0 { + g.ierror_stack_pointer_aliases.delete_last() + } + if g.scope_defer_starts.len > 0 { + g.scope_defer_starts.delete_last() + } +} + +fn (mut g FlatGen) enter_conditional_branch(has_scope bool) { + g.conditional_branch_depth++ + if has_scope && g.tc != unsafe { nil } && g.tc.cur_scope != unsafe { nil } { + g.conditional_branch_scopes << g.tc.cur_scope + g.conditional_branch_depths << g.conditional_branch_depth + } +} + +fn (mut g FlatGen) leave_conditional_branch() { + if g.conditional_branch_scopes.len > 0 + && g.conditional_branch_depths.last() == g.conditional_branch_depth { + g.conditional_branch_scopes.delete_last() + g.conditional_branch_depths.delete_last() + } + if g.conditional_branch_depth > 0 { + g.conditional_branch_depth-- + } +} + +fn (mut g FlatGen) declare_ierror_pointer_alias(name string, needs_copy bool) { + if name.len == 0 { + return + } + if g.ierror_stack_pointer_aliases.len == 0 { + g.ierror_stack_pointer_aliases << map[string]bool{} + } + last := g.ierror_stack_pointer_aliases.len - 1 + g.ierror_stack_pointer_aliases[last][name] = needs_copy +} + +fn (mut g FlatGen) assign_ierror_pointer_alias(name string, needs_copy bool) { + if name.len == 0 { + return + } + current_idx := g.ierror_stack_pointer_aliases.len - 1 + if idx := g.ierror_pointer_alias_scope_index(name) { + previous := if name in g.ierror_stack_pointer_aliases[idx] { + g.ierror_stack_pointer_aliases[idx][name] + } else { + false + } + g.ierror_stack_pointer_aliases[idx][name] = if idx == current_idx { + needs_copy + } else { + previous || needs_copy + } + return + } + g.declare_ierror_pointer_alias(name, needs_copy) +} + +fn (g &FlatGen) ierror_pointer_alias_scope_index(name string) ?int { + if name.len == 0 { + return none + } + mut scope := g.tc.cur_scope + mut idx := g.ierror_stack_pointer_aliases.len - 1 + for scope != unsafe { nil } && voidptr(scope) != voidptr(g.tc.file_scope) && idx >= 0 { + for existing in scope.names { + if existing == name { + return idx + } + } + scope = scope.parent + idx-- + } + return none +} + +fn (g &FlatGen) ierror_pointer_alias_needs_copy(name string) bool { + idx := g.ierror_pointer_alias_scope_index(name) or { return false } + if name in g.ierror_stack_pointer_aliases[idx] { + return g.ierror_stack_pointer_aliases[idx][name] + } + return false +} + +fn (mut g FlatGen) declare_local_pointer_storage(owner types.ScopeBindingOwner, is_pointer bool) { + key := owner.storage_key() + if key.len == 0 { + return + } + if is_pointer { + g.local_pointer_storage_by_owner[key] = true + } else { + g.local_pointer_storage_by_owner.delete(key) + } +} + +fn (mut g FlatGen) declare_ierror_owned_pointer(owner types.ScopeBindingOwner, is_owned bool) { + key := owner.storage_key() + if key.len == 0 { + return + } + if is_owned { + g.ierror_owned_pointer_by_owner[key] = true + } else { + g.ierror_owned_pointer_by_owner.delete(key) + } +} + +fn (g &FlatGen) ierror_local_pointer_is_owned(name string) bool { + if name.len == 0 || g.ierror_owned_pointer_by_owner.len == 0 { + return false + } + owner := g.local_storage_owner(name) or { return false } + return g.ierror_owned_pointer_by_owner[owner.storage_key()] or { false } +} + +fn (mut g FlatGen) declare_local_c_type(owner types.ScopeBindingOwner, c_type string) { + key := owner.storage_key() + if key.len == 0 { + return + } + if c_type.len > 0 { + g.local_c_type_by_owner[key] = c_type + } else { + g.local_c_type_by_owner.delete(key) + } +} + +fn (mut g FlatGen) declare_local_mutability(owner types.ScopeBindingOwner, is_mut bool) { + key := owner.storage_key() + if key.len == 0 { + return + } + if is_mut { + g.local_mutable_by_owner[key] = true + } else { + g.local_mutable_by_owner.delete(key) + } +} + +fn (g &FlatGen) local_storage_is_mutable(name string) bool { + owner := g.local_storage_owner(name) or { return false } + return g.local_mutable_by_owner[owner.storage_key()] or { false } +} + +fn (mut g FlatGen) declare_local_pointer_alias_source(owner types.ScopeBindingOwner, source string) { + g.declare_local_pointer_alias_source_kind(owner, source, false) +} + +fn (mut g FlatGen) declare_local_pointer_alias_source_kind(owner types.ScopeBindingOwner, source string, is_mut_param bool) { + key := owner.storage_key() + if key.len == 0 { + return + } + if source.len > 0 { + g.local_pointer_alias_by_owner[key] = source + if is_mut_param { + g.local_pointer_alias_mut_param[key] = true + } else { + g.local_pointer_alias_mut_param.delete(key) + } + } else { + g.local_pointer_alias_by_owner.delete(key) + g.local_pointer_alias_mut_param.delete(key) + } +} + +fn (g &FlatGen) local_storage_owner(name string) ?types.ScopeBindingOwner { + if g.tc == unsafe { nil } || g.tc.cur_scope == unsafe { nil } { + return none + } + owner := g.tc.cur_scope.lookup_owner(name) or { return none } + $if ownership ? { + if !g.tc.cur_scope.nearest_binding_owned_by(name, owner) { + return none + } + } + return owner +} + +fn (g &FlatGen) local_storage_c_type(name string) ?string { + if name.len == 0 || g.local_c_type_by_owner.len == 0 { + return none + } + owner := g.local_storage_owner(name) or { return none } + return g.local_c_type_by_owner[owner.storage_key()] or { none } +} + +fn (g &FlatGen) local_pointer_alias_source(name string) ?string { + if name.len == 0 || g.local_pointer_alias_by_owner.len == 0 { + return none + } + owner := g.local_storage_owner(name) or { return none } + return g.local_pointer_alias_by_owner[owner.storage_key()] or { none } +} + +fn (g &FlatGen) local_pointer_alias_source_is_mut_param(name string) bool { + if name.len == 0 || g.local_pointer_alias_mut_param.len == 0 { + return false + } + owner := g.local_storage_owner(name) or { return false } + return g.local_pointer_alias_mut_param[owner.storage_key()] or { false } +} + +fn (mut g FlatGen) declare_local_raw_type(owner types.ScopeBindingOwner, raw_type string) { + key := owner.storage_key() + if key.len == 0 { + return + } + if raw_type.len > 0 { + g.local_raw_type_by_owner[key] = raw_type + } else { + g.local_raw_type_by_owner.delete(key) + } +} + +fn (g &FlatGen) local_storage_raw_type(name string) ?string { + if name.len == 0 || g.local_raw_type_by_owner.len == 0 { + return none + } + owner := g.local_storage_owner(name) or { return none } + return g.local_raw_type_by_owner[owner.storage_key()] or { none } +} + +fn (mut g FlatGen) declare_local_shared_storage(owner types.ScopeBindingOwner, is_shared bool) { + key := owner.storage_key() + if key.len == 0 { + return + } + if is_shared { + g.local_shared_storage_by_owner[key] = true + } else { + g.local_shared_storage_by_owner.delete(key) + } +} + +fn (g &FlatGen) local_storage_is_shared(name string) bool { + if name.len == 0 || g.local_shared_storage_by_owner.len == 0 { + return false + } + owner := g.local_storage_owner(name) or { return false } + return g.local_shared_storage_by_owner[owner.storage_key()] or { false } +} + +fn (mut g FlatGen) declare_local_fn_value_c_name(owner types.ScopeBindingOwner, c_name string) { + key := owner.storage_key() + if key.len == 0 { + return + } + if c_name.len > 0 { + g.local_fn_value_c_name_by_owner[key] = c_name + } else { + g.local_fn_value_c_name_by_owner.delete(key) + } +} + +fn (g &FlatGen) local_fn_value_c_name(name string) ?string { + if name.len == 0 || g.local_fn_value_c_name_by_owner.len == 0 { + return none + } + owner := g.local_storage_owner(name) or { return none } + return g.local_fn_value_c_name_by_owner[owner.storage_key()] or { none } +} + +fn (g &FlatGen) local_storage_is_pointer(name string) bool { + if name.len == 0 { + return false + } + // Pointer-storage locals are rare; when none are registered the two scope + // chain walks below cannot change the answer (asked once per ident on the + // call-emission path). + if g.local_pointer_storage_by_owner.len == 0 { + return false + } + owner := g.local_storage_owner(name) or { return false } + return g.local_pointer_storage_by_owner[owner.storage_key()] or { false } +} + +// new creates a FlatGen value for c. +pub fn FlatGen.new() FlatGen { + return FlatGen{ + memo_usable_expr_types: os.getenv('V3_NO_CGEN_EXPR_TYPE_MEMO') == '' + cache_struct_fields: os.getenv('V3_NO_CGEN_STRUCT_FIELDS_CACHE') == '' + dedup_fn_decl_aliases: os.getenv('V3_NO_DEDUP_FN_DECL_ALIASES') == '' + prefix_param_scan: os.getenv('V3_NO_PREFIX_PARAM_SCAN') == '' + lean_parallel_worker_init: os.getenv('V3_NO_LEAN_CGEN_WORKER_INIT') == '' + lazy_param_abi_merge: os.getenv('V3_NO_LAZY_PARAM_ABI_MERGE') == '' + sb: strings.new_builder(4096) + fn_gen_items: []FlatFnGenItem{} + top_level_node_ids: []int{} + fn_segs: []string{} + fn_seg_chunk_indexes: []int{} + parallel_chunk_wrapper_defs: []ParallelChunkWrapperDefs{} + test_files: map[string]bool{} + profile_counters: []ProfileCounterMeta{} + coverage_files: map[string]&CoverageInfo{} + cache_program_files: map[string]bool{} + incremental_fn_names: map[string]bool{} + str_lit_ids: map[string]int{} + global_types: map[string]types.Type{} + global_raw_type_texts: map[string]string{} + enum_vals: map[string]int{} + enum_value_exprs: map[string]string{} + interfaces: map[string][]string{} + const_vals: map[string]flat.NodeId{} + const_modules: map[string]string{} + const_files: map[string]string{} + const_init_order: []string{} + fixed_storage_consts: map[string]bool{} + global_modules: map[string]string{} + global_files: map[string]string{} + global_inits: map[string]flat.NodeId{} + global_init_order: []string{} + enum_backing_infos: map[string]EnumBackingInfo{} + iface_impls: map[string][]string{} + interface_dispatch_required: map[string]bool{} + iface_type_ids: map[string]int{} + interface_boxed_types: map[string]bool{} + ierror_method_emit_names: map[string]bool{} + ierror_stack_pointer_aliases: []map[string]bool{} + ierror_owned_pointer_by_owner: map[string]bool{} + recursive_drop_helpers: map[string]string{} + local_pointer_storage_by_owner: map[string]bool{} + local_c_type_by_owner: map[string]string{} + local_mutable_by_owner: map[string]bool{} + local_pointer_alias_by_owner: map[string]string{} + local_pointer_alias_mut_param: map[string]bool{} + local_raw_type_by_owner: map[string]string{} + local_shared_storage_by_owner: map[string]bool{} + local_fn_value_c_name_by_owner: map[string]string{} + shadowed_global_locals: map[string]bool{} + sum_name_lookup: map[string]string{} + module_init_fns: []string{} + module_init_fn_modules: map[string]string{} + module_cleanup_fns: []string{} + module_cleanup_fn_modules: map[string]string{} + module_imports: map[string][]string{} + c_directives: []CDirective{} + preinclude_directives: []string{} + postinclude_directives: []string{} + early_c_source_directives: map[string]bool{} + native_source_contexts: map[string][]NativeSourceContextDirective{} + objective_cpp_source_requests: []ObjectiveCppSourceRequest{} + inlined_c_structs: map[string]bool{} + inlined_c_fns: map[string]bool{} + inlined_c_declared_fns: map[string]bool{} + inlined_c_static_fns: map[string]bool{} + cache_omitted_c_fns: map[string]bool{} + preserved_header_files_seen: map[string]bool{} + inlined_c_typedef_names: map[string]bool{} + initial_c_flags: []string{} + c_flags: []string{} + libc_compat_fns: map[string]bool{} + modules: map[string]string{} + fn_ptr_types: map[string]string{} + used_fn_ptr_types: map[string]bool{} + multi_return_types: []types.Type{} + multi_return_type_names: map[string]bool{} + fixed_array_ret_wrappers: map[string]bool{} + emitted_fixed_array_typedefs: map[string]bool{} + concrete_optional_abi_fns: map[string]bool{} + fixed_array_typedefs_needed: map[string]FixedArrayTypedefInfo{} + fixed_array_map_key_types: map[string]types.ArrayFixed{} + fn_decl_param_types: map[string][]types.Type{} + fn_decl_variadic: map[string]bool{} + fn_decl_variadic_short_counts: map[string]int{} + fn_decl_shared_params: map[string][]bool{} + fn_shared_params_resolved: map[string][]bool{} + fn_decl_mut_receivers: map[string]bool{} + fn_decl_ret_types: map[string]types.Type{} + fn_decl_nodes_by_name: map[string]flat.NodeId{} + fn_decl_nodes_by_short: map[string]flat.NodeId{} + fn_decl_nodes_by_module_short: map[string]flat.NodeId{} + non_generic_fn_names_by_module: map[string]bool{} + generic_fn_keys_by_short: map[string][]string{} + generic_fn_keys_by_cname: map[string][]string{} + generic_fn_key_ordinal: map[string]int{} + struct_decl_infos: map[string]StructDeclInfo{} + struct_decl_short_infos: map[string]StructDeclInfo{} + decl_attrs: map[int][]string{} + c_decl_abi_names: map[string]string{} + c_extern_global_names: map[string]string{} + shared_type_names: map[string]SharedTypeInfo{} + shared_alias_pointer_shorts: map[string]string{} + default_value_stack: map[string]bool{} + cur_param_names: []string{} + cur_param_type_values: []types.Type{} + cur_param_types: map[string]types.Type{} + cur_concrete_optional_params: map[string]bool{} + cur_mut_params: map[string]bool{} + cur_mut_pointer_params: map[string]bool{} + cur_mut_param_owners: map[string]types.ScopeBindingOwner{} + active_locks: []ActiveLock{} + conditional_branch_scopes: []&types.Scope{} + conditional_branch_depths: []int{} + loop_label_depths: map[string]int{} + loop_defer_starts: []int{} + loop_label_defer_starts: map[string]int{} + loop_control_copybacks: []LoopControlCopyback{} + map_loop_copyback_guards: []MapLoopCopybackGuard{} + goto_label_lock_scopes: map[string][]int{} + ownership_seen_return_sources: map[string]bool{} + needed_optional_types: map[string]string{} + emitted_optional_types: map[string]bool{} + emitted_fns: map[string]bool{} + array_method_cache: map[string]string{} + param_types_cache: map[string][]types.Type{} + interface_receiver_cache: &StringLookupCache{} + normalize_call_cache: &StringLookupCache{} + flattened_generic_name_cache: &StringLookupCache{} + generic_struct_context_ct_cache: &StringLookupCache{} + struct_cname_cache: &StringLookupCache{} + unique_struct_ct_cache: &StringLookupCache{} + alias_method_cache: &StringLookupCache{} + import_alias_cache: &ContextStringLookupCache{} + enum_selector_cache: &ContextStringLookupCache{} + enum_method_cache: &ContextStringLookupCache{} + qualified_enum_method_cache: &ContextStringLookupCache{} + embedded_fields_by_type: map[string][]types.StructField{} + param_types_by_short: map[string][]types.Type{} + generic_method_candidates: map[string][]GenericMethodCandidate{} + spawn_wrapper_names: map[string]string{} + spawn_wrapper_defs: []string{} + spawn_wrapper_defs_seen: map[string]bool{} + callback_wrapper_names: map[string]string{} + callback_wrapper_defs: []string{} + callback_wrapper_defs_seen: map[string]bool{} + emitted_loop_break_labels: map[string]bool{} + goto_label_c_names: map[string]string{} + c_name_cache: &CNameCache{} + const_short_index: &ConstShortIndex{} + mut_recv_facts: &FnNameFactCache{} + local_global_shadow_facts: &ContextNameFactCache{} + local_global_suffix_names: map[string]bool{} + generic_app_cache: &GenericAppCache{} + cached_support_identifiers: map[string]bool{} + str_lits: []string{} + defers: []flat.NodeId{} + scope_defer_starts: []int{} + fn_defers: []flat.NodeId{} + fn_defer_counts: map[int]string{} + assert_expr_overrides: map[int]string{} + defer_capture_names: []string{} + defer_capture_types: map[string]types.Type{} + const_runtime_inits: []string{} + const_runtime_init_modules: []string{} + runtime_inits: []string{} + runtime_init_modules: []string{} + compiler_vroot: '' + compiler_vexe: '' + target: pref.host_target() + line_start: true + } +} + +// top_level_nodes returns the precomputed declaration index in full cgen and +// preserves standalone generator helpers used by focused tests and tools. +fn (g &FlatGen) top_level_nodes() []int { + if g.top_level_node_ids.len > 0 { + return g.top_level_node_ids + } + mut ids := []int{} + for node_idx, node in g.a.nodes { + if node.kind in [.file, .module_decl, .fn_decl, .c_fn_decl, .struct_decl, .type_decl, + .global_decl, .const_decl, .enum_decl, .interface_decl, .import_decl, .directive] { + ids << node_idx + } + } + return ids +} + +// set_compiler_vexe sets the V executable path baked into generated test/runtime helpers. +pub fn (mut g FlatGen) set_compiler_vexe(path string) { + g.compiler_vexe = path +} + +// set_compiler_vexe_env_setup controls whether generated programs populate an unset VEXE. +pub fn (mut g FlatGen) set_compiler_vexe_env_setup(enabled bool) { + g.compiler_vexe_env_setup = enabled +} + +// set_target sets the canonical code-generation target. +pub fn (mut g FlatGen) set_target(target pref.Target) { + g.target = target +} + +// set_thread_stack_size configures the stack size used by generated worker threads. +pub fn (mut g FlatGen) set_thread_stack_size(size int) { + g.thread_stack_size = size +} + +// set_show_test_stats enables the per-test assertion summary used by `v -stats test`. +pub fn (mut g FlatGen) set_show_test_stats(enabled bool) { + g.show_test_stats = enabled +} + +// set_show_test_summary enables the aggregate report used by the `v test` command. +pub fn (mut g FlatGen) set_show_test_summary(enabled bool) { + g.show_test_summary = enabled +} + +// set_test_run_only limits the generated test harness to matching test functions. +pub fn (mut g FlatGen) set_test_run_only(patterns []string) { + g.test_run_only = patterns.clone() +} + +// set_print_fn_names selects generated C functions to print to stdout. +pub fn (mut g FlatGen) set_print_fn_names(names []string) { + g.print_fn_names = names.clone() +} + +// set_profile configures V1-compatible per-function runtime profiling. +pub fn (mut g FlatGen) set_profile(file string, no_inline bool, fn_names []string) { + g.profile_file = file + g.profile_no_inline = no_inline + g.profile_fns = fn_names.clone() +} + +// set_shared configures shared-library entry point generation. +pub fn (mut g FlatGen) set_shared(enabled bool) { + g.is_shared = enabled +} + +// set_object_file_mode gives generated runtime symbols translation-unit-local +// linkage while retaining public entry-module functions through C ABI wrappers. +pub fn (mut g FlatGen) set_object_file_mode(enabled bool) { + g.object_file_mode = enabled +} + +// set_suppress_main disables executable entry point generation for `-d no_main` builds. +pub fn (mut g FlatGen) set_suppress_main(enabled bool) { + g.suppress_main = enabled +} + +// set_compile_values records explicit `-d` values so `$d(...)` inside `#flag` +// directives resolves configured values over fallbacks. +pub fn (mut g FlatGen) set_compile_values(values map[string]string) { + g.compile_values = values.clone() +} + +// set_cache_split enables stable cache markers and string symbols in generated C. +// The v3 driver uses them to split one checked program into independently cached +// module objects without changing regular `-o file.c` output. +pub fn (mut g FlatGen) set_cache_split(enabled bool) { + g.cache_split = enabled +} + +// set_cache_native_input_paths assigns native headers and sources to their owning +// cached module object instead of the shared generated declaration prefix. +pub fn (mut g FlatGen) set_cache_native_input_paths(paths []string) { + g.cache_native_input_paths = map[string]bool{} + for path in paths { + g.cache_native_input_paths[os.real_path(path)] = true + } +} + +// set_program_body_only omits the reusable declaration/type prefix. It is used +// when that prefix has already been validated and loaded from the module cache. +pub fn (mut g FlatGen) set_program_body_only(enabled bool) { + g.program_body_only = enabled +} + +// set_cache_program_files assigns entry-module source files to the program +// translation unit rather than an imported module cache object. +pub fn (mut g FlatGen) set_cache_program_files(files []string) { + g.cache_program_files = map[string]bool{} + for file in files { + g.cache_program_files[file] = true + g.cache_program_files[os.real_path(file)] = true + } +} + +// set_incremental_fn_names limits program-body generation to functions whose +// parsed bodies changed. An empty map preserves normal whole-program emission. +pub fn (mut g FlatGen) set_incremental_fn_names(names map[string]bool) { + g.incremental_fn_names = names.clone() +} + +// set_cached_support_declarations records C identifiers already supplied by the +// cached program prefix so body-only generation emits only newly needed typedefs. +pub fn (mut g FlatGen) set_cached_support_declarations(source string) { + g.cached_support_identifiers.clear() + mut i := 0 + for i < source.len { + if source[i] == `/` && i + 1 < source.len { + if source[i + 1] == `/` { + i += 2 + for i < source.len && source[i] != `\n` { + i++ + } + continue + } + if source[i + 1] == `*` { + i += 2 + for i + 1 < source.len && !(source[i] == `*` && source[i + 1] == `/`) { + i++ + } + if i + 1 < source.len { + i += 2 + } + continue + } + } + if source[i] in [`'`, `"`] { + quote := source[i] + i++ + for i < source.len { + if source[i] == `\\` && i + 1 < source.len { + i += 2 + continue + } + i++ + if source[i - 1] == quote { + break + } + } + continue + } + if !c_identifier_start(source[i]) { + i++ + continue + } + start := i + i++ + for i < source.len && c_identifier_continue(source[i]) { + i++ + } + g.cached_support_identifiers[source[start..i]] = true + } +} + +// cache_external_input_files returns local include/embed inputs grouped by the +// module whose cached object incorporates their contents, plus the ordered root +// native source includes for each module. Forced-include inputs affect every +// object and are kept in a configuration-wide group. The last result reports +// include forms whose dependencies cannot be resolved statically. +pub fn cache_external_input_files(a &flat.FlatAst, vroot string, source_modules map[string]bool, initial_c_flags []string, target pref.Target) (map[string][]string, map[string][]string, bool) { + mut c_flags := []string{} + mut cur_file := '' + for node in a.nodes { + if node.kind == .file { + cur_file = node.value + continue + } + if node.kind != .directive || node.value != 'flag' || node.typ.len == 0 { + continue + } + for flag in c_flag_args(node.typ, vroot, cur_file, target) { + if flag.len > 0 && flag !in c_flags { + c_flags << flag + } + } + } + c_flags << initial_c_flags + inputs, native_source_roots, _, _, _, _, _, has_untracked_include := cache_external_input_files_with_resolved_flags(a, + vroot, source_modules, c_flags, target, map[string]bool{}, map[string]string{}, false) + return inputs, native_source_roots, has_untracked_include +} + +// cache_external_input_files_with_resolved_flags collects cache inputs without +// resolving source `#flag` directives a second time. unscoped_inputs contains the +// dependency trees of native source roots and direct non-source includes whose +// linkage can cross generated units. resolution_dirs contains every searched include +// directory whose contents can change path resolution; missing_resolution_paths +// are the first nonexistent path components searched. Directives from program_files +// belong to the program translation unit even when a library test declares that module. +pub fn cache_external_input_files_with_resolved_flags(a &flat.FlatAst, vroot string, source_modules map[string]bool, c_flags []string, target pref.Target, program_files map[string]bool, compiler_macros map[string]string, compiler_macro_environment_complete bool) (map[string][]string, map[string][]string, map[string][]string, map[string][]string, map[string][]string, []string, []string, bool) { + include_dirs := c_flag_include_dirs(c_flags) + flag_inputs, flags_have_untracked_include, mut include_macros, mut dynamic_include_macros, mut resolution_dirs, mut missing_resolution_paths := cache_c_flag_input_files_with_status(c_flags, + compiler_macros, compiler_macro_environment_complete) + mut collect_modules := map[string]bool{} + for module_name, enabled in source_modules { + if enabled { + collect_modules[module_name] = true + collect_modules[module_name.all_after_last('.')] = true + } + } + if program_files.len > 0 { + collect_modules['main'] = true + } + mut inputs := map[string][]string{} + mut native_source_roots := map[string][]string{} + mut native_root_contexts := map[string][]string{} + mut unscoped_inputs := map[string][]string{} + mut static_storage_inputs := map[string][]string{} + mut has_untracked_include := false + mut collected_paths := map[string]bool{} + mut ambiguous_collected_paths := map[string]bool{} + mut active_static_storage_paths := map[string]bool{} + mut cur_module := '' + mut cur_file := '' + mut cur_file_is_program := false + mut context_directives := map[string][]string{} + mut conditional_context_mutations := map[string]bool{} + mut conditional_depth := 0 + for node in a.nodes { + if node.kind == .file { + cur_file = node.value + cur_file_is_program = program_files[cur_file] || program_files[os.real_path(cur_file)] + cur_module = '' + conditional_depth = 0 + continue + } + if node.kind == .module_decl { + cur_module = node.value + continue + } + owner_module := if cur_file_is_program { + 'main' + } else if cur_module.len > 0 { + cur_module + } else { + 'main' + } + if !collect_modules[owner_module] { + continue + } + if node.kind == .directive { + if node.value in ['if', 'ifdef', 'ifndef'] { + conditional_depth++ + } else if node.value == 'endif' && conditional_depth > 0 { + conditional_depth-- + } + } + if node.kind == .directive && node.value in ['define', 'undef'] { + directive := c_preprocessor_directive_line(node.value, node.typ) + is_conditional := conditional_depth > 0 + c_record_include_macro_definition(directive, is_conditional, mut include_macros, mut + dynamic_include_macros) + mut module_context := context_directives[owner_module] + module_context << directive + context_directives[owner_module] = module_context + if is_conditional { + conditional_context_mutations[owner_module] = true + } + continue + } + if node.kind == .directive + && node.value in ['include', 'insert', 'preinclude', 'postinclude'] && node.typ.len > 0 { + include_arg := c_include_arg_for_target(node.typ, vroot, cur_file, target) + if include_arg.len == 0 { + continue + } + if c_include_arg_is_builtin_abi_helper(include_arg, vroot) { + continue + } + if !c_include_arg_is_literal(include_arg) { + if os.getenv('V3_CACHE_TRACE') != '' { + eprintln(' V3 module cache dynamic source include: file=${cur_file} include=${include_arg}') + } + has_untracked_include = true + continue + } + context_is_replayable := conditional_depth == 0 + && !conditional_context_mutations[owner_module] + for path in c_include_file_paths(include_arg, vroot, cur_file, include_dirs) { + c_record_cache_resolution_path(path, mut resolution_dirs, mut + missing_resolution_paths) + if !os.is_file(path) { + continue + } + is_source_input := c_include_arg_is_source_file(include_arg) + if is_source_input || node.value == 'insert' { + real_path := os.real_path(path) + if !c_add_cache_native_source_root(mut native_source_roots, mut + native_root_contexts, owner_module, real_path, + context_directives[owner_module], context_is_replayable) { + has_untracked_include = true + } + } + mut active_paths := map[string]bool{} + mut files := []string{} + if c_collect_external_input_tree(path, vroot, include_dirs, mut active_paths, mut + collected_paths, mut ambiguous_collected_paths, mut files, mut include_macros, mut + dynamic_include_macros, mut resolution_dirs, mut missing_resolution_paths, mut + active_static_storage_paths, owner_module, false, + compiler_macro_environment_complete) + { + has_untracked_include = true + } + for file in files { + c_add_cache_external_input(mut inputs, owner_module, file) + if is_source_input || include_arg.trim_space().starts_with('"') { + c_add_cache_external_input(mut unscoped_inputs, owner_module, file) + collection_key := owner_module + '\x00' + os.real_path(file) + if active_static_storage_paths[collection_key] { + c_add_cache_external_input(mut static_storage_inputs, owner_module, + file) + } + } + } + if !is_source_input && include_arg.trim_space().starts_with('"') + && files.any(active_static_storage_paths[owner_module + '\x00' + os.real_path(it)]) { + real_path := os.real_path(path) + if !c_add_cache_native_source_root(mut native_source_roots, mut + native_root_contexts, owner_module, real_path, + context_directives[owner_module], context_is_replayable) { + has_untracked_include = true + } + } + break + } + continue + } + if path := c_embed_external_input_path(a, node) { + c_add_cache_external_input(mut inputs, owner_module, path) + } + } + if flags_have_untracked_include { + has_untracked_include = true + } + for path in flag_inputs { + c_add_cache_external_input(mut inputs, '__v3_c_flags__', path) + } + for module_name, paths in inputs { + mut sorted := paths.clone() + sorted.sort() + inputs[module_name] = sorted + } + for module_name, paths in unscoped_inputs { + mut sorted := paths.clone() + sorted.sort() + unscoped_inputs[module_name] = sorted + } + for module_name, paths in static_storage_inputs { + mut sorted := paths.clone() + sorted.sort() + static_storage_inputs[module_name] = sorted + } + mut sorted_resolution_dirs := resolution_dirs.keys() + sorted_resolution_dirs.sort() + mut sorted_missing_resolution_paths := missing_resolution_paths.keys() + sorted_missing_resolution_paths.sort() + return inputs, native_source_roots, native_root_contexts, unscoped_inputs, static_storage_inputs, sorted_resolution_dirs, sorted_missing_resolution_paths, has_untracked_include +} + +fn c_add_cache_native_source_root(mut native_source_roots map[string][]string, mut native_root_contexts map[string][]string, owner_module string, real_path string, context []string, context_is_replayable bool) bool { + mut roots := native_source_roots[owner_module] + if real_path !in roots { + roots << real_path + native_source_roots[owner_module] = roots + } + if real_path in native_root_contexts { + if native_root_contexts[real_path] != context { + if os.getenv('V3_CACHE_TRACE') != '' { + eprintln(' V3 module cache incompatible native root contexts: module=${owner_module} path=${real_path}') + } + return false + } + } else { + native_root_contexts[real_path] = context.clone() + } + if !context_is_replayable { + if os.getenv('V3_CACHE_TRACE') != '' { + eprintln(' V3 module cache conditional native root context: module=${owner_module} path=${real_path}') + } + return false + } + return true +} + +// cache_native_inputs_need_objective_c reports whether cgen will implicitly +// compile the generated translation unit as Objective-C because of a source +// directive rather than an explicit compiler flag. +pub fn cache_native_inputs_need_objective_c(a &flat.FlatAst, vroot string, c_flags []string, c99_mode bool, target pref.Target) bool { + include_dirs := c_flag_include_dirs(c_flags) + mut cur_file := '' + for node in a.nodes { + if node.kind == .file { + cur_file = node.value + continue + } + if node.kind != .directive || node.value !in ['include', 'insert'] || node.typ.len == 0 { + continue + } + include_arg := c_include_arg_for_target(node.typ, vroot, cur_file, target) + if include_arg.len == 0 || c_include_arg_is_builtin_abi_helper(include_arg, vroot) { + continue + } + if c_include_arg_is_source_file(include_arg) { + for path in c_include_file_paths(include_arg, vroot, cur_file, include_dirs) { + if cache_native_input_path_needs_objective_c(path, c_flags, c99_mode, target) { + return true + } + } + continue + } + if header := c_inline_header_text(include_arg, vroot, cur_file, include_dirs, false) { + if c_header_text_needs_objective_c_for_target(header.text, c_flags, c99_mode, target) { + return true + } + } + } + return false +} + +// cache_native_input_path_needs_objective_c reports whether a native input +// must use the Objective-C preprocessor language selected by cgen. +pub fn cache_native_input_path_needs_objective_c(path string, c_flags []string, c99_mode bool, target pref.Target) bool { + if !os.is_file(path) { + return false + } + if path.ends_with('.m') { + return true + } + text := os.read_file(path) or { return false } + return c_header_text_needs_objective_c_for_target(text, c_flags, c99_mode, target) +} + +// cache_native_input_language reports the exact preprocessor language cgen +// selects for a native input. The dependency probe and privacy preprocessing must +// use it so they observe the same predefined macros as the real build; an `.mm` +// source, for example, is Objective-C++ and defines both `__OBJC__` and +// `__cplusplus`, so probing it as plain C or Objective-C omits `__cplusplus` and +// wrongly discards branches guarded by it. +pub fn cache_native_input_language(path string, c_flags []string, c99_mode bool, target pref.Target) string { + if path.ends_with('.mm') { + return 'objective-c++' + } + if path.ends_with('.m') { + return 'objective-c' + } + if path.ends_with('.cc') || path.ends_with('.cpp') { + return 'c++' + } + if cache_native_input_path_needs_objective_c(path, c_flags, c99_mode, target) { + return 'objective-c' + } + return 'c' +} + +// cache_native_inputs_language reports the richest preprocessor language any +// native input requires. The shared compiler-macro probe uses it so it never omits +// a language macro (`__OBJC__`, `__cplusplus`) that an input's active branches +// depend on. +pub fn cache_native_inputs_language(a &flat.FlatAst, vroot string, c_flags []string, c99_mode bool, target pref.Target) string { + include_dirs := c_flag_include_dirs(c_flags) + mut need_objc := false + mut need_cpp := false + mut cur_file := '' + for node in a.nodes { + if node.kind == .file { + cur_file = node.value + continue + } + if node.kind != .directive || node.value !in ['include', 'insert'] || node.typ.len == 0 { + continue + } + include_arg := c_include_arg_for_target(node.typ, vroot, cur_file, target) + if include_arg.len == 0 || c_include_arg_is_builtin_abi_helper(include_arg, vroot) { + continue + } + if c_include_arg_is_source_file(include_arg) { + for path in c_include_file_paths(include_arg, vroot, cur_file, include_dirs) { + match cache_native_input_language(path, c_flags, c99_mode, target) { + 'objective-c++' { + need_objc = true + need_cpp = true + } + 'objective-c' { + need_objc = true + } + 'c++' { + need_cpp = true + } + else {} + } + } + continue + } + if header := c_inline_header_text(include_arg, vroot, cur_file, include_dirs, false) { + if c_header_text_needs_objective_c_for_target(header.text, c_flags, c99_mode, target) { + need_objc = true + } + } + } + return c_native_language_from_features(need_objc, need_cpp) +} + +fn c_native_language_from_features(need_objc bool, need_cpp bool) string { + if need_objc && need_cpp { + return 'objective-c++' + } + if need_cpp { + return 'c++' + } + if need_objc { + return 'objective-c' + } + return 'c' +} + +// cache_c_flag_input_files returns forced include/macro files whose contents +// affect every cached object compiled with the supplied C flags. +pub fn cache_c_flag_input_files(flags []string) []string { + files, _, _, _, _, _ := cache_c_flag_input_files_with_status(flags, map[string]string{}, false) + return files +} + +fn cache_c_flag_input_files_with_status(flags []string, compiler_macros map[string]string, compiler_macro_environment_complete bool) ([]string, bool, map[string][]string, map[string]bool, map[string]bool, map[string]bool) { + include_dirs := c_flag_include_dirs(flags) + mut active_paths := map[string]bool{} + mut collected_paths := map[string]bool{} + mut ambiguous_collected_paths := map[string]bool{} + mut files := []string{} + mut resolution_dirs := map[string]bool{} + mut missing_resolution_paths := map[string]bool{} + mut active_static_storage_paths := map[string]bool{} + mut has_untracked_include := false + mut include_macros, mut dynamic_include_macros := c_flag_include_macro_definitions(flags, + compiler_macros) + for forced_input in c_forced_include_inputs(flags) { + for path in c_include_file_paths('"${forced_input}"', '', '', include_dirs) { + c_record_cache_resolution_path(path, mut resolution_dirs, mut missing_resolution_paths) + if !os.is_file(path) { + continue + } + if c_collect_external_input_tree(path, '', include_dirs, mut active_paths, mut + collected_paths, mut ambiguous_collected_paths, mut files, mut include_macros, mut + dynamic_include_macros, mut resolution_dirs, mut missing_resolution_paths, mut + active_static_storage_paths, '__v3_c_flags__', false, + compiler_macro_environment_complete) + { + has_untracked_include = true + } + break + } + } + files.sort() + return files, has_untracked_include, include_macros, dynamic_include_macros, resolution_dirs, missing_resolution_paths +} + +fn c_forced_include_inputs(flags []string) []string { + mut inputs := []string{} + mut expect_input := false + for flag in flags { + token := flag.trim_space() + if expect_input { + inputs << token.trim('"\'') + expect_input = false + continue + } + if token in ['-include', '-imacros'] { + expect_input = true + continue + } + for prefix in ['-include=', '-imacros='] { + if token.starts_with(prefix) && token.len > prefix.len { + inputs << token[prefix.len..].trim('"\'') + } + } + } + return inputs +} + +// tokenize_c_flag splits a C flag on unquoted whitespace while preserving quotes. +pub fn tokenize_c_flag(value string) []string { + return util.tokenize_c_flag(value) +} + +fn c_add_cache_external_input(mut inputs map[string][]string, module_name string, path string) { + if module_name.len == 0 || path.len == 0 || !os.is_file(path) { + return + } + real_path := os.real_path(path) + mut paths := inputs[module_name] + if real_path !in paths { + paths << real_path + inputs[module_name] = paths + } +} + +struct CCacheConditional { + parent_inactive bool +mut: + condition int + inactive bool + ambiguous bool +} + +fn c_collect_external_input_tree(path string, vroot string, include_dirs []string, mut active_paths map[string]bool, mut collected_paths map[string]bool, mut ambiguous_collected_paths map[string]bool, mut files []string, mut include_macros map[string][]string, mut dynamic_include_macros map[string]bool, mut resolution_dirs map[string]bool, mut missing_resolution_paths map[string]bool, mut active_static_storage_paths map[string]bool, collection_scope string, ambient_ambiguous bool, compiler_macro_environment_complete bool) bool { + if path.len == 0 || !os.is_file(path) { + return false + } + real_path := os.real_path(path) + if active_paths[real_path] { + return false + } + collection_key := collection_scope + '\x00' + real_path + first_collection := !collected_paths[collection_key] + mut text := '' + if collected_paths[collection_key] { + text = os.read_file(real_path) or { return true } + if guard := c_whole_file_guard_macro(text) { + // The preprocessor skips a repeat include only while the guard is definitely + // still defined: `#pragma once` always is, and an `#ifndef NAME` guard is when + // NAME is a concrete define or a definitely-defined dynamic macro. An ordinary + // diamond re-include then contributes no new inputs and stays cacheable (subject + // to first-traversal ambiguity). If the guard was `#undef`d — or its defined + // state is only ambiguous (`dynamic_include_macros[NAME] == false`) after a + // conditional `#undef` under an unresolved branch — the preprocessor may traverse + // the file again and pull in newly selected dependencies, so fall through and + // rescan. The value test matches c_cache_known_condition, where `false` is + // ambiguous rather than defined. + guard_in_effect := guard.len == 0 || guard in include_macros + || dynamic_include_macros[guard] + if guard_in_effect { + return ambiguous_collected_paths[collection_key] + } + } + } + active_paths[real_path] = true + defer { + active_paths.delete(real_path) + } + if first_collection { + collected_paths[collection_key] = true + if ambient_ambiguous { + ambiguous_collected_paths[collection_key] = true + } + files << real_path + } + if text.len == 0 { + text = os.read_file(real_path) or { return false } + } + if first_collection { + if guard := c_whole_file_guard_macro(text) { + if guard.len > 0 && guard in include_macros { + // Macro state can arrive from the same guarded system header scanned + // for another cache unit. Its companion macros are unit-local, so make + // the first traversal in this scope collect the complete guarded body. + include_macros.delete(guard) + dynamic_include_macros.delete(guard) + } + } + } + mut has_untracked_include := false + mut possible_source := strings.new_builder(text.len) + mut in_block_comment := false + mut conditionals := []CCacheConditional{} + for line in text.split_into_lines() { + clean, next_in_block_comment := c_preprocessor_directive_scan_line(line, in_block_comment) + in_block_comment = next_in_block_comment + directive_name := c_directive_name(clean) + if directive_name in ['if', 'ifdef', 'ifndef'] { + parent_inactive := conditionals.any(it.inactive) + parent_ambiguous := conditionals.any(it.ambiguous) + condition := c_cache_known_condition(clean, include_macros, dynamic_include_macros, + compiler_macro_environment_complete) + conditionals << CCacheConditional{ + parent_inactive: parent_inactive + condition: condition + inactive: parent_inactive || condition < 0 + ambiguous: parent_ambiguous || condition == 0 + } + continue + } + if directive_name in ['else', 'elif'] && conditionals.len > 0 { + conditional_idx := conditionals.len - 1 + mut conditional := conditionals[conditional_idx] + if directive_name == 'else' { + conditional.inactive = conditional.parent_inactive || conditional.condition > 0 + } else if conditional.condition > 0 { + conditional.inactive = true + } else { + next_condition := c_cache_known_condition(clean, include_macros, + dynamic_include_macros, compiler_macro_environment_complete) + conditional.condition = next_condition + conditional.ambiguous = conditional.ambiguous || next_condition == 0 + conditional.inactive = conditional.parent_inactive || conditional.condition < 0 + } + conditionals[conditional_idx] = conditional + continue + } + if directive_name == 'endif' { + if conditionals.len > 0 { + conditionals.delete_last() + } + continue + } + if conditionals.any(it.inactive) { + continue + } + possible_source.writeln(line) + if directive_name !in ['include', 'import'] { + c_record_include_macro_definition(clean, ambient_ambiguous + || conditionals.any(it.ambiguous), mut include_macros, mut dynamic_include_macros) + continue + } + mut include_args := [c_include_arg(c_directive_arg(clean), vroot, real_path)] + if !c_include_arg_is_literal(include_args[0]) { + macro_name := include_args[0].trim_space() + // A `true` value marks a nonliteral dynamic definition; a `false` value + // marks an ambiguous mutation. Both make the include target unknowable, so + // membership alone is untracked. Falling through on a `false` entry would let + // literal recovery adopt a stale textual literal that the real preprocessor + // never selects. + if macro_name in dynamic_include_macros { + if os.getenv('V3_CACHE_TRACE') != '' { + eprintln(' V3 module cache dynamic nested include: file=${real_path} include=${macro_name}') + } + has_untracked_include = true + continue + } + include_args = include_macros[macro_name].clone() + if include_args.len == 0 { + literal_values := c_literal_include_macro_values(files, macro_name) + if literal_values.len == 1 { + include_args = literal_values.clone() + include_macros[macro_name] = include_args.clone() + dynamic_include_macros.delete(macro_name) + } + } + if include_args.len == 0 { + if os.getenv('V3_CACHE_TRACE') != '' { + eprintln(' V3 module cache unresolved nested include: file=${real_path} include=${macro_name} known=${macro_name in include_macros} dynamic=${macro_name in dynamic_include_macros}') + } + has_untracked_include = true + continue + } + } + for include_arg in include_args { + for nested_path in c_include_file_paths(include_arg, vroot, real_path, include_dirs) { + c_record_cache_resolution_path(nested_path, mut resolution_dirs, mut + missing_resolution_paths) + if !os.is_file(nested_path) { + continue + } + nested_ambiguous := ambient_ambiguous || conditionals.any(it.ambiguous) + if c_collect_external_input_tree(nested_path, vroot, include_dirs, mut + active_paths, mut collected_paths, mut ambiguous_collected_paths, mut files, mut + include_macros, mut dynamic_include_macros, mut resolution_dirs, mut + missing_resolution_paths, mut active_static_storage_paths, collection_scope, + nested_ambiguous, compiler_macro_environment_complete) + { + has_untracked_include = true + } + break + } + } + } + possible_text := possible_source.str() + if modulecache.c_source_has_static_storage(possible_text) { + active_static_storage_paths[collection_key] = true + if os.getenv('V3_CACHE_TRACE') != '' { + eprintln(' V3 module cache active static C input: module=${collection_scope} path=${real_path}') + } + } + unsafe { possible_source.free() } + return has_untracked_include +} + +fn c_literal_include_macro_values(paths []string, macro_name string) []string { + mut values := []string{} + for path in paths { + text := os.read_file(path) or { continue } + mut in_block_comment := false + for line in text.split_into_lines() { + clean, next_in_block_comment := c_preprocessor_directive_scan_line(line, + in_block_comment) + in_block_comment = next_in_block_comment + if c_directive_name(clean) != 'define' { + continue + } + definition := c_directive_arg(clean) + fields := definition.fields() + if fields.len == 0 || fields[0] != macro_name { + continue + } + value := definition[macro_name.len..].trim_space() + if c_include_arg_is_literal(value) && value !in values { + values << value + } + } + } + values.sort() + return values +} + +// c_whole_file_guard_macro returns the include guard that gates a whole-file +// guarded header: an empty string for `#pragma once` (always effective), the +// macro name for an `#ifndef NAME` / `#define NAME` wrapper, or none when the +// file is not whole-file guarded. Callers consult the macro's current defined +// state to decide whether the preprocessor would skip a repeat include. +fn c_whole_file_guard_macro(text string) ?string { + mut in_block_comment := false + mut guard_name := '' + mut guard_defined := false + mut guard_closed := false + mut conditional_depth := 0 + for line in text.split_into_lines() { + clean, next_in_block_comment := c_preprocessor_directive_scan_line(line, in_block_comment) + in_block_comment = next_in_block_comment + if clean.trim_space().len == 0 { + continue + } + if guard_closed { + return none + } + directive_name := c_directive_name(clean) + if guard_name.len == 0 { + if directive_name == 'pragma' && c_directive_arg(clean).trim_space() == 'once' { + return '' + } + guard_name = c_whole_file_guard_name(clean) + if guard_name.len == 0 { + return none + } + conditional_depth = 1 + continue + } + if !guard_defined { + define_fields := c_directive_arg(clean).fields() + if directive_name != 'define' || define_fields.len == 0 + || define_fields[0] != guard_name { + return none + } + guard_defined = true + continue + } + if directive_name in ['if', 'ifdef', 'ifndef'] { + conditional_depth++ + } else if directive_name == 'endif' { + conditional_depth-- + if conditional_depth == 0 { + guard_closed = true + } + } else if directive_name in ['else', 'elif'] && conditional_depth == 1 { + // A guard-level `#else`/`#elif` runs an alternative branch when the guard + // macro is already defined, so a repeat include is not skipped — the file is + // not whole-file guarded. (A nested branch at depth > 1 is guarded content.) + return none + } + } + if guard_defined && guard_closed { + return guard_name + } + return none +} + +fn c_whole_file_guard_name(directive string) string { + name := c_directive_name(directive) + if name == 'ifndef' { + fields := c_directive_arg(directive).fields() + return if fields.len == 1 { fields[0] } else { '' } + } + if name != 'if' { + return '' + } + expression := c_directive_arg(directive).replace(' ', '').replace('\t', '') + if expression.starts_with('!defined(') && expression.ends_with(')') { + return expression['!defined('.len..expression.len - 1] + } + if expression.starts_with('!defined') { + return expression['!defined'.len..] + } + return '' +} + +// A false dynamic_include_macros value marks a macro whose defined state is ambiguous. +fn c_cache_known_condition(directive string, include_macros map[string][]string, dynamic_include_macros map[string]bool, compiler_macro_environment_complete bool) int { + name := c_directive_name(directive) + expression := c_directive_arg(directive).trim_space() + if name in ['ifdef', 'ifndef'] { + return c_cache_macro_condition(expression, name == 'ifndef', include_macros, + dynamic_include_macros, compiler_macro_environment_complete) + } + return c_cache_known_expression(expression, include_macros, dynamic_include_macros, + compiler_macro_environment_complete) +} + +fn c_cache_known_expression(raw_expression string, include_macros map[string][]string, dynamic_include_macros map[string]bool, compiler_macro_environment_complete bool) int { + expression := c_header_condition_without_outer_parens(raw_expression.trim_space()) + or_parts := c_header_condition_top_level_parts(expression, '||') + if or_parts.len > 1 { + mut all_false := true + for part in or_parts { + condition := c_cache_known_expression(part, include_macros, dynamic_include_macros, + compiler_macro_environment_complete) + if condition > 0 { + return 1 + } + all_false = all_false && condition < 0 + } + return if all_false { -1 } else { 0 } + } + and_parts := c_header_condition_top_level_parts(expression, '&&') + if and_parts.len > 1 { + mut all_true := true + for part in and_parts { + condition := c_cache_known_expression(part, include_macros, dynamic_include_macros, + compiler_macro_environment_complete) + if condition < 0 { + return -1 + } + all_true = all_true && condition > 0 + } + return if all_true { 1 } else { 0 } + } + if expression.starts_with('!') { + condition := c_cache_known_expression(expression[1..], include_macros, + dynamic_include_macros, compiler_macro_environment_complete) + return if condition == 0 { 0 } else { -condition } + } + if macro_name := c_header_defined_macro_name(expression) { + return c_cache_macro_condition(macro_name, false, include_macros, dynamic_include_macros, + compiler_macro_environment_complete) + } + if expression == '0' { + return -1 + } + if expression == '1' { + return 1 + } + return 0 +} + +fn c_cache_macro_condition(macro_name string, invert bool, include_macros map[string][]string, dynamic_include_macros map[string]bool, compiler_macro_environment_complete bool) int { + if macro_name in dynamic_include_macros && !dynamic_include_macros[macro_name] { + return 0 + } + if macro_name !in include_macros && macro_name !in dynamic_include_macros { + if compiler_macro_environment_complete { + return if invert { 1 } else { -1 } + } + // Preserve both branches when the compiler may provide this macro. + return 0 + } + return if invert { -1 } else { 1 } +} + +fn c_record_cache_resolution_path(path string, mut resolution_dirs map[string]bool, mut missing_resolution_paths map[string]bool) { + if path.len == 0 { + return + } + mut dir := os.dir(os.abs_path(path)) + mut first_missing := '' + for dir.len > 0 { + if os.is_dir(dir) { + if first_missing.len > 0 { + missing_resolution_paths[first_missing] = true + } else { + resolution_dirs[dir] = true + real_dir := os.real_path(dir) + if real_dir.len > 0 { + resolution_dirs[real_dir] = true + } + } + return + } + first_missing = dir + parent := os.dir(dir) + if parent == dir { + return + } + dir = parent + } +} + +fn c_flag_include_macro_definitions(flags []string, compiler_macros map[string]string) (map[string][]string, map[string]bool) { + mut include_macros := map[string][]string{} + mut dynamic_include_macros := map[string]bool{} + for name, value in compiler_macros { + c_record_include_macro_value(name, value, mut include_macros, mut dynamic_include_macros) + } + mut i := 0 + for i < flags.len { + clean := flags[i].trim_space() + mut definition := '' + if clean == '-D' && i + 1 < flags.len { + i++ + definition = flags[i].trim_space() + } else if clean.starts_with('-D') { + definition = clean[2..].trim_space() + } + if definition.len > 0 { + name := definition.all_before('=').trim_space() + value := if definition.contains('=') { + definition.all_after('=').trim_space() + } else { + '' + } + c_record_include_macro_value(name, value, mut include_macros, mut + dynamic_include_macros) + } + i++ + } + return include_macros, dynamic_include_macros +} + +fn c_record_include_macro_definition(directive string, ambiguous bool, mut include_macros map[string][]string, mut dynamic_include_macros map[string]bool) { + directive_name := c_directive_name(directive) + if directive_name == 'undef' { + fields := c_directive_arg(directive).fields() + if fields.len == 0 { + return + } + macro_name := fields[0] + include_macros.delete(macro_name) + if ambiguous { + dynamic_include_macros[macro_name] = false + } else { + dynamic_include_macros.delete(macro_name) + } + return + } + if directive_name != 'define' { + return + } + definition := c_directive_arg(directive) + parts := definition.fields() + if parts.len == 0 || parts[0].contains('(') { + return + } + name := parts[0] + if ambiguous { + include_macros.delete(name) + dynamic_include_macros[name] = false + return + } + value := definition[name.len..].trim_space() + c_record_include_macro_value(name, value, mut include_macros, mut dynamic_include_macros) +} + +fn c_record_include_macro_value(name string, value string, mut include_macros map[string][]string, mut dynamic_include_macros map[string]bool) { + if name.len == 0 { + return + } + if value.len == 0 { + dynamic_include_macros.delete(name) + include_macros[name] = []string{} + return + } + if !c_include_arg_is_literal(value) { + is_alias := !value[0].is_digit() && value.bytes().all(it.is_alnum() || it == `_`) + if is_alias && value in include_macros && value !in dynamic_include_macros + && include_macros[value].len > 0 { + dynamic_include_macros.delete(name) + include_macros[name] = include_macros[value].clone() + return + } + include_macros.delete(name) + dynamic_include_macros[name] = true + return + } + dynamic_include_macros.delete(name) + mut values := include_macros[name] + if value !in values { + values << value + include_macros[name] = values + } +} + +fn c_embed_external_input_path(a &flat.FlatAst, node flat.Node) ?string { + if node.kind != .struct_init || node.value != 'embed_file.EmbedFileData' { + return none + } + for i in 0 .. node.children_count { + field := a.child_node(&node, i) + if field.kind != .field_init || field.value != 'apath' || field.children_count == 0 { + continue + } + value := a.child_node(field, 0) + if value.kind == .string_literal && value.value.len > 0 && os.is_file(value.value) { + return os.real_path(value.value) + } + } + return none +} + +// set_scope_parallel_workers makes cgen helpers use disposable prealloc +// arenas. The caller must release them with free_parallel_worker_scopes after +// consuming the generated C output and cgen metadata. +pub fn (mut g FlatGen) set_scope_parallel_workers(enabled bool) { + g.scope_parallel_workers = enabled +} + +// free_parallel_worker_scopes releases scratch arenas retained by joined cgen +// helper threads. +pub fn (mut g FlatGen) free_parallel_worker_scopes() { + $if prealloc { + for scope in g.parallel_worker_scopes { + if scope != unsafe { nil } { + unsafe { prealloc_scope_free_after(scope) } + } + } + } + g.parallel_worker_scopes = []voidptr{} +} + +// gen supports gen handling for FlatGen. +pub fn (mut g FlatGen) gen(a &flat.FlatAst) string { + tc := types.TypeChecker.new(a) + return g.gen_with_used(a, map[string]bool{}, &tc) +} + +// gen_with_used emits with used output for c. +pub fn (mut g FlatGen) gen_with_used(a &flat.FlatAst, used_fns map[string]bool, tc &types.TypeChecker) string { + return g.gen_with_used_options(a, used_fns, tc, false) +} + +pub fn (mut g FlatGen) gen_with_used_test_options(a &flat.FlatAst, used_fns map[string]bool, tc &types.TypeChecker, no_parallel bool, test_files []string) string { + g.test_files = map[string]bool{} + for file in test_files { + g.test_files[file] = true + g.test_files[os.real_path(file)] = true + } + return g.gen_with_used_options(a, used_fns, tc, no_parallel) +} + +// gen_to_file_with_used_test_options writes the completed translation unit by transferring the +// builder buffer to the file writer, avoiding a second full-size string allocation. +pub fn (mut g FlatGen) gen_to_file_with_used_test_options(path string, a &flat.FlatAst, used_fns map[string]bool, tc &types.TypeChecker, no_parallel bool, test_files []string) ! { + g.output_path = path + g.output_error = '' + _ = g.gen_with_used_test_options(a, used_fns, tc, no_parallel, test_files) + g.output_path = '' + if g.output_error.len > 0 { + return error(g.output_error) + } +} + +fn (mut g FlatGen) write_scoped_output_chunk(path string, append bool, scope voidptr) bool { + mut output := unsafe { g.sb.reuse_as_plain_u8_array() } + if append { + mut file := os.open_append(path) or { + g.output_error = err.msg() + unsafe { output.free() } + cgen_worker_scope_free(scope) + return false + } + unsafe { + file.write_full_buffer(output.data, usize(output.len)) or { + g.output_error = err.msg() + file.close() + output.free() + cgen_worker_scope_free(scope) + return false + } + } + file.close() + } else { + os.write_file_array(path, output) or { + g.output_error = err.msg() + unsafe { output.free() } + cgen_worker_scope_free(scope) + return false + } + } + unsafe { output.free() } + cgen_worker_scope_free(scope) + return true +} + +fn (mut g FlatGen) start_scoped_output_builder(cap int) voidptr { + scope := cgen_worker_scope_begin(true) + g.sb = strings.new_builder(cap) + cgen_worker_scope_leave(scope) + return scope +} + +fn (mut g FlatGen) flush_and_restart_scoped_output(path string, append bool, scope voidptr, cap int) !voidptr { + if !g.write_scoped_output_chunk(path, append, scope) { + g.sb = strings.new_builder(4096) + return error(g.output_error) + } + return g.start_scoped_output_builder(cap) +} + +fn (mut g FlatGen) release_scoped_fn_items() { + if g.object_file_mode { + // Export wrappers are emitted after the object-local linkage pragma is + // popped, so retain their function metadata until final output. + return + } + if g.scoped_fn_items_scope == unsafe { nil } { + return + } + scope := g.scoped_fn_items_scope + g.fn_gen_items = []FlatFnGenItem{} + g.emitted_fns = map[string]bool{} + g.tc.cur_file = '' + g.tc.cur_module = '' + g.scoped_fn_items_scope = unsafe { nil } + cgen_worker_scope_free(scope) +} + +fn (mut g FlatGen) write_scoped_function_output(path string, fn_code string) bool { + mut initial_file := os.create(path) or { + g.output_error = err.msg() + return false + } + initial_file.close() + for chunk_path in g.scoped_fn_output_paths { + if !g.append_scoped_output_file(path, chunk_path) { + return false + } + os.rm(chunk_path) or {} + } + g.scoped_fn_output_path = '' + g.scoped_fn_output_paths = []string{} + mut file := os.open_append(path) or { + g.output_error = err.msg() + return false + } + if g.fn_segs.len > 0 { + for segment in g.fn_segs { + file.write_string(segment) or { + g.output_error = err.msg() + file.close() + return false + } + unsafe { segment.free() } + } + g.fn_segs = []string{} + } + if fn_code.len > 0 { + file.write_string(fn_code) or { + g.output_error = err.msg() + file.close() + return false + } + unsafe { fn_code.free() } + } + file.close() + return true +} + +fn (mut g FlatGen) append_function_output(path string, fn_code string) bool { + mut file := os.open_append(path) or { + g.output_error = err.msg() + return false + } + for segment in g.fn_segs { + file.write_string(segment) or { + g.output_error = err.msg() + file.close() + return false + } + unsafe { segment.free() } + } + g.fn_segs = []string{} + if fn_code.len > 0 { + file.write_string(fn_code) or { + g.output_error = err.msg() + file.close() + return false + } + unsafe { fn_code.free() } + } + file.close() + return true +} + +fn (mut g FlatGen) append_scoped_output_file(path string, source_path string) bool { + mut source := os.open(source_path) or { + g.output_error = err.msg() + return false + } + mut output := os.open_append(path) or { + g.output_error = err.msg() + source.close() + return false + } + mut buffer := []u8{len: 65_536} // 64 KiB + for { + n_read := source.read(mut buffer) or { + if err is os.Eof { + break + } + g.output_error = err.msg() + output.close() + source.close() + return false + } + if n_read == 0 { + break + } + unsafe { + output.write_full_buffer(buffer.data, usize(n_read)) or { + g.output_error = err.msg() + output.close() + source.close() + return false + } + } + } + output.close() + source.close() + return true +} + +fn (g &FlatGen) cleanup_scoped_output_files(stream_path string, fn_stream_path string) { + os.rm(stream_path) or {} + os.rm(fn_stream_path) or {} + for chunk_path in g.scoped_fn_output_paths { + os.rm(chunk_path) or {} + } +} + +// gen_with_used_options emits with used options output for c. +pub fn (mut g FlatGen) gen_with_used_options(a &flat.FlatAst, used_fns map[string]bool, tc &types.TypeChecker, no_parallel bool) string { + effective_no_parallel := no_parallel || g.profile_file.len > 0 + if g.profile_file.len > 0 { + // Counter metadata and numbering are accumulated by one serial generator. + g.scope_parallel_workers = false + } + g.a = a + // Mark-used is immutable during cgen. Sharing this potentially very large + // post-monomorph map matches the worker path and avoids a full-program clone + // at the cgen memory peak. + g.used_fns = &used_fns + g.used_fn_names = []string{} + g.fn_gen_items = []FlatFnGenItem{} + g.top_level_node_ids = []int{} + g.ast_string_literals = []string{} + g.ast_string_literals_ready = false + g.direct_array_access = false + g.unsafe_depth = 0 + g.fn_segs = []string{} + g.fn_seg_chunk_indexes = []int{} + g.parallel_chunk_wrapper_defs = []ParallelChunkWrapperDefs{} + g.parallel_chunk_wrapper_capture = -1 + g.parallel_type_decls = '' + g.parallel_global_decls = '' + g.parallel_forward_decls = '' + g.parallel_support_decls = '' + g.parallel_enum_str_defs = '' + g.parallel_interface_stubs = '' + g.parallel_init_defs = '' + g.parallel_const_code = '' + g.parallel_support_ready = false + g.coverage_files.clear() + g.coverage_counter_count = 0 + g.profile_counters = []ProfileCounterMeta{} + g.profile_fn_active = false + g.profile_fn_restore_enabled = false + g.str_lits = []string{} + g.str_lits_shared = false + g.defers = []flat.NodeId{} + g.scope_defer_starts = []int{} + g.emitted_loop_break_labels.clear() + g.fn_defers = []flat.NodeId{} + g.fn_defer_counts.clear() + g.defer_capture_names = []string{} + g.defer_capture_types.clear() + g.const_runtime_inits = []string{} + g.const_runtime_init_modules = []string{} + g.runtime_inits = []string{} + g.runtime_init_modules = []string{} + g.compiler_vroot = '' + g.str_lit_ids.clear() + g.global_types.clear() + g.global_raw_type_texts.clear() + g.enum_vals.clear() + g.enum_value_exprs.clear() + g.interfaces.clear() + g.const_vals.clear() + g.const_modules.clear() + g.const_files.clear() + g.const_init_order = []string{} + g.fixed_storage_consts.clear() + g.global_modules.clear() + g.global_files.clear() + g.global_inits.clear() + g.global_init_order = []string{} + g.enum_backing_infos.clear() + g.iface_impls.clear() + g.interface_dispatch_required.clear() + g.iface_type_ids.clear() + g.ierror_method_emit_names.clear() + g.ierror_stack_pointer_aliases = []map[string]bool{} + g.ierror_owned_pointer_by_owner.clear() + g.recursive_drop_helpers.clear() + g.local_pointer_storage_by_owner.clear() + g.local_c_type_by_owner.clear() + g.local_mutable_by_owner.clear() + g.local_pointer_alias_by_owner.clear() + g.local_pointer_alias_mut_param.clear() + g.local_raw_type_by_owner.clear() + g.local_shared_storage_by_owner.clear() + g.local_fn_value_c_name_by_owner.clear() + g.shadowed_global_locals.clear() + g.sum_name_lookup.clear() + g.module_init_fns = []string{} + g.module_init_fn_modules.clear() + g.module_cleanup_fns = []string{} + g.module_cleanup_fn_modules.clear() + g.module_imports.clear() + g.c_directives = []CDirective{} + g.preinclude_directives = []string{} + g.postinclude_directives = []string{} + g.early_c_source_directives.clear() + g.native_source_contexts.clear() + g.objective_cpp_source_requests = []ObjectiveCppSourceRequest{} + g.native_source_wrapper_index = 0 + g.inlined_c_structs.clear() + g.inlined_c_fns.clear() + g.inlined_c_declared_fns.clear() + g.inlined_c_static_fns.clear() + g.cache_omitted_c_fns.clear() + g.preserved_header_files_seen.clear() + g.inlined_c_typedef_names.clear() + g.c_flags = []string{} + g.use_system_stdint = false + g.libc_compat_fns.clear() + g.modules.clear() + g.fn_ptr_types.clear() + g.used_fn_ptr_types.clear() + g.multi_return_types = []types.Type{} + g.multi_return_type_names.clear() + g.multi_return_types_ready = false + g.decl_types_ready = false + g.optional_types_ready = false + g.fixed_array_ret_wrappers.clear() + g.emitted_fixed_array_typedefs.clear() + g.concrete_optional_abi_fns.clear() + g.fixed_array_typedefs_needed.clear() + g.fixed_array_typedefs_ready = false + g.fixed_array_map_key_types.clear() + g.fn_decl_param_types.clear() + g.fn_decl_variadic.clear() + g.fn_decl_variadic_short_counts.clear() + g.fn_decl_shared_params.clear() + g.fn_shared_params_resolved.clear() + g.has_shared_params = false + g.fn_decl_mut_receivers.clear() + g.fn_decl_ret_types.clear() + g.fn_decl_nodes_by_name.clear() + g.fn_decl_nodes_by_short.clear() + g.fn_decl_nodes_by_module_short.clear() + g.non_generic_fn_names_by_module.clear() + g.generic_fn_keys_by_short.clear() + g.generic_fn_keys_by_cname.clear() + g.generic_fn_key_ordinal.clear() + g.struct_decl_infos.clear() + g.struct_decl_short_infos.clear() + g.decl_attrs.clear() + g.c_decl_abi_names.clear() + g.c_extern_global_names.clear() + g.shared_type_names.clear() + g.shared_alias_pointer_shorts.clear() + g.needs_shared_runtime = false + g.cur_param_names = []string{} + g.cur_param_type_values = []types.Type{} + g.cur_param_types.clear() + g.cur_concrete_optional_params.clear() + g.cur_mut_params.clear() + g.cur_mut_pointer_params.clear() + g.cur_mut_param_owners.clear() + g.active_locks = []ActiveLock{} + g.loop_depth = 0 + g.conditional_branch_scopes = []&types.Scope{} + g.conditional_branch_depths = []int{} + g.conditional_branch_depth = 0 + g.loop_label_depths.clear() + g.loop_defer_starts = []int{} + g.loop_label_defer_starts.clear() + g.loop_control_copybacks = []LoopControlCopyback{} + g.map_loop_copyback_guards = []MapLoopCopybackGuard{} + g.goto_label_c_names.clear() + g.goto_label_count = 0 + g.goto_label_lock_scopes.clear() + g.pending_loop_label = '' + g.needed_optional_types.clear() + g.emitted_optional_types.clear() + g.emitted_fns.clear() + g.array_method_cache.clear() + g.param_types_cache.clear() + g.interface_receiver_cache = &StringLookupCache{} + g.normalize_call_cache = &StringLookupCache{} + g.flattened_generic_name_cache = &StringLookupCache{} + g.generic_struct_context_ct_cache = &StringLookupCache{} + g.struct_cname_cache = &StringLookupCache{} + g.unique_struct_ct_cache = &StringLookupCache{} + g.alias_method_cache = &StringLookupCache{} + g.import_alias_cache = &ContextStringLookupCache{} + g.enum_selector_cache = &ContextStringLookupCache{} + g.enum_method_cache = &ContextStringLookupCache{} + g.qualified_enum_method_cache = &ContextStringLookupCache{} + g.embedded_fields_by_type.clear() + g.param_types_by_short.clear() + g.generic_method_candidates.clear() + g.spawn_wrapper_names.clear() + g.spawn_wrapper_defs = []string{} + g.spawn_wrapper_defs_seen.clear() + g.callback_wrapper_names.clear() + g.callback_wrapper_defs = []string{} + g.callback_wrapper_defs_seen.clear() + g.parallel_used = false + g.c_name_cache = &CNameCache{} + g.emitted_fn_ptr_typedefs.clear() + g.c_extern_refs.clear() + g.c_extern_refs_ready = false + g.parallel_prepared = false + g.scoped_fn_items_scope = unsafe { nil } + g.scoped_fn_output_path = '' + g.scoped_fn_output_paths = []string{} + g.const_short_index = &ConstShortIndex{} + g.mut_recv_facts = &FnNameFactCache{} + g.local_global_shadow_facts = &ContextNameFactCache{} + g.local_global_suffix_names.clear() + g.local_global_suffix_names_ready = false + g.generic_app_cache = &GenericAppCache{} + g.want_parallel_prep = false + g.want_parallel_c_extern_prep = false + g.worker_scope = unsafe { nil } + g.parallel_worker_scopes = []voidptr{} + g.tc = unsafe { tc } + if g.tc.a == unsafe { nil } { + g.tc.collect(a) + } + mut cgsw := time.new_stopwatch() + g.tc.precompute_source_error_embed_index() + g.timing_profile(' [ttime] ci embed idx ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + if g.skip_generics { + // The declared-type tables are static for the whole generic-free cgen + // phase, so qualify_name is memoizable per (module, file) context. + // Worker forks receive their own instances (fork_for_parallel_codegen). + g.tc.qualify_name_cache = &types.QualifyNameCache{} + } + defer { + g.tc.qualify_name_cache = unsafe { nil } + } + g.has_builtins = g.tc.has_builtins + g.precompute_shared_alias_pointer_shorts() + g.collect_gen_info() + g.precompute_local_global_suffix_names() + g.preintern_json_encode_strings() + g.timing_profile(' [ttime] cg collect_info ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + cgsw.restart() + mut parallel_support_precomputed := false + if g.incremental_fn_names.len > 0 { + // Cached declarations already contain whole-program typedefs, wrappers, + // interface tables and shared-parameter metadata. A body-only update only + // needs indexes consulted while emitting the selected function. Rebuild + // interface IDs too: newly boxed values must match the cached dispatch tables. + g.precompute_embedded_fields() + g.precompute_param_type_index() + g.precompute_shared_param_index() + g.precompute_sum_name_lookup() + g.collect_interface_impls() + g.precompute_required_interface_dispatch_methods() + } else { + // Function-item selection can run during pre-dispatch preparation. Populate + // interface implementers first so that late-lowered dispatch targets are not + // pruned before their concrete method bodies are emitted. + mut parallel_iface_scan := false + mut iface_worker := &FlatGen{} + mut iface_threads := []thread voidptr{cap: 1} + $if !v3_no_parallel ? { + parallel_iface_scan = g.scope_parallel_workers && !effective_no_parallel + } + if parallel_iface_scan { + $if !v3_no_parallel ? { + iface_worker = g.new_parallel_worker(4) + iface_worker.interface_boxed_types = map[string]bool{} + iface_worker.interface_boxed_types_done = false + iface_worker.iface_impls = map[string][]string{} + iface_worker.iface_type_ids = map[string]int{} + iface_worker.ierror_method_emit_names = map[string]bool{} + iface_threads << spawn interface_impl_scan_thread(voidptr(iface_worker)) + } + } else { + g.collect_interface_impls() + g.precompute_required_interface_dispatch_methods() + g.timing_profile(' [ttime] cg iface impls ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + cgsw.restart() + } + // Struct field defaults are emitted from their declarations when an otherwise + // unrelated function initializes the struct. Parallel function pre-scanning only + // visits that function body, so seed literals from defaults before workers fork. + g.preseed_struct_default_string_literals() + g.precompute_shared_param_index() + if !g.skip_generics { + g.precompute_non_generic_fn_index() + g.precompute_generic_fn_key_index() + } + // In the parallel path the fixed-storage scan runs on a helper thread, + // overlapped with the fn-item collection and parallel pre-seeding. + // The master emits selectors/inits itself (serial regions, postamble), so + // its embedded-fields map must be populated even when the worker-fork prep + // runs its own copy; do it before any helper thread can observe `g`. + g.precompute_embedded_fields() + g.timing_profile(' [ttime] cg preseeds ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + cgsw.restart() + if parallel_iface_scan { + $if !v3_no_parallel ? { + _ = iface_threads[0].wait() + g.publish_interface_impl_scan(mut iface_worker) + g.precompute_required_interface_dispatch_methods() + g.timing_profile(' [ttime] cg iface wait ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms (overlapped)') + cgsw.restart() + } + } + parallel_prep_done := g.run_pre_dispatch_parallel(effective_no_parallel) + g.timing_profile(' [ttime] cg predispatch ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + cgsw.restart() + if !parallel_prep_done { + g.collect_fixed_storage_consts(false) + g.precompute_param_type_index() + g.precompute_concrete_optional_abi_fns() + if effective_no_parallel { + g.prepare_serial_fn_tables() + } + } + // The fixed-array return scan only reads completed declaration tables. Run + // it beside the independent shared/sum indexes on the parallel self-host + // path; both must finish before any function body is generated. + parallel_support_precomputed = + g.prepare_shared_sum_and_fixed_array_ret_wrappers(parallel_prep_done) + cgsw.restart() + // Seed declaration-owned function-pointer types before parallel type + // generation starts. The pre-dispatch item walk adds body-local types + // before the declaration task is launched. Declaration types repeat the + // same canonical values heavily; dedup whole traversals for this block. + g.preseed_sig_type_seen = &PreseedTypeSeen{} + g.preseed_struct_fn_ptr_types() + g.preseed_sum_fn_ptr_types() + g.preseed_global_fn_ptr_types() + g.timing_profile(' [ttime] wr struct/sum ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + cgsw.restart() + g.preseed_fn_signature_fn_ptr_types() + g.timing_profile(' [ttime] wr fn sigs ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + cgsw.restart() + g.preseed_c_extern_fn_ptr_types() + g.preseed_sig_type_seen = unsafe { nil } + g.timing_profile(' [ttime] cg wrappers ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms (sig+extern)') + cgsw.restart() + } + if !parallel_support_precomputed { + g.precompute_ownership_recursive_drop_helpers() + g.precompute_fixed_array_map_key_types() + } + defer_parallel_support := g.scope_parallel_workers && !effective_no_parallel + && !g.program_body_only && g.incremental_fn_names.len == 0 + mut const_code := if g.program_body_only || defer_parallel_support { + '' + } else { + g.precompute_consts() + } + g.timing_profile(' [ttime] cg precompute ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + cgsw.restart() + orig_sb := g.sb + orig_line_start := g.line_start + g.sb = strings.new_builder(4096) + g.line_start = true + g.gen_fns_dispatch(effective_no_parallel) + g.writeln('// THE END.') + g.timing_profile(' [ttime] cg fns dispatch ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + cgsw.restart() + if defer_parallel_support { + if g.parallel_support_ready { + const_code = g.parallel_const_code + g.parallel_const_code = '' + } else { + const_code = g.precompute_consts() + } + } + // Function workers collect only the C symbols reached by emitted bodies. + // Finalize their declarations and function-pointer types after the merge. + g.c_extern_refs_ready = true + g.preseed_c_extern_fn_ptr_types() + g.preseed_libc_compat_fns() + fn_code := g.sb.str() + // `.str()` copies out of the builder; free the emptied backing array under -gc none. + unsafe { g.sb.free() } + g.sb = orig_sb + g.line_start = orig_line_start + g.timing_profile(' [ttime] cg fn_code copy ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms (len: ${fn_code.len})') + cgsw.restart() + if g.program_body_only { + unsafe { const_code.free() } + g.sb.ensure_cap(fn_code.len + 262_144) + g.writeln('#define V3CACHE_PROGRAM_UNIT 1') + g.string_literals() + if g.incremental_fn_names.len > 0 { + g.writeln('/* V3CACHE_SUPPORT_BEGIN */') + g.fixed_array_early_typedefs() + g.fn_ptr_typedefs() + g.struct_decls() + g.fixed_array_typedefs() + g.optional_typedefs() + g.forward_decls() + } + g.gen_ownership_recursive_drop_helpers() + if g.incremental_fn_names.len > 0 { + g.writeln('/* V3CACHE_SUPPORT_END */') + } + g.release_scoped_fn_items() + g.writeln('/* V3CACHE_BODY_BEGIN */') + g.writeln('/* V3CACHE_MODULE main */') + for segment in g.fn_segs { + g.sb.write_string(segment) + unsafe { segment.free() } + } + g.fn_segs = []string{} + if fn_code.len > 0 { + g.sb.write_string(fn_code) + unsafe { fn_code.free() } + } + g.writeln('/* V3CACHE_BODY_END */') + source := g.sb.str() + result := g.rewrite_cache_string_symbols(source) + unsafe { + source.free() + g.sb.free() + } + if g.output_path.len > 0 { + os.write_file(g.output_path, result) or { g.output_error = err.msg() } + unsafe { result.free() } + g.sb = strings.new_builder(4096) + return '' + } + return result + } + mut known_output_len := g.sb.len + fn_code.len + const_code.len + g.parallel_type_decls.len + + g.parallel_global_decls.len + g.parallel_support_decls.len + g.parallel_enum_str_defs.len + + g.parallel_interface_stubs.len + g.parallel_init_defs.len + for segment in g.fn_segs { + known_output_len += segment.len + } + // Leave headroom for the small body-dependent supplement emitted below. + g.sb.ensure_cap(known_output_len + 1_048_576) // 1 MiB + if g.parallel_type_decls.len == 0 { + g.gen_translation_unit_prefix() + } + g.write_type_declaration_block() + if g.cache_split { + g.writeln('/* V3CACHE_SOURCE_DIRECTIVES_BEGIN */') + } + g.emit_c_source_directives() + if g.cache_split { + g.writeln('/* V3CACHE_SOURCE_DIRECTIVES_END */') + } + g.c_extern_forward_decls() + if g.parallel_global_decls.len > 0 { + g.sb.write_string(g.parallel_global_decls) + unsafe { g.parallel_global_decls.free() } + g.parallel_global_decls = '' + } else { + g.gen_global_declaration_block() + } + if g.parallel_forward_decls.len > 0 { + g.sb.write_string(g.parallel_forward_decls) + unsafe { g.parallel_forward_decls.free() } + g.parallel_forward_decls = '' + } else { + g.forward_decls() + } + if g.parallel_support_decls.len > 0 { + g.sb.write_string(g.parallel_support_decls) + unsafe { g.parallel_support_decls.free() } + g.parallel_support_decls = '' + } else { + g.gen_pre_body_support_declarations() + } + g.release_scoped_fn_items() + g.callback_wrapper_decls() + g.spawn_wrapper_decls() + g.register_interface_strings() + g.string_literals() + if !g.cache_split { + if g.parallel_interface_stubs.len > 0 { + g.sb.write_string(g.parallel_interface_stubs) + unsafe { g.parallel_interface_stubs.free() } + g.parallel_interface_stubs = '' + } else { + g.interface_method_stubs() + } + } + if !g.skip_enum_autostr { + if g.parallel_enum_str_defs.len > 0 { + g.sb.write_string(g.parallel_enum_str_defs) + unsafe { g.parallel_enum_str_defs.free() } + g.parallel_enum_str_defs = '' + } else { + g.enum_str_defs() + } + } + g.sb.write_string(const_code) + // The final builder now owns a copy of the const code. + unsafe { const_code.free() } + if g.cache_split { + g.writeln('/* V3CACHE_BODY_BEGIN */') + // `_vinit` and interface stubs depend on the complete entry program, but + // remain stable across function-body literal edits. Keep them with the + // program specialization cache instead of regenerating module globals in + // every edited translation unit. + g.writeln('/* V3CACHE_MODULE __v3_program_support */') + } + if g.parallel_init_defs.len > 0 { + g.sb.write_string(g.parallel_init_defs) + unsafe { g.parallel_init_defs.free() } + g.parallel_init_defs = '' + } else { + g.gen_vinit() + g.gen_vcleanup() + } + if g.cache_split { + g.interface_method_stubs() + } + g.timing_profile(' [ttime] cg postamble ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms (sb: ${g.sb.len})') + cgsw.restart() + if !g.cache_split && !g.object_file_mode && g.output_path.len > 0 + && g.postinclude_directives.len == 0 && (g.fn_segs.len > 0 || fn_code.len > 0) { + mut prefix := unsafe { g.sb.reuse_as_plain_u8_array() } + $if !windows { + if os.getenv('V3_NO_MMAP_CGEN_OUTPUT') == '' { + write_c_output_mapped(g.output_path, prefix, g.fn_segs, fn_code) or { + g.output_error = err.msg() + } + unsafe { prefix.free() } + for segment in g.fn_segs { + unsafe { segment.free() } + } + g.fn_segs = []string{} + if fn_code.len > 0 { + unsafe { fn_code.free() } + } + g.sb = strings.new_builder(4096) + g.timing_profile(' [ttime] cg write out ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + return '' + } + } + os.write_file_array(g.output_path, prefix) or { g.output_error = err.msg() } + unsafe { prefix.free() } + g.sb = strings.new_builder(4096) + if g.output_error.len == 0 { + g.append_function_output(g.output_path, fn_code) + } + g.timing_profile(' [ttime] cg write out ${f64(cgsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + return '' + } + if g.fn_segs.len > 0 { + for segment in g.fn_segs { + g.sb.write_string(segment) + unsafe { segment.free() } + } + g.fn_segs = []string{} + } + if fn_code.len > 0 { + g.sb.write_string(fn_code) + // The final builder now owns a copy of the function code. + unsafe { fn_code.free() } + } + g.emit_postinclude_directives() + if g.object_file_mode { + g.writeln('#if defined(__clang__)') + g.writeln('#pragma clang attribute pop') + g.writeln('#endif') + g.emit_object_file_export_wrappers() + } + if g.cache_split { + g.writeln('/* V3CACHE_BODY_END */') + source := g.sb.str() + result := g.rewrite_cache_string_symbols(source) + unsafe { + source.free() + g.sb.free() + } + if g.output_path.len > 0 { + os.write_file(g.output_path, result) or { g.output_error = err.msg() } + unsafe { result.free() } + g.sb = strings.new_builder(4096) + return '' + } + return result + } + if g.output_path.len > 0 { + mut output := unsafe { g.sb.reuse_as_plain_u8_array() } + os.write_file_array(g.output_path, output) or { g.output_error = err.msg() } + unsafe { output.free() } + g.sb = strings.new_builder(4096) + return '' + } + result := g.sb.str() + // Keep only the returned C string, not the builder's copied backing array. + unsafe { g.sb.free() } + return result +} + +fn (mut g FlatGen) gen_pre_body_support_declarations() { + g.fixed_array_map_key_forward_decls() + g.fixed_array_map_key_definitions() + g.gen_ownership_recursive_drop_helpers() + g.cached_header_forward_decls() + g.interface_method_forward_decls() + g.shared_dup_fns() + if !g.skip_enum_autostr { + g.enum_str_forward_decls() + } +} + +fn (mut g FlatGen) gen_translation_unit_prefix() { + g.c99_feature_test_macros() + if g.profile_file.len > 0 { + g.writeln('#define _VPROFILE (1)') + } + g.thread_stack_size_definition() + g.emit_preinclude_directives() + g.emit_preserved_c_directives() + g.preamble() + if g.cache_split { + g.writeln('/* V3CACHE_NATIVE_DIRECTIVES_BEGIN */') + } + g.emit_c_directives(false) + if g.cache_split { + g.writeln('/* V3CACHE_NATIVE_DIRECTIVES_END */') + } +} + +fn (mut g FlatGen) gen_global_declaration_block() { + if g.object_file_mode { + g.writeln('#if defined(__clang__)') + g.writeln('#pragma clang attribute push(__attribute__((internal_linkage)), apply_to = any(function, variable(is_global)))') + g.writeln('#endif') + } + g.builtin_abi_decls() + g.test_failure_helpers() + g.global_decls() + g.gen_profile_support() + g.emit_coverage_support() + // Objective-C implementation files commonly use complete V structs in their + // function signatures and bodies. Their framework imports are lifted above + // the headerless preamble, but the implementation itself belongs after the V + // type declarations. + if g.cache_split { + g.writeln('/* V3CACHE_LATE_DIRECTIVES_BEGIN */') + } + g.emit_c_directives(true) + if g.cache_split { + g.writeln('/* V3CACHE_LATE_DIRECTIVES_END */') + } +} + +// gen_type_declaration_block emits the declaration block whose inputs are +// immutable after cgen preparation. Parallel self-host cgen can build it on the +// caller while the persistent worker pool emits function bodies. +fn (mut g FlatGen) gen_type_declaration_block() { + g.enum_decls() + g.type_forward_decls() + g.type_alias_decls() + // Forward-declare multi-return structs before fn-ptr typedefs, which may name a + // multi-return as a by-value return type (full bodies come after struct_decls). + g.multi_return_forward_decls() + // Bare typedefs for primitive-element fixed arrays and wrapper structs for + // fixed-array return types, before fn-ptr typedefs (which may name a fixed + // array in param or return position) and the function declarations. + g.fixed_array_early_typedefs() + g.fn_ptr_typedefs() + g.struct_decls() + g.fixed_array_typedefs() + g.multi_return_typedefs() + g.optional_typedefs() + g.gen_ownership_recursive_drop_helper_forward_decls() +} + +// write_type_declaration_block writes the precomputed parallel block when available, +// then supplements it with function-pointer types discovered by body workers. +fn (mut g FlatGen) write_type_declaration_block() { + if g.parallel_type_decls.len == 0 { + g.gen_type_declaration_block() + return + } + g.sb.write_string(g.parallel_type_decls) + unsafe { g.parallel_type_decls.free() } + g.parallel_type_decls = '' + // The parallel declaration task finishes before body-worker state is merged. + // fn_ptr_typedefs deduplicates the precomputed set and emits only late types. + g.fn_ptr_typedefs() +} + +fn (mut g FlatGen) gen_vinit() { + if g.const_runtime_inits.len == 0 && g.runtime_inits.len == 0 && g.module_init_fns.len == 0 + && g.global_inits.len == 0 { + return + } + fn_start_pos := g.sb.len + g.writeln('void _vinit() {') + mut emitted_const := []bool{len: g.const_runtime_inits.len} + mut emitted_runtime := []bool{len: g.runtime_inits.len} + g.emit_const_referenced_global_defaults(mut emitted_runtime) + init_fns := g.module_init_fn_map() + for mod in g.ordered_startup_modules(init_fns) { + g.emit_runtime_inits_for_module(mod, mut emitted_const, mut emitted_runtime) + if init_fn := init_fns[mod] { + g.writeln('\t${init_fn}();') + } + } + g.emit_remaining_runtime_inits(mut emitted_const, mut emitted_runtime) + g.writeln('}') + g.writeln('') + if '_vinit' in g.print_fn_names { + println(g.sb.after(fn_start_pos)) + } +} + +fn (mut g FlatGen) gen_vcleanup() { + if !g.is_shared && g.module_cleanup_fns.len == 0 { + return + } + fn_start_pos := g.sb.len + g.writeln('void _vcleanup(void) {') + g.writeln('\tstatic bool once = false;') + g.writeln('\tif (once) { return; }') + g.writeln('\tonce = true;') + cleanup_fns := g.ordered_module_cleanup_fns() + for i := cleanup_fns.len - 1; i >= 0; i-- { + g.writeln('\t${cleanup_fns[i]}();') + } + g.writeln('}') + g.writeln('') + if '_vcleanup' in g.print_fn_names { + println(g.sb.after(fn_start_pos)) + } +} + +// emit_const_referenced_global_defaults initializes implicit global struct +// defaults before a runtime constant that reads one of their fields. Explicit +// global initializers keep normal module ordering because they can themselves +// depend on runtime constants. +fn (mut g FlatGen) emit_const_referenced_global_defaults(mut emitted_runtime []bool) { + for qname in g.global_init_order { + if qname in g.global_inits { + continue + } + cname := g.global_c_name(qname) + mut is_referenced := false + for init in g.const_runtime_inits { + if init.contains('${cname}.') || init.contains('${cname}[') + || init.contains('&${cname}') || init.contains('(${cname}') { + is_referenced = true + break + } + } + if !is_referenced { + continue + } + for i, init in g.runtime_inits { + if emitted_runtime[i] || !runtime_init_targets_global(init, cname) { + continue + } + g.writeln(init) + emitted_runtime[i] = true + } + } +} + +fn runtime_init_targets_global(init string, cname string) bool { + clean := init.trim_space() + return clean.starts_with('${cname} =') || clean.starts_with('${cname}.') + || clean.starts_with('${cname}[') || clean.starts_with('memmove(${cname}') +} + +fn (mut g FlatGen) rewrite_cache_string_symbols(source string) string { + mut symbols := []string{cap: g.str_lits.len} + for value in g.str_lits { + symbols << cache_string_symbol(value) + } + user_c_symbols := g.cache_user_c_string_symbols() + mut out := strings.new_builder(source.len + g.str_lits.len * 8) + mut i := 0 + for i < source.len { + if source[i] in [`"`, `'`] { + quote := source[i] + start := i + i++ + for i < source.len { + if source[i] == `\\` && i + 1 < source.len { + i += 2 + continue + } + i++ + if source[i - 1] == quote { + break + } + } + out.write_string(source[start..i]) + continue + } + if i + 1 < source.len && source[i] == `/` && source[i + 1] == `/` { + start := i + i += 2 + for i < source.len && source[i] != `\n` { + i++ + } + out.write_string(source[start..i]) + continue + } + if i + 1 < source.len && source[i] == `/` && source[i + 1] == `*` { + start := i + i += 2 + for i + 1 < source.len && !(source[i] == `*` && source[i + 1] == `/`) { + i++ + } + if i + 1 < source.len { + i += 2 + } else { + i = source.len + } + out.write_string(source[start..i]) + continue + } + if c_identifier_start(source[i]) { + start := i + i++ + for i < source.len && c_identifier_continue(source[i]) { + i++ + } + identifier := source[start..i] + if cache_numbered_string_symbol(identifier) && !user_c_symbols[identifier] { + mut id := 0 + for digit in identifier[5..].bytes() { + id = id * 10 + int(digit - `0`) + } + if id >= 0 && id < symbols.len { + out.write_string(symbols[id]) + continue + } + } + out.write_string(identifier) + continue + } + out.write_u8(source[i]) + i++ + } + return out.str() +} + +fn (mut g FlatGen) cache_user_c_string_symbols() map[string]bool { + mut symbols := map[string]bool{} + for directive in g.c_directives { + collect_cache_numbered_string_symbols(directive.text, mut symbols) + } + for name in g.inlined_c_fns.keys() { + collect_cache_numbered_string_symbols(name, mut symbols) + } + for name in g.inlined_c_declared_fns.keys() { + collect_cache_numbered_string_symbols(name, mut symbols) + } + referenced_symbols := g.c_extern_referenced_symbols() + for name in referenced_symbols.keys() { + collect_cache_numbered_string_symbols(name, mut symbols) + } + return symbols +} + +fn collect_cache_numbered_string_symbols(source string, mut symbols map[string]bool) { + mut i := 0 + for i < source.len { + if source[i] in [`\"`, `'`] { + quote := source[i] + i++ + for i < source.len { + if source[i] == `\\` && i + 1 < source.len { + i += 2 + continue + } + i++ + if source[i - 1] == quote { + break + } + } + continue + } + if i + 1 < source.len && source[i] == `/` && source[i + 1] == `/` { + i += 2 + for i < source.len && source[i] != `\n` { + i++ + } + continue + } + if i + 1 < source.len && source[i] == `/` && source[i + 1] == `*` { + i += 2 + for i + 1 < source.len && !(source[i] == `*` && source[i + 1] == `/`) { + i++ + } + if i + 1 < source.len { + i += 2 + } else { + i = source.len + } + continue + } + if !c_identifier_start(source[i]) { + i++ + continue + } + start := i + i++ + for i < source.len && c_identifier_continue(source[i]) { + i++ + } + identifier := source[start..i] + if cache_numbered_string_symbol(identifier) { + symbols[identifier] = true + } + } +} + +fn cache_numbered_string_symbol(identifier string) bool { + return identifier.len > 5 && identifier.starts_with('_str_') + && identifier[5..].bytes().all(it >= `0` && it <= `9`) +} + +fn c_identifier_start(c u8) bool { + return (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || c == `_` +} + +fn c_identifier_continue(c u8) bool { + return c_identifier_start(c) || (c >= `0` && c <= `9`) +} + +fn cache_string_symbol(value string) string { + mut hash := u64(1469598103934665603) + for c in value.bytes() { + hash = (hash ^ u64(c)) * u64(1099511628211) + } + return '_v3_lit_${value.len}_${hash.hex()}' +} + +// node_kind_id supports node kind id handling for c. +@[inline] +fn node_kind_id(node flat.Node) int { + return int(node.kind) +} + +// collect_gen_info updates collect gen info state for c. +// UnusedParamSeen tracks param type texts already preseeded for unused fn +// declarations within the current module (see preseed_unused_fn_ptr_param_types). +struct UnusedParamSeen { +mut: + module string + texts map[string]bool +} + +// CollectGenFnPrep carries the context-dependent, table-write-free portion of +// one function declaration's collect_gen_info work. Parallel workers compute +// these values; the master still replays all registrations in source order. +struct CollectGenFnPrep { +mut: + prepared bool + ptypes []types.Type + shared_params []bool + fn_ptr_ctypes []string + return_type types.Type = types.Type(types.void_) + decl_is_variadic bool + first_param_is_mut bool +} + +struct FnSignatureRegistration { + module_key string + short_name string + aliases [6]string + alias_count u8 + ptypes []types.Type + shared_params []bool + is_variadic bool + is_mut bool + return_type types.Type +} + +fn (g &FlatGen) new_collect_gen_info_view() FlatGen { + mut view := *g + view.tc = g.clone_parallel_type_checker() + view.c_name_cache = &CNameCache{} + view.param_types_cache = map[string][]types.Type{} + view.interface_receiver_cache = &StringLookupCache{} + view.normalize_call_cache = &StringLookupCache{} + view.flattened_generic_name_cache = &StringLookupCache{} + view.generic_struct_context_ct_cache = &StringLookupCache{} + view.struct_cname_cache = &StringLookupCache{} + view.unique_struct_ct_cache = &StringLookupCache{} + view.alias_method_cache = &StringLookupCache{} + view.import_alias_cache = &ContextStringLookupCache{} + view.enum_selector_cache = &ContextStringLookupCache{} + view.enum_method_cache = &ContextStringLookupCache{} + view.qualified_enum_method_cache = &ContextStringLookupCache{} + view.mut_recv_facts = &FnNameFactCache{} + view.local_global_shadow_facts = &ContextNameFactCache{} + view.generic_app_cache = &GenericAppCache{} + return view +} + +fn (mut g FlatGen) compute_collect_gen_fn_prep(node flat.Node, module_name string, file string) CollectGenFnPrep { + g.tc.cur_file = file + g.tc.cur_module = module_name + typed_params := g.fn_node_param_types(node, module_name) + param_cap := if node.children_count < 64 { int(node.children_count) } else { 64 } + mut ptypes := []types.Type{cap: param_cap} + mut shared_params := []bool{} + mut fn_ptr_ctypes := []string{} + mut decl_is_variadic := false + mut first_param_is_mut := false + mut seen_param := false + mut param_idx := 0 + for i in 0 .. node.children_count { + child := g.a.child_node(&node, i) + if node_kind_id(child) != 75 { + if g.prefix_param_scan { + break + } + continue + } + if child.typ.starts_with('...') { + decl_is_variadic = true + } + raw_pt := if param_idx < typed_params.len { + typed_params[param_idx] + } else { + g.tc.parse_resolution_type(child.typ) + } + mut pt := raw_pt + if shared_alias_ptr := g.cached_shared_alias_pointer_type_from_text(child.typ) { + pt = shared_alias_ptr + } else if raw_pt is types.Pointer && param_idx < typed_params.len { + typed_pt := typed_params[param_idx] + if child.is_mut && child.op == .amp && typed_pt is types.Pointer + && typed_pt.base_type is types.Pointer { + pt = typed_pt + } else if typed_pt is types.Pointer && raw_pt.base_type is types.FnType + && typed_pt.base_type is types.FnType { + // Specialized `mut T` parameters keep a pointer-to-function type in + // the flat declaration. The registered signature retains the concrete + // module identity when same-named callback parameter types coexist. + pt = typed_pt + } + } else if raw_pt !is types.Pointer && param_idx < typed_params.len { + pt = typed_params[param_idx] + } + if child.is_mut && child.op == .amp { + pt = g.explicit_mut_pointer_param_type(child, pt) + } + mut is_shared_param := false + if child.typ.len > 0 && child.typ[0] in [`s`, ` `, `\t`, `\n`, `\r`] { + if _ := shared_inner_type_text(child.typ) { + is_shared_param = true + } + } + if is_shared_param { + for shared_params.len <= param_idx { + shared_params << false + } + shared_params[param_idx] = true + } else if shared_params.len > 0 { + shared_params << false + } + param_idx++ + if !seen_param { + first_param_is_mut = child.is_mut || raw_pt is types.Pointer || pt is types.Pointer + || child.typ.starts_with('&') || child.typ.starts_with('mut ') + seen_param = true + } + ptypes << pt + if pt is types.FnType { + fn_ptr_ctypes << g.tc.c_type(pt) + } + } + ptypes = g.fn_param_types_with_implicit_veb_ctx(node, ptypes) + if shared_params.len > 0 { + shared_params = g.fn_shared_params_with_implicit_veb_ctx(node, shared_params) + } + return_type := g.fn_node_return_type(node, module_name) + return CollectGenFnPrep{ + prepared: true + ptypes: ptypes + shared_params: shared_params + fn_ptr_ctypes: fn_ptr_ctypes + return_type: return_type + decl_is_variadic: decl_is_variadic + first_param_is_mut: first_param_is_mut + } +} + +@[direct_array_access] +fn (mut g FlatGen) collect_gen_info() { + profile := !isnil(g.tc) && g.tc.verbose + mut presw := time.new_stopwatch() + g.unused_param_seen = &UnusedParamSeen{} + g.reserve_collect_gen_info_maps() + if profile { + g.timing_profile(' [ttime] ci reserve maps ${f64(presw.elapsed().microseconds()) / 1000.0:7.2f} ms') + presw.restart() + } + if g.incremental_fn_names.len == 0 { + g.collect_c_flags_from_directives() + } + g.c_flags << g.initial_c_flags + g.use_system_stdint = g.translation_unit_uses_inttypes() + if profile { + g.timing_profile(' [ttime] ci flags+stdint ${f64(presw.elapsed().microseconds()) / 1000.0:7.2f} ms') + } + cisw := time.new_stopwatch() + mut ci_fn_ns := u64(0) + mut ci_reg_ns := u64(0) + mut ci_ret_ns := u64(0) + mut ci_ptypes_ns := u64(0) + mut cur_module := 'main' + mut cur_file := '' + mut seen_import_in_file := false + mut nonshared_fn_short_names := []string{cap: 1024} + mut nonshared_fn_full_names := []string{cap: 1024} + mut nonshared_fn_file_ranks := []int{cap: 1024} + mut nonshared_fn_node_indexes := []int{cap: 1024} + mut canonical_shared_fn_short_names := map[string]bool{} + mut preferred_shared_fn_file_ranks := map[string]int{} + mut preferred_shared_fn_node_indexes := map[string]int{} + mut preferred_shared_fn_params := map[string][]bool{} + mut fn_signature_registrations := []FnSignatureRegistration{cap: 16_384} + defer_fn_signature_registrations := g.scope_parallel_workers && g.skip_generics + && g.incremental_fn_names.len == 0 && par_cgen_prep_enabled() + top_level_nodes := g.top_level_nodes() + fn_preps := g.collect_gen_info_fn_preps(top_level_nodes) + for top_level_pos, node_idx in top_level_nodes { + node := g.a.nodes[node_idx] + kind_id := node_kind_id(node) + if node.kind == .directive && node.value.starts_with('@attributes:') { + target_idx := node.value['@attributes:'.len..].int() + attrs := node.generic_params().clone() + g.decl_attrs[target_idx] = attrs + g.index_c_decl_attributes(target_idx, cur_module, attrs) + continue + } + if kind_id == 77 { + cur_file = node.value + g.note_compiler_source_file(node.value) + cur_module = 'main' + g.tc.cur_module = cur_module + g.tc.cur_file = cur_file + seen_import_in_file = false + continue + } + if kind_id == 73 { + cur_module = node.value + g.tc.cur_file = cur_file + g.tc.cur_module = cur_module + continue + } + if kind_id == 61 { + ci_t0 := if profile { time.sys_mono_now() } else { u64(0) } + full_name := qualify_name_in_module(cur_module, node.value) + if g.has_used_fn_filter() && !g.used_fn_contains_in_module(node.value, cur_module) { + if g.incremental_fn_names.len == 0 { + g.preseed_unused_fn_ptr_param_types(node, cur_module, cur_file) + } + if profile { + ci_fn_ns += time.sys_mono_now() - ci_t0 + } + continue + } + g.register_fn_decl_node(node.value, cur_module, flat.NodeId(node_idx)) + ci_p0 := if profile { time.sys_mono_now() } else { u64(0) } + prep := if top_level_pos < fn_preps.len && fn_preps[top_level_pos].prepared { + fn_preps[top_level_pos] + } else { + g.compute_collect_gen_fn_prep(node, cur_module, cur_file) + } + ptypes := prep.ptypes + shared_params := prep.shared_params + decl_is_variadic := prep.decl_is_variadic + first_param_is_mut := prep.first_param_is_mut + g.tc.cur_file = cur_file + g.tc.cur_module = cur_module + for ct in prep.fn_ptr_ctypes { + g.resolve_fn_ptr_type(ct) + } + if profile { + ci_ptypes_ns += time.sys_mono_now() - ci_p0 + } + if shared_params.len > 0 { + file_rank := c_backend_fn_file_rank(cur_file) + if full_name !in preferred_shared_fn_file_ranks + || file_rank > preferred_shared_fn_file_ranks[full_name] { + preferred_shared_fn_file_ranks[full_name] = file_rank + preferred_shared_fn_node_indexes[full_name] = node_idx + preferred_shared_fn_params[full_name] = shared_params.clone() + } + if cur_module.len == 0 || cur_module == 'main' || cur_module == 'builtin' { + canonical_shared_fn_short_names[node.value] = true + } + } else { + nonshared_fn_short_names << node.value + nonshared_fn_full_names << full_name + nonshared_fn_file_ranks << c_backend_fn_file_rank(cur_file) + nonshared_fn_node_indexes << node_idx + } + ci_r0 := if profile { time.sys_mono_now() } else { u64(0) } + return_type := prep.return_type + if profile { + ci_ret_ns += time.sys_mono_now() - ci_r0 + } + if defer_fn_signature_registrations { + fn_signature_registrations << g.prepare_fn_signature_registration(node.value, + full_name, ptypes, shared_params, decl_is_variadic, first_param_is_mut, + return_type) + } else { + g.register_fn_decl_signature_type(node.value, full_name, ptypes, shared_params, + decl_is_variadic, first_param_is_mut, return_type) + } + if profile { + ci_reg_ns += time.sys_mono_now() - ci_r0 + } + // Module-level `init()` functions run once at startup. Collect their C + // names so _vinit can invoke them (V semantics). + is_builtin_init := cur_module == 'builtin' && node.value == 'builtin_init' + if (node.value == 'init' || is_builtin_init) && ptypes.len == 0 + && (!g.has_used_fn_filter() || g.used_fn_contains_in_module(node.value, cur_module)) { + init_cname := g.qualified_fn_name_in_module_c(cur_module, node.value) + if init_cname !in g.module_init_fns { + g.module_init_fns << init_cname + } + g.module_init_fn_modules[init_cname] = cur_module + } + if node.value == 'cleanup' && ptypes.len == 0 + && (!g.has_used_fn_filter() || g.used_fn_contains_in_module(node.value, cur_module)) { + cleanup_cname := g.qualified_fn_name_in_module_c(cur_module, node.value) + if cleanup_cname !in g.module_cleanup_fns { + g.module_cleanup_fns << cleanup_cname + } + g.module_cleanup_fn_modules[cleanup_cname] = cur_module + } + if profile { + ci_fn_ns += time.sys_mono_now() - ci_t0 + } + continue + } + if g.incremental_fn_names.len > 0 && node.kind == .directive { + continue + } + if g.collect_c_directive(cur_module, node, cur_file, !seen_import_in_file) { + continue + } + if node.kind == .directive && node.value == 'flag' { + continue + } + if node.kind == .directive && node.value == 'pkgconfig' { + continue + } + if kind_id == 62 { + full_name := qualify_name_in_module(cur_module, node.value) + g.tc.cur_file = cur_file + g.tc.cur_module = cur_module + g.register_struct_decl_info_at(node_idx, node.value, full_name, cur_module, cur_file, + node) + continue + } + if kind_id == 64 { + g.tc.cur_file = cur_file + g.tc.cur_module = cur_module + for i in 0 .. node.children_count { + f := g.a.child_node(&node, i) + if f.value.starts_with('C.') { + if f.children_count > 0 { + mut ft := g.tc.parse_type(f.typ) + if ft is types.Void { + ft = g.tc.resolve_type(g.a.child(f, 0)) + } + g.global_types[f.value] = ft + g.global_raw_type_texts[f.value] = f.typ + g.global_modules[f.value] = cur_module + g.global_files[f.value] = cur_file + g.global_init_order << f.value + val_id := g.a.child(f, 0) + if int(val_id) >= 0 { + g.global_inits[f.value] = val_id + } + } + continue + } + mut ft := g.tc.parse_type(f.typ) + if ft is types.Void && f.children_count > 0 { + ft = g.tc.resolve_type(g.a.child(f, 0)) + } + qname := qualify_name_in_module(cur_module, f.value) + g.global_types[qname] = ft + g.global_raw_type_texts[qname] = f.typ + g.global_modules[f.value] = cur_module + g.global_modules[qname] = cur_module + g.global_files[qname] = cur_file + g.global_init_order << qname + if f.children_count > 0 { + val_id := g.a.child(f, 0) + if int(val_id) >= 0 { + g.global_inits[qname] = val_id + } + } + g.tc.file_scope.insert(f.value, ft) + if qname != f.value { + g.tc.file_scope.insert(qname, ft) + } + } + continue + } + if kind_id == 67 { + is_flag := enum_decl_is_flag(node) + mut val := 0 + enum_name := qualify_name_in_module(cur_module, node.value) + backing := enum_decl_backing_type(node) or { '' } + if backing.len > 0 { + g.register_enum_backing_info(enum_name, backing) + } + is_backed_enum := backing.len > 0 + mut field_exprs := map[string]flat.NodeId{} + for i in 0 .. node.children_count { + f := g.a.child_node(&node, i) + if f.children_count > 0 { + field_exprs[f.value] = g.a.child(f, 0) + } + } + mut field_values := map[string]i64{} + for i in 0 .. node.children_count { + f := g.a.child_node(&node, i) + if f.children_count > 0 { + mut resolving := map[string]bool{} + if enum_val := g.enum_field_expr_value_with_enum(g.a.child(f, 0), cur_module, + node.value, mut field_values, field_exprs, mut resolving) + { + val = int(enum_val) + } + } + key := '${enum_name}.${f.value}' + if is_backed_enum { + g.enum_value_exprs[key] = '${g.cname(enum_name)}__${g.cname(f.value)}' + val++ + } else if is_flag { + g.enum_vals[key] = 1 << val + field_values[f.value] = i64(val) + val++ + } else { + // Keep enum expressions symbolic so explicit values that come from C + // macros (and therefore cannot be folded by V) survive at use sites. + g.enum_value_exprs[key] = '${g.cname(enum_name)}__${g.cname(f.value)}' + g.enum_vals[key] = val + field_values[f.value] = val + val++ + } + } + continue + } + if kind_id == 70 { + iface_name := qualify_name_in_module(cur_module, node.value) + g.interfaces[iface_name] = g.tc.interface_abstract_method_names(iface_name) + continue + } + if kind_id == 65 { + for i in 0 .. node.children_count { + f := g.a.child_node(&node, i) + if node_kind_id(f) == 66 && f.children_count > 0 { + qname := g.const_storage_name(cur_module, f.value) + g.const_vals[qname] = g.a.child(f, 0) + g.const_modules[qname] = cur_module + g.const_files[qname] = cur_file + if (cur_module.len == 0 || cur_module == 'main' || cur_module == 'builtin') + && f.value !in g.const_vals { + g.const_vals[f.value] = g.a.child(f, 0) + g.const_modules[f.value] = cur_module + g.const_files[f.value] = cur_file + } + } + } + continue + } + if kind_id == 72 { + seen_import_in_file = true + alias := node.typ.clone() + mod_name := node.value.clone() + if alias.len > 0 && mod_name.len > 0 { + g.modules[alias] = mod_name + } + if cur_module.len > 0 && mod_name.len > 0 { + dep_module := mod_name + if cur_module !in g.module_imports { + g.module_imports[cur_module] = []string{} + } + if dep_module !in g.module_imports[cur_module] { + g.module_imports[cur_module] << dep_module + } + } + continue + } + } + if defer_fn_signature_registrations { + g.apply_fn_signature_registrations(fn_signature_registrations) + } + if g.has_shared_params { + for full_name, flags in preferred_shared_fn_params { + g.fn_decl_shared_params[full_name] = flags + g.fn_decl_shared_params[g.cname(full_name)] = flags + } + for i, name in nonshared_fn_short_names { + if name in g.fn_decl_shared_params { + full_name := nonshared_fn_full_names[i] + mut nonshared_is_preferred := full_name !in preferred_shared_fn_file_ranks + if !nonshared_is_preferred { + shared_rank := preferred_shared_fn_file_ranks[full_name] + nonshared_rank := nonshared_fn_file_ranks[i] + nonshared_is_preferred = nonshared_rank > shared_rank + || (nonshared_rank == shared_rank + && nonshared_fn_node_indexes[i] < preferred_shared_fn_node_indexes[full_name]) + } + if nonshared_is_preferred { + g.fn_decl_shared_params[full_name] = []bool{} + g.fn_decl_shared_params[g.cname(full_name)] = []bool{} + } + if !canonical_shared_fn_short_names[name] { + g.fn_decl_shared_params[name] = []bool{} + cname := g.cname(name) + if cname != name { + g.fn_decl_shared_params[cname] = []bool{} + } + } + } + } + } + g.modules['strings'] = 'strings' + g.materialize_objective_cpp_sources() + ccio_sw := time.new_stopwatch() + g.collect_const_init_order_from_files() + if profile { + ci_total_ms := f64(cisw.elapsed().microseconds()) / 1000.0 + ci_fn_ms := f64(ci_fn_ns) / 1e6 + ccio_ms := f64(ccio_sw.elapsed().microseconds()) / 1000.0 + ci_reg_ms := f64(ci_reg_ns) / 1e6 + ci_ret_ms := f64(ci_ret_ns) / 1e6 + ci_ptypes_ms := f64(ci_ptypes_ns) / 1e6 + g.timing_profile(' [ttime] ci fns ${ci_fn_ms:7.2f} ms of ${ci_total_ms:7.2f} ms (ptypes ${ci_ptypes_ms:.2f}, ret ${ci_ret_ms:.2f}, ret+reg ${ci_reg_ms:.2f}), const order ${ccio_ms:7.2f} ms') + } +} + +@[direct_array_access] +fn (mut g FlatGen) scan_collect_gen_info_serial() CollectGenInfoScanCounts { + mut counts := CollectGenInfoScanCounts{} + incremental := g.incremental_fn_names.len > 0 + g.ast_string_literals = []string{cap: 4096} + g.top_level_node_ids = []int{cap: 4096} + for node_idx, node in g.a.nodes { + if node.kind == .string_literal { + g.ast_string_literals << node.value + } + if node.kind in [.file, .module_decl, .fn_decl, .c_fn_decl, .struct_decl, .type_decl, + .global_decl, .const_decl, .enum_decl, .interface_decl, .import_decl, .directive] { + g.top_level_node_ids << node_idx + } + match node.kind { + .fn_decl { + if !incremental || g.incremental_fn_names[node.value] { + counts.fn_count++ + } + } + .struct_decl { + counts.struct_count++ + } + .global_decl { + counts.global_count += int(node.children_count) + } + .const_decl { + counts.const_count += int(node.children_count) + } + .enum_decl { + counts.enum_field_count += int(node.children_count) + } + .interface_decl { + counts.interface_count++ + } + .import_decl { + counts.import_count++ + } + else {} + } + } + return counts +} + +struct CollectGenInfoScanCounts { +mut: + fn_count int + struct_count int + global_count int + const_count int + enum_field_count int + interface_count int + import_count int +} + +@[direct_array_access] +fn (mut g FlatGen) reserve_collect_gen_info_maps() { + counts := g.scan_collect_gen_info() + mut fn_count := counts.fn_count + struct_count := counts.struct_count + global_count := counts.global_count + const_count := counts.const_count + enum_field_count := counts.enum_field_count + interface_count := counts.interface_count + import_count := counts.import_count + incremental := g.incremental_fn_names.len > 0 + g.ast_string_literals_ready = true + if incremental && fn_count < g.incremental_fn_names.len { + fn_count = g.incremental_fn_names.len + } + fn_alias_count := u32(fn_count * 7 + 1024) + fn_name_count := u32(fn_count * 2 + 1024) + g.fn_decl_param_types.reserve(fn_alias_count) + g.fn_decl_variadic.reserve(fn_name_count) + g.fn_decl_variadic_short_counts.reserve(u32(fn_count + 256)) + g.fn_decl_shared_params.reserve(fn_alias_count) + g.fn_decl_mut_receivers.reserve(fn_name_count) + g.fn_decl_ret_types.reserve(fn_alias_count) + g.fn_decl_nodes_by_name.reserve(fn_name_count) + g.fn_decl_nodes_by_short.reserve(u32(fn_count + 256)) + g.fn_decl_nodes_by_module_short.reserve(fn_name_count) + g.module_init_fn_modules.reserve(u32(fn_count / 8 + 64)) + g.module_cleanup_fn_modules.reserve(u32(fn_count / 8 + 64)) + g.struct_decl_infos.reserve(u32(struct_count * 2 + 256)) + g.struct_decl_short_infos.reserve(u32(struct_count + 256)) + g.global_types.reserve(u32(global_count * 2 + 64)) + g.global_raw_type_texts.reserve(u32(global_count * 2 + 64)) + g.global_modules.reserve(u32(global_count * 3 + 64)) + g.global_files.reserve(u32(global_count * 2 + 64)) + g.global_inits.reserve(u32(global_count * 2 + 64)) + g.const_vals.reserve(u32(const_count * 2 + 64)) + g.const_modules.reserve(u32(const_count * 2 + 64)) + g.const_files.reserve(u32(const_count * 2 + 64)) + g.enum_vals.reserve(u32(enum_field_count * 2 + 64)) + g.enum_value_exprs.reserve(u32(enum_field_count * 2 + 64)) + g.interfaces.reserve(u32(interface_count * 2 + 64)) + g.modules.reserve(u32(import_count * 2 + 64)) + g.module_imports.reserve(u32(import_count + 64)) + if !isnil(g.c_name_cache) { + mut cache := g.c_name_cache + cache.entries.reserve(fn_alias_count) + } +} + +fn (mut g FlatGen) cached_shared_alias_pointer_type_from_text(raw string) ?types.Type { + key := '\x00shared-alias-pointer\x00${g.tc.cur_file}\x00${g.tc.cur_module}\x00${raw}' + if cached := g.param_types_cache[key] { + if cached.len > 0 { + return cached[0] + } + return none + } + resolved := g.shared_alias_pointer_type_from_text(raw) or { + g.param_types_cache[key] = []types.Type{} + return none + } + g.param_types_cache[key] = [resolved] + return resolved +} + +fn (mut g FlatGen) preseed_unused_fn_ptr_param_types(node flat.Node, module_name string, file string) { + // Unused declarations repeat the same few param type texts; when every + // param text of this fn was already preseeded within this module, the + // whole typed-param resolution below is a no-op. + if !isnil(g.unused_param_seen) { + mut seen := g.unused_param_seen + if seen.module != module_name { + seen.module = module_name + seen.texts.clear() + } + mut all_seen := true + for i in 0 .. node.children_count { + child := g.a.child_node(&node, i) + if node_kind_id(child) != 75 { + if g.prefix_param_scan { + break + } + continue + } + if child.typ !in seen.texts { + all_seen = false + break + } + } + if all_seen { + return + } + } + g.tc.cur_module = module_name + g.tc.cur_file = file + typed_params := g.fn_node_param_types(node, module_name) + mut param_idx := 0 + for i in 0 .. node.children_count { + child := g.a.child_node(&node, i) + if node_kind_id(child) != 75 { + if g.prefix_param_scan { + break + } + continue + } + if !isnil(g.unused_param_seen) { + g.unused_param_seen.texts[child.typ] = true + } + raw_type := if param_idx < typed_params.len { + typed_params[param_idx] + } else { + g.parse_node_type(child) + } + param_idx++ + param_type := cgen_unalias_type(raw_type) + if param_type is types.FnType { + key := g.fn_ptr_type_key(param_type) + if file.ends_with('.vh') { + // Cached interfaces still emit their declaration prototypes even when + // mark-used discards the function body, so their callback typedefs are used. + g.resolve_fn_ptr_type(key) + } else { + // Keep the canonical name available for a later concrete use without + // emitting a typedef solely for a discarded source function. + g.register_fn_ptr_type(key) + } + } + } +} + +fn (mut g FlatGen) collect_c_flags_from_directives() { + mut cur_file := '' + mut seen_groups := map[string]bool{} + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + kind_id := node_kind_id(node) + if kind_id == 77 { + cur_file = node.value + g.note_compiler_source_file(node.value) + continue + } + if node.kind != .directive || node.typ.len == 0 { + continue + } + if node.value == 'flag' { + flags := c_flag_args_with_values(node.typ, g.compiler_vroot, cur_file, g.target, + g.compile_values) + key := flags.join('\x00') + if flags.len > 0 && key !in seen_groups { + seen_groups[key] = true + g.c_flags << flags + } + continue + } + if node.value == 'pkgconfig' { + flags := c_pkgconfig_flags(node.typ) + key := flags.join('\x00') + if flags.len > 0 && key !in seen_groups { + seen_groups[key] = true + g.c_flags << flags + } + } + } +} + +// cache_directive_flags resolves source C flags that affect early C cache keys. +pub fn cache_directive_flags(a &flat.FlatAst, vroot string, target pref.Target, compile_values map[string]string) []string { + mut result := []string{} + mut seen_groups := map[string]bool{} + mut cur_file := '' + for node in a.nodes { + if node.kind == .file { + cur_file = node.value + continue + } + if node.kind != .directive || node.typ.len == 0 { + continue + } + flags := if node.value == 'flag' { + c_flag_args_with_values(node.typ, vroot, cur_file, target, compile_values) + } else if node.value == 'pkgconfig' { + c_pkgconfig_flags(node.typ) + } else { + continue + } + key := flags.join('\x00') + if flags.len > 0 && key !in seen_groups { + seen_groups[key] = true + result << flags + } + } + return result +} + +fn (g &FlatGen) translation_unit_uses_inttypes() bool { + mut cur_file := '' + include_dirs := c_flag_include_dirs(g.c_flags) + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + if node_kind_id(node) == 77 { + cur_file = node.value + continue + } + if node.kind != .directive + || node.value !in ['include', 'insert', 'preinclude', 'postinclude'] + || node.typ.len == 0 { + continue + } + include_arg := c_include_arg_for_target(node.typ, g.compiler_vroot, cur_file, g.target) + if trimmed_space(include_arg) == '' { + return true + } + mut seen := map[string]bool{} + for path in c_include_file_paths(include_arg, g.compiler_vroot, cur_file, include_dirs) { + if c_inline_header_tree_uses_inttypes(path, g.compiler_vroot, include_dirs, mut seen) { + return true + } + } + } + return false +} + +fn (mut g FlatGen) collect_c_directive(module_name string, node flat.Node, source_file string, before_import bool) bool { + if node.kind != .directive { + return false + } + if node.value in ['preinclude', 'postinclude'] { + if node.typ.len == 0 { + return true + } + include_arg := c_include_arg_for_target(node.typ, g.compiler_vroot, source_file, g.target) + if include_arg.len == 0 { + return true + } + directive := '#include ${include_arg}' + if node.value == 'preinclude' { + if directive !in g.preinclude_directives { + g.preinclude_directives << directive + } + } else if directive !in g.postinclude_directives { + g.postinclude_directives << directive + } + return true + } + if node.value in ['include', 'insert'] { + if node.typ.len == 0 { + return true + } + include_arg := c_include_arg_for_target(node.typ, g.compiler_vroot, source_file, g.target) + if include_arg.len == 0 { + return true + } + // These helper headers are superseded by the inline compiler helpers emitted in + // builtin_abi_decls(); also including them would redefine the helpers. + if c_include_arg_is_builtin_abi_helper(include_arg, g.compiler_vroot) { + return true + } + include_dirs := c_flag_include_dirs(g.c_flags) + // `#insert` is an explicit request to inline the source text. Delay only + // ordinary source includes until after generated type declarations. + if node.value == 'include' && c_include_arg_is_source_file(include_arg) { + paths := c_include_file_paths(include_arg, g.compiler_vroot, source_file, include_dirs) + mut source_path := '' + mut source_text := '' + for path in paths { + if text := os.read_file(path) { + source_path = os.real_path(path) + source_text = text + break + } + } + if source_path.len > 0 { + // Generated V output is not C++ compatible, so Objective-C++ sources use + // separate native-language wrappers. Keep active Objective-C includes in the + // main unit for internal-linkage helpers; definitely inactive ones can be + // omitted without forcing the generated unit into Objective-C mode. + if source_path.ends_with('.mm') { + g.objective_cpp_source_requests << ObjectiveCppSourceRequest{ + module: module_name + source_path: source_path + source_macros_possible: g.native_source_context_has_macro_inputs(module_name) + local_context: (g.native_source_contexts[module_name] or { + []NativeSourceContextDirective{} + }).clone() + } + return true + } + if source_path.ends_with('.m') { + local_context := g.native_source_contexts[module_name] or { + []NativeSourceContextDirective{} + } + context_directives := g.ordered_native_source_context(module_name, + local_context) + if context_directives.len > 0 + && c_native_source_context_definitely_inactive(context_directives, g.c_flags, g.c99_mode, g.target, g.native_source_context_has_macro_inputs(module_name)) { + return true + } + } + g.collect_inlined_c_structs(source_text) + g.collect_inlined_c_fns_for_cache(source_text, true, false) + g.collect_inlined_c_declared_fns(source_text) + source_directive := c_native_source_context_include(source_path) + if source_path.ends_with('.m') { + if g.c_source_defines_used_c_type(source_text) { + g.early_c_source_directives[source_directive] = true + } + if 'objective-c' !in g.c_flags { + g.c_flags << ['-x', 'objective-c', '-x', 'none'] + } + } + g.add_c_directive(module_name, source_directive, before_import) + } else { + g.add_c_directive(module_name, '#include ${include_arg}', before_import) + } + return true + } + if !c_include_arg_is_source_file(include_arg) { + g.add_native_source_context_directive(module_name, c_native_source_context_header_include(include_arg, + g.compiler_vroot, source_file, include_dirs), before_import) + } + if trimmed_space(include_arg) == '' { + g.collect_preserved_c_fns(c_preserved_system_include_declared_fns(include_arg)) + g.collect_preserved_c_structs(c_preserved_system_include_struct_names(include_arg)) + g.add_c_directive(module_name, '#include ${include_arg}', before_import) + return true + } + // Resolved angle headers already have a compiler search path. Preserve the + // include and scan their tree for declaration metadata without recursively + // materializing every header body into the generated translation unit. + if trimmed_space(include_arg).starts_with('<') + && g.collect_preserved_header_tree(include_arg, source_file, include_dirs) { + g.add_c_directive(module_name, '#include ${include_arg}', before_import) + return true + } + if header := c_inline_header_text(include_arg, g.compiler_vroot, source_file, include_dirs, + g.use_system_stdint) + { + header_text := header.text + late_source := c_include_is_late_source(include_arg) + mut scoped_native_header_path := '' + if g.cache_split { + for path in c_include_file_paths(include_arg, g.compiler_vroot, source_file, + include_dirs) { + real_path := os.real_path(path) + if g.cache_native_input_paths[real_path] { + scoped_native_header_path = real_path + break + } + } + } + if c_header_text_needs_objective_c_for_target(header_text, g.c_flags, g.c99_mode, g.target) + && 'objective-c' !in g.c_flags { + g.c_flags << ['-x', 'objective-c', '-x', 'none'] + } + g.collect_inlined_c_structs(header_text) + g.collect_inlined_c_fns_for_cache(header_text, false, true) + g.collect_inlined_c_declared_fns(header_text) + g.collect_preserved_c_fns(header.preserved_c_fns) + g.collect_preserved_c_structs(header.preserved_c_structs) + for preserved_header in header.preserved_headers { + g.collect_preserved_header_tree(preserved_header.include_arg, + preserved_header.source_file, include_dirs) + } + for directive in header.preserved_directives { + g.add_c_directive(module_name, directive, before_import) + } + if header_text.len > 0 { + system_includes := if late_source { + c_late_source_system_includes(header_text) + } else { + c_header_objective_c_framework_imports(header_text) + } + if system_includes.len > 0 { + // An inserted header can use Objective-C types supplied by a later + // module's header. Lift Apple framework imports before every inlined + // body while preserving guarded non-Apple includes in place. + g.add_c_directive(module_name, system_includes, before_import) + } + } + if header_text.len > 0 && scoped_native_header_path.len == 0 { + g.add_c_directive_at(module_name, header_text, before_import, late_source) + } else if scoped_native_header_path.len > 0 { + g.add_c_directive(module_name, + c_native_source_context_include(scoped_native_header_path), before_import) + } + } else if c_should_preserve_uninlined_include(include_arg) || (g.cache_split + && include_arg in ['', '', '']) { + g.collect_preserved_c_fns(c_preserved_system_include_declared_fns(include_arg)) + g.collect_preserved_c_structs(c_preserved_system_include_struct_names(include_arg)) + g.add_c_directive(module_name, '#include ${include_arg}', before_import) + } + return true + } + if node.value in ['define', 'undef', 'ifdef', 'ifndef', 'if', 'elif', 'else', 'endif', 'pragma', + 'error', 'warning'] { + directive := c_preprocessor_directive_line(node.value, node.typ) + g.add_native_source_context_directive(module_name, directive, before_import) + g.add_c_directive(module_name, directive, before_import) + return true + } + return false +} + +// c_builtin_abi_helper_header_paths are the superseded helper headers whose +// declarations builtin_abi_decls() already emits inline; re-including them would +// redefine those helpers. Each is its VROOT-relative path anchored at a `/` +// boundary, so an unrelated user header that merely shares a basename (its own +// `filelock_helpers.h`, or a `my_stdatomic_wrapper.h`) is never dropped. +const c_builtin_abi_helper_header_paths = [ + '/vlib/builtin/prealloc_atomics.h', + '/vlib/os/filelock/filelock_helpers.h', + '/vlib/sync/stdatomic/stdatomic_include_after_compat.h', + '/vlib/sync/stdatomic/tcc_compat_aliases.h', + '/vlib/sync/stdatomic/tcc_compat_cleanup.h', + '/vlib/sync/stdatomic/tcc_compat_freebsd_amd64_fence.h', + '/vlib/sync/stdatomic/tcc_compat_freebsd_amd64_fence_pre.h', + '/vlib/sync/stdatomic/tcc_compat_linux_fence.h', + '/vlib/sync/stdatomic/tcc_compat_restore.h', + '/thirdparty/stdatomic/nix/atomic.h', + '/thirdparty/stdatomic/nix/atomic_cpp.h', + '/thirdparty/stdatomic/win/atomic.h', +] + +// c_include_arg_is_builtin_abi_helper matches only the superseded helper headers, +// resolved against the active compiler VROOT. A bare suffix test would also drop an +// unrelated absolute user header that merely ends in a repository-shaped suffix +// (e.g. `/tmp/vlib/os/filelock/filelock_helpers.h`), silently losing its +// declarations from the translation unit; anchoring at `vroot` suppresses only the +// helper actually shipped under the running V installation. +fn c_include_arg_is_builtin_abi_helper(include_arg string, vroot string) bool { + clean := trimmed_space(include_arg) + if clean.len < 2 { + return false + } + path := if (clean[0] == `"` && clean[clean.len - 1] == `"`) + || (clean[0] == `<` && clean[clean.len - 1] == `>`) { + clean[1..clean.len - 1] + } else { + clean + } + normalized := path.replace('\\', '/') + root := vroot.replace('\\', '/').trim_right('/') + for suffix in c_builtin_abi_helper_header_paths { + // The helper's `#insert "@VEXEROOT/..."` resolves to `vroot` + suffix, so an + // exact anchored compare keeps a same-suffix header outside VROOT included. + if root.len > 0 && normalized == root + suffix { + return true + } + // When VROOT is unknown the pseudo-path stays unexpanded; `@VEXEROOT` is + // always the compiler's own root, so it still identifies the helper. + if normalized == '@VEXEROOT' + suffix { + return true + } + } + return false +} + +fn (mut g FlatGen) emit_preinclude_directives() { + for directive in g.preinclude_directives { + g.writeln(directive) + } + if g.preinclude_directives.len > 0 { + g.writeln('') + } +} + +fn (mut g FlatGen) emit_postinclude_directives() { + if g.postinclude_directives.len == 0 { + return + } + g.writeln('') + for directive in g.postinclude_directives { + g.writeln(directive) + } +} + +fn (mut g FlatGen) collect_preserved_header_tree(include_arg string, source_file string, include_dirs []string) bool { + // Some system APIs are declared through macros that the lightweight header + // declaration scanner cannot expand (for example OpenSSL's X509_free). + // Record the known declarations even when the resolved tree is scanned below. + g.collect_preserved_c_fns(c_preserved_system_include_declared_fns(include_arg)) + g.collect_preserved_c_structs(c_preserved_system_include_struct_names(include_arg)) + for path in c_include_file_paths(include_arg, g.compiler_vroot, source_file, include_dirs) { + mut tree_size := CHeaderTreeSize{} + if os.is_file(path) + && c_header_tree_exceeds_inline_limit(path, g.compiler_vroot, include_dirs, mut tree_size) { + g.collect_preserved_header_file(path, include_dirs) + return true + } + } + return false +} + +fn c_header_tree_exceeds_inline_limit(path string, vroot string, include_dirs []string, mut tree_size CHeaderTreeSize) bool { + real_path := os.real_path(path) + if real_path.len == 0 || tree_size.seen[real_path] || !os.is_file(real_path) { + return false + } + tree_size.seen[real_path] = true + tree_size.total_size += os.file_size(real_path) + if tree_size.total_size > c_inline_header_size_limit { + return true + } + text := os.read_file(real_path) or { return false } + mut in_block_comment := false + for line in text.split_into_lines() { + clean, next_in_block_comment := c_preprocessor_directive_scan_line(line, in_block_comment) + in_block_comment = next_in_block_comment + if c_directive_name(clean) !in ['include', 'import'] { + continue + } + include_arg := c_include_arg(c_directive_arg(clean), vroot, real_path) + for nested_path in c_include_file_paths(include_arg, vroot, real_path, include_dirs) { + if c_header_tree_exceeds_inline_limit(nested_path, vroot, include_dirs, mut tree_size) { + return true + } + } + } + return false +} + +fn (mut g FlatGen) collect_preserved_header_file(path string, include_dirs []string) { + real_path := os.real_path(path) + if real_path.len == 0 || g.preserved_header_files_seen[real_path] { + return + } + g.preserved_header_files_seen[real_path] = true + text := os.read_file(real_path) or { return } + g.collect_inlined_c_structs(text) + g.collect_inlined_c_fns(text) + g.collect_inlined_c_declared_fns(text) + mut in_block_comment := false + for line in text.split_into_lines() { + clean, next_in_block_comment := c_preprocessor_directive_scan_line(line, in_block_comment) + in_block_comment = next_in_block_comment + if c_directive_name(clean) !in ['include', 'import'] { + continue + } + include_arg := c_include_arg(c_directive_arg(clean), g.compiler_vroot, real_path) + mut found := false + for nested_path in c_include_file_paths(include_arg, g.compiler_vroot, real_path, + include_dirs) { + if os.is_file(nested_path) { + g.collect_preserved_header_file(nested_path, include_dirs) + found = true + break + } + } + if !found { + g.collect_preserved_c_fns(c_preserved_system_include_declared_fns(include_arg)) + g.collect_preserved_c_structs(c_preserved_system_include_struct_names(include_arg)) + } + } +} + +fn c_inline_header_text(include_arg string, vroot string, source_file string, include_dirs []string, translation_unit_uses_inttypes bool) ?CInlineHeader { + if replacement := c_system_include_replacement(include_arg, translation_unit_uses_inttypes) { + return CInlineHeader{ + text: replacement + } + } + mut seen := map[string]bool{} + mut inlining := map[string]bool{} + for path in c_include_file_paths(include_arg, vroot, source_file, include_dirs) { + mut scan_seen := map[string]bool{} + use_system_stdint := translation_unit_uses_inttypes + || c_inline_header_tree_uses_inttypes(path, vroot, include_dirs, mut scan_seen) + mut output := strings.new_builder(4096) + if header := c_inline_header_file(path, vroot, include_dirs, false, use_system_stdint, mut + seen, mut inlining, mut output) + { + return CInlineHeader{ + text: output.str() + preserved_directives: header.preserved_directives + preserved_c_fns: header.preserved_c_fns + preserved_c_structs: header.preserved_c_structs + preserved_headers: header.preserved_headers + } + } + unsafe { output.free() } + } + return none +} + +fn c_inline_header_file(path string, vroot string, include_dirs []string, conditional bool, use_system_stdint bool, mut seen map[string]bool, mut inlining map[string]bool, mut output strings.Builder) ?CInlineHeader { + if path.len == 0 || !os.exists(path) { + return none + } + real_path := os.real_path(path) + if seen[real_path] || inlining[real_path] { + return CInlineHeader{} + } + // A header first reached inside a false `#if` region would be invisible to + // the C preprocessor, so only an unconditional inline may suppress later + // copies; include guards make the re-emission a no-op. + if !conditional { + seen[real_path] = true + } + text := os.read_file(real_path) or { return none } + inlining[real_path] = true + header := c_inline_header_file_text(text, vroot, real_path, include_dirs, conditional, + use_system_stdint, mut seen, mut inlining, mut output) + inlining.delete(real_path) + return header +} + +fn c_inline_header_file_text(text string, vroot string, source_file string, include_dirs []string, conditional bool, use_system_stdint bool, mut seen map[string]bool, mut inlining map[string]bool, mut output strings.Builder) CInlineHeader { + guard_name := c_header_guard_name(text) + mut preserved_directives := []string{} + mut preserved_c_fns := []string{} + mut preserved_c_structs := []string{} + mut preserved_headers := []CPreservedHeader{} + mut include_context := []string{} + mut include_prefix := []string{} + mut in_block_comment := false + for line in text.split_into_lines() { + clean, next_in_block_comment := c_preprocessor_directive_scan_line(line, in_block_comment) + in_block_comment = next_in_block_comment + if c_directive_name(clean) in ['include', 'import'] { + include_arg := c_include_arg(c_directive_arg(clean), vroot, source_file) + // The headerless preamble already supplies the pthread/stdlib ABI used + // by these runtime helpers. Keeping their nested system includes would + // make an otherwise headerless translation unit redeclare those types. + clean_source_file := source_file.replace('\\', '/') + clean_include_arg := trimmed_space(include_arg) + if (clean_include_arg == '' + && clean_source_file.ends_with('/builtin/closure/closure_once_nix.h')) + || (clean_include_arg == '' + && clean_source_file.ends_with('/builtin/closure/closure_once_windows.h')) + || (clean_include_arg in ['', '', ''] + && clean_source_file.ends_with('/sync/thread_helper.h')) { + continue + } + if replacement := c_system_include_replacement(include_arg, use_system_stdint) { + output.writeln(replacement) + continue + } + nested_conditional := conditional + || !c_include_context_is_guard_only(include_context, guard_name) + mut inlined := false + if trimmed_space(include_arg).starts_with('<') { + for path in c_include_file_paths(include_arg, vroot, source_file, include_dirs) { + mut tree_size := CHeaderTreeSize{} + if os.is_file(path) + && c_header_tree_exceeds_inline_limit(path, vroot, include_dirs, mut tree_size) { + output.writeln('#include ${include_arg}') + preserved_headers << CPreservedHeader{ + include_arg: include_arg + source_file: source_file + } + preserved_c_fns << c_preserved_system_include_declared_fns(include_arg) + preserved_c_structs << c_preserved_system_include_struct_names(include_arg) + inlined = true + break + } + } + } + if inlined { + continue + } + for path in c_include_file_paths(include_arg, vroot, source_file, include_dirs) { + if nested := c_inline_header_file(path, vroot, include_dirs, nested_conditional, + use_system_stdint, mut seen, mut inlining, mut output) + { + for directive in nested.preserved_directives { + preserved_directives << c_wrap_preserved_nested_directive(directive, + include_context, include_prefix) + } + preserved_c_fns << nested.preserved_c_fns + preserved_c_structs << nested.preserved_c_structs + preserved_headers << nested.preserved_headers + inlined = true + break + } + } + if !inlined && (trimmed_space(include_arg).starts_with('<') + || c_include_should_remain_in_inlined_text(include_arg)) { + output.writeln('#include ${include_arg}') + preserved_c_fns << c_preserved_system_include_declared_fns(include_arg) + preserved_c_structs << c_preserved_system_include_struct_names(include_arg) + } else if !inlined && c_should_preserve_uninlined_include(include_arg) { + preserved_directives << c_preserved_nested_include_directive(include_arg, + include_context, include_prefix) + preserved_c_fns << c_preserved_system_include_declared_fns(include_arg) + preserved_c_structs << c_preserved_system_include_struct_names(include_arg) + } + continue + } + output.writeln(line) + c_update_nested_include_context(clean, line, mut include_context) + c_update_nested_include_prefix(clean, line, mut include_prefix) + } + return CInlineHeader{ + preserved_directives: preserved_directives + preserved_c_fns: preserved_c_fns + preserved_c_structs: preserved_c_structs + preserved_headers: preserved_headers + } +} + +// c_inline_header_tree_uses_inttypes detects whether an inlined include tree needs the real +// inttypes/stdint pair. Mixing the synthetic stdint typedefs with libc's inttypes header can +// redefine exact-width types with a different underlying C type on LP64 targets. +fn c_inline_header_tree_uses_inttypes(path string, vroot string, include_dirs []string, mut seen map[string]bool) bool { + if path.len == 0 || !os.exists(path) { + return false + } + real_path := os.real_path(path) + if seen[real_path] { + return false + } + seen[real_path] = true + text := os.read_file(real_path) or { return false } + mut in_block_comment := false + for line in text.split_into_lines() { + clean, next_in_block_comment := c_preprocessor_directive_scan_line(line, in_block_comment) + in_block_comment = next_in_block_comment + if c_directive_name(clean) !in ['include', 'import'] { + continue + } + include_arg := c_include_arg(c_directive_arg(clean), vroot, real_path) + if trimmed_space(include_arg) == '' { + return true + } + for nested_path in c_include_file_paths(include_arg, vroot, real_path, include_dirs) { + if c_inline_header_tree_uses_inttypes(nested_path, vroot, include_dirs, mut seen) { + return true + } + } + } + return false +} + +fn c_preprocessor_directive_scan_line(line string, in_block_comment bool) (string, bool) { + mut in_comment := in_block_comment + mut directive := strings.new_builder(line.len) + mut directive_started := false + mut directive_possible := true + mut quote := u8(0) + mut escaped := false + mut i := 0 + for i < line.len { + if in_comment { + end_rel := line[i..].index('*/') or { + unsafe { directive.free() } + return '', true + } + i += end_rel + 2 + in_comment = false + continue + } + if quote != 0 { + if directive_started { + directive.write_u8(line[i]) + } + if escaped { + escaped = false + } else if line[i] == `\\` { + escaped = true + } else if line[i] == quote { + quote = 0 + } + i++ + continue + } + if i + 1 < line.len && line[i] == `/` && line[i + 1] == `*` { + in_comment = true + if directive_started { + directive.write_u8(` `) + } + i += 2 + continue + } + if i + 1 < line.len && line[i] == `/` && line[i + 1] == `/` { + break + } + if !directive_started && directive_possible && line[i].is_space() { + i++ + continue + } + if !directive_started { + if !directive_possible || line[i] != `#` { + // Continue scanning ordinary source so a trailing block comment is + // carried into the next line, where `#` is still comment text. + directive_possible = false + if line[i] in [`'`, `"`] { + quote = line[i] + } + i++ + continue + } + directive_started = true + } + directive.write_u8(line[i]) + if line[i] in [`'`, `"`] { + quote = line[i] + } + i++ + } + result := if directive_started { directive.str().trim_space() } else { '' } + unsafe { directive.free() } + return result, in_comment +} + +// c_header_guard_name returns the macro of a classic `#ifndef X` / `#define X` +// include guard when it opens the header, or '' when there is no such guard. +fn c_header_guard_name(text string) string { + return c_header_guard_name_from_lines(text.split_into_lines()) +} + +fn c_header_guard_name_from_lines(lines []string) string { + mut in_block_comment := false + mut guard := '' + for line in lines { + clean, next_in_block_comment := c_preprocessor_directive_scan_line(line, in_block_comment) + in_block_comment = next_in_block_comment + name := c_directive_name(clean) + if name.len == 0 { + continue + } + if guard.len == 0 { + if name != 'ifndef' { + return '' + } + guard = c_directive_arg(clean) + if guard.len == 0 { + return '' + } + continue + } + if name == 'define' { + arg := c_directive_arg(clean) + if arg == guard || arg.starts_with(guard + ' ') { + return guard + } + } + return '' + } + return '' +} + +// c_include_context_is_guard_only reports whether the active `#if` context at an +// include site consists of nothing but the header's own include guard, i.e. the +// include is unconditionally reachable when the header itself is. +fn c_include_context_is_guard_only(context []string, guard_name string) bool { + if context.len == 0 { + return true + } + if context.len != 1 || guard_name.len == 0 { + return false + } + clean := trimmed_space(context[0]) + return c_directive_name(clean) == 'ifndef' && c_directive_arg(clean) == guard_name +} + +fn c_update_nested_include_context(clean string, line string, mut context []string) { + if context.len > 0 && c_line_has_continuation(context[context.len - 1]) { + context[context.len - 1] += '\n${line}' + return + } + match c_directive_name(clean) { + 'if', 'ifdef', 'ifndef', 'elif', 'else' { + context << line + } + 'endif' { + for context.len > 0 { + name := c_directive_name(context[context.len - 1].trim_space()) + context.delete_last() + if name in ['if', 'ifdef', 'ifndef'] { + break + } + } + } + else {} + } +} + +fn c_update_nested_include_prefix(clean string, line string, mut prefix []string) { + if prefix.len > 0 && c_line_has_continuation(prefix[prefix.len - 1]) { + prefix[prefix.len - 1] += '\n${line}' + return + } + match c_directive_name(clean) { + 'define', 'undef' { + prefix << line + } + else { + prefix.clear() + } + } +} + +fn c_wrap_preserved_nested_directive(directive string, context []string, prefix []string) string { + if context.len == 0 && prefix.len == 0 { + return directive + } + mut lines := context.clone() + lines << prefix + lines << directive + for _ in 0 .. c_nested_include_context_depth(context) { + lines << '#endif' + } + return lines.join('\n') +} + +fn c_line_has_continuation(line string) bool { + return line.trim_right(' \t\r').ends_with('\\') +} + +fn c_join_continued_lines(text string) []string { + mut lines := []string{} + mut logical_line := '' + for line in text.split_into_lines() { + logical_line += line + trimmed := logical_line.trim_right(' \t\r') + if trimmed.ends_with('\\') { + logical_line = trimmed[..trimmed.len - 1] + continue + } + lines << logical_line + logical_line = '' + } + if logical_line.len > 0 { + lines << logical_line + } + return lines +} + +fn c_preserved_nested_include_directive(include_arg string, context []string, prefix []string) string { + if context.len == 0 && prefix.len == 0 { + return '#include ${include_arg}' + } + mut lines := context.clone() + lines << prefix + lines << '#include ${include_arg}' + for _ in 0 .. c_nested_include_context_depth(context) { + lines << '#endif' + } + return lines.join('\n') +} + +fn c_nested_include_context_depth(context []string) int { + mut depth := 0 + for line in context { + if c_directive_name(line.trim_space()) in ['if', 'ifdef', 'ifndef'] { + depth++ + } + } + return depth +} + +fn c_flag_include_dirs(flags []string) []string { + mut dirs := []string{} + mut expect_include_dir := false + for flag in flags { + tok := flag.trim_space() + mut dir := '' + if expect_include_dir { + dir = tok + expect_include_dir = false + } else if tok in ['-I', '-isystem'] { + expect_include_dir = true + } else if tok.starts_with('-I') && tok.len > 2 { + dir = tok[2..] + } else if tok.starts_with('-isystem') && tok.len > '-isystem'.len { + dir = tok['-isystem'.len..].trim_left('=') + } + dir = dir.trim('"\'') + if dir.len > 0 && dir !in dirs { + dirs << dir + } + } + return dirs +} + +fn c_system_include_replacement(include_arg string, use_system_stdint bool) ?string { + match trimmed_space(include_arg) { + '' { + if use_system_stdint { + return '#include ' + } + return c_stdint_header_text() + } + '' { + // PRI*/SCN* format macros are implementation-specific; keep the real + // standard header when it is referenced by an inlined C header. + return '#include ' + } + '' { + // headerless build has no runtime assert support; NDEBUG semantics + return '#ifndef assert\n#define assert(e) ((void)0)\n#endif' + } + else { + return none + } + } +} + +fn c_should_preserve_uninlined_include(include_arg string) bool { + clean := trimmed_space(include_arg) + if clean.len == 0 { + return false + } + if clean[0] == `<` { + return clean in ['', '', '', '', ''] + || c_is_apple_framework_include(clean) + } + return true +} + +fn c_is_apple_framework_include(include_arg string) bool { + clean := trimmed_space(include_arg) + if clean.len < 4 || clean[0] != `<` || clean[clean.len - 1] != `>` { + return false + } + inner := clean[1..clean.len - 1] + slash := inner.index_u8(`/`) + if slash < 0 { + return false + } + framework := inner[..slash] + return framework.len > 0 && framework[0] >= `A` && framework[0] <= `Z` + && framework.bytes().all((it >= `A` && it <= `Z`) + || (it >= `a` && it <= `z`)) +} + +fn c_header_text_needs_objective_c(text string) bool { + return c_header_text_needs_objective_c_for_target(text, []string{}, false, pref.host_target()) +} + +fn c_header_text_needs_objective_c_for_target(text string, flags []string, c99_mode bool, target pref.Target) bool { + mut defined := map[string]bool{} + mut undefined := { + '__OBJC__': true + } + mut uncertain := map[string]bool{} + mut macro_values := map[string]string{} + mut objective_c_compatibility_macros := map[string]bool{} + mut i := 0 + for i < flags.len { + clean := trimmed_space(flags[i]) + mut definition := '' + mut is_undef := false + if clean == '-D' && i + 1 < flags.len { + definition = trimmed_space(flags[i + 1]) + i++ + } else if clean.starts_with('-D') { + definition = clean[2..] + } else if clean == '-U' && i + 1 < flags.len { + definition = trimmed_space(flags[i + 1]) + is_undef = true + i++ + } else if clean.starts_with('-U') { + definition = clean[2..] + is_undef = true + } + macro_declarator := definition.all_before('=').trim_space() + function_open := macro_declarator.index_u8(`(`) + is_function_like := function_open > 0 && !macro_declarator[function_open - 1].is_space() + macro_name := if is_function_like { + macro_declarator[..function_open].trim_space() + } else { + macro_declarator + } + if macro_name.len > 0 { + if is_undef { + defined.delete(macro_name) + undefined[macro_name] = true + macro_values.delete(macro_name) + if macro_name == c_has_attribute_predicate { + macro_values.delete(c_has_attribute_override_key) + } + if macro_name in c_objective_c_compatibility_qualifiers { + objective_c_compatibility_macros.delete(macro_name) + } + } else { + undefined.delete(macro_name) + defined[macro_name] = true + if macro_name in c_objective_c_compatibility_qualifiers { + if is_function_like { + objective_c_compatibility_macros.delete(macro_name) + } else { + objective_c_compatibility_macros[macro_name] = true + } + } + if is_function_like { + macro_values.delete(macro_name) + if macro_name == c_has_attribute_predicate { + macro_values[c_has_attribute_override_key] = if definition.contains('=') { + definition.all_after('=').trim_space() + } else { + '1' + } + } + } else { + if macro_name == c_has_attribute_predicate { + macro_values.delete(c_has_attribute_override_key) + } + macro_values[macro_name] = if definition.contains('=') { + definition.all_after('=').trim_space() + } else { + '1' + } + } + } + } + i++ + } + strict_iso_mode := c_effective_strict_iso_mode(flags, c99_mode) + mut condition_known := []bool{} + mut condition_active := []bool{} + mut condition_taken_known := []bool{} + mut condition_taken := []bool{} + mut possible_text := strings.new_builder(text.len) + mut definite_text := strings.new_builder(text.len / 4) + mut in_block_comment := false + for line in c_join_continued_lines(text) { + clean, next_in_block_comment := c_preprocessor_directive_scan_line(line, in_block_comment) + in_block_comment = next_in_block_comment + name := c_directive_name(clean) + mut directive_macro_name := '' + if name in ['ifdef', 'ifndef'] { + macro_name := c_directive_arg(clean).fields()[0] or { '' } + known, mut active := c_header_objective_c_macro_state(macro_name, defined, undefined, + uncertain, strict_iso_mode, target) + if name == 'ifndef' { + active = !active + } + condition_known << known + condition_active << (if known { active } else { true }) + condition_taken_known << known + condition_taken << (if known { active } else { true }) + } else if name == 'if' { + known, active := c_header_objective_c_condition_state(c_directive_arg(clean), defined, + undefined, uncertain, macro_values, strict_iso_mode, target) + condition_known << known + condition_active << (if known { active } else { true }) + condition_taken_known << known + condition_taken << (if known { active } else { true }) + } else if name == 'elif' && condition_known.len > 0 { + last := condition_known.len - 1 + prior_known := condition_taken_known[last] + prior_taken := condition_taken[last] + known, active := c_header_objective_c_condition_state(c_directive_arg(clean), defined, + undefined, uncertain, macro_values, strict_iso_mode, target) + if (prior_known && prior_taken) || (known && !active) { + condition_known[last] = true + condition_active[last] = false + } else if prior_known && known { + condition_known[last] = true + condition_active[last] = true + } else { + condition_known[last] = false + condition_active[last] = true + } + if (prior_known && prior_taken) || (known && active) { + condition_taken_known[last] = true + condition_taken[last] = true + } else if prior_known && known { + condition_taken_known[last] = true + condition_taken[last] = false + } else { + condition_taken_known[last] = false + condition_taken[last] = true + } + } else if name == 'else' && condition_known.len > 0 { + last := condition_known.len - 1 + condition_known[last] = condition_taken_known[last] + condition_active[last] = if condition_taken_known[last] { + !condition_taken[last] + } else { + true + } + condition_taken_known[last] = true + condition_taken[last] = true + } else if name == 'endif' && condition_known.len > 0 { + condition_known.delete_last() + condition_active.delete_last() + condition_taken_known.delete_last() + condition_taken.delete_last() + } + mut possibly_active := true + mut definitely_active := true + for depth in 0 .. condition_known.len { + if condition_known[depth] && !condition_active[depth] { + possibly_active = false + } + definitely_active = definitely_active && condition_known[depth] + && condition_active[depth] + } + if !possibly_active { + possible_text.writeln('') + definite_text.writeln('') + continue + } + if name == 'import' && c_is_apple_framework_include(c_directive_arg(clean)) { + return true + } + if name in ['define', 'undef'] { + parts := c_directive_arg(clean).fields() + if parts.len > 0 { + macro_name := parts[0].all_before('(') + directive_macro_name = macro_name + if definitely_active { + uncertain.delete(macro_name) + if name == 'define' { + undefined.delete(macro_name) + defined[macro_name] = true + macro_values.delete(macro_name) + definition := c_directive_arg(clean).trim_space() + macro_token := parts[0] + if macro_name in c_objective_c_compatibility_qualifiers { + if macro_token.contains('(') { + objective_c_compatibility_macros.delete(macro_name) + } else { + objective_c_compatibility_macros[macro_name] = true + } + } + if macro_token.contains('(') { + if macro_name == c_has_attribute_predicate { + macro_values[c_has_attribute_override_key] = if definition.len > macro_token.len { + definition[macro_token.len..].trim_space() + } else { + '' + } + } + } else { + if macro_name == c_has_attribute_predicate { + macro_values.delete(c_has_attribute_override_key) + } + if definition.len > macro_token.len { + macro_values[macro_name] = + definition[macro_token.len..].trim_space() + } + } + } else { + defined.delete(macro_name) + undefined[macro_name] = true + macro_values.delete(macro_name) + if macro_name == c_has_attribute_predicate { + macro_values.delete(c_has_attribute_override_key) + } + if macro_name in c_objective_c_compatibility_qualifiers { + objective_c_compatibility_macros.delete(macro_name) + } + } + } else { + defined.delete(macro_name) + undefined.delete(macro_name) + uncertain[macro_name] = true + macro_values.delete(macro_name) + if macro_name == c_has_attribute_predicate { + macro_values.delete(c_has_attribute_override_key) + } + if macro_name in c_objective_c_compatibility_qualifiers { + objective_c_compatibility_macros.delete(macro_name) + } + } + } + } + mut possible_line := line + for qualifier in c_objective_c_compatibility_qualifiers { + if objective_c_compatibility_macros[qualifier] || directive_macro_name == qualifier { + possible_line = c_header_text_without_identifier(possible_line, qualifier) + } + } + possible_text.writeln(possible_line) + definite_text.writeln(if definitely_active { possible_line } else { '' }) + } + possible_source := possible_text.str() + definite_typedefs := modulecache.c_source_typedef_identifiers(definite_text.str()) + return c_header_text_has_objective_c_tokens(possible_source, definite_typedefs) +} + +fn c_header_objective_c_macro_state(name string, defined map[string]bool, undefined map[string]bool, uncertain map[string]bool, strict_iso_mode bool, target pref.Target) (bool, bool) { + if name.len == 0 { + return false, true + } + if name !in defined && name !in undefined && name !in uncertain && !name.starts_with('_') { + return false, true + } + return c_preprocessor_macro_state(name, defined, undefined, uncertain, strict_iso_mode, target) +} + +fn c_header_condition_without_comments(raw string) string { + mut result := strings.new_builder(raw.len) + mut i := 0 + mut quote := u8(0) + mut escaped := false + mut in_block_comment := false + for i < raw.len { + c := raw[i] + if in_block_comment { + if c == `*` && i + 1 < raw.len && raw[i + 1] == `/` { + in_block_comment = false + i += 2 + continue + } + i++ + continue + } + if quote != 0 { + result.write_u8(c) + if escaped { + escaped = false + } else if c == `\\` { + escaped = true + } else if c == quote { + quote = 0 + } + i++ + continue + } + if c == `"` || c == `'` { + quote = c + result.write_u8(c) + i++ + continue + } + if c == `/` && i + 1 < raw.len { + if raw[i + 1] == `/` { + break + } + if raw[i + 1] == `*` { + in_block_comment = true + i += 2 + continue + } + } + result.write_u8(c) + i++ + } + return result.str().trim_space() +} + +fn c_header_objective_c_condition_state(raw string, defined map[string]bool, undefined map[string]bool, uncertain map[string]bool, macro_values map[string]string, strict_iso_mode bool, target pref.Target) (bool, bool) { + clean := c_header_condition_without_outer_parens(c_header_condition_without_comments(raw)) + if active := c_header_objective_c_compiler_predicate_state(clean, defined, undefined, + uncertain, macro_values, strict_iso_mode, target) + { + return true, active + } + has_conditional, condition, if_true, if_false := c_header_condition_top_level_conditional(clean) + if has_conditional { + known, active := c_header_objective_c_condition_state(condition, defined, undefined, + uncertain, macro_values, strict_iso_mode, target) + if !known { + return false, true + } + return c_header_objective_c_condition_state(if active { if_true } else { if_false }, + defined, undefined, uncertain, macro_values, strict_iso_mode, target) + } + or_parts := c_header_condition_top_level_parts(clean, '||') + if or_parts.len > 1 { + mut all_known := true + for part in or_parts { + known, active := c_header_objective_c_condition_state(part, defined, undefined, + uncertain, macro_values, strict_iso_mode, target) + if known && active { + return true, true + } + all_known = all_known && known + } + return if all_known { true, false } else { false, true } + } + and_parts := c_header_condition_top_level_parts(clean, '&&') + if and_parts.len > 1 { + mut all_known := true + for part in and_parts { + known, active := c_header_objective_c_condition_state(part, defined, undefined, + uncertain, macro_values, strict_iso_mode, target) + if known && !active { + return true, false + } + all_known = all_known && known + } + return if all_known { true, true } else { false, true } + } + if value := c_header_objective_c_integer_operand_value(clean, defined, undefined, uncertain, + macro_values, strict_iso_mode, target) + { + return true, value != 0 + } + has_comparison, left_text, operator, right_text := + c_header_condition_top_level_comparison(clean) + if has_comparison { + left := c_header_objective_c_integer_operand_value(left_text, defined, undefined, + uncertain, macro_values, strict_iso_mode, target) or { return false, true } + right := c_header_objective_c_integer_operand_value(right_text, defined, undefined, + uncertain, macro_values, strict_iso_mode, target) or { return false, true } + if operator in ['<', '<=', '>', '>='] && (left < 0 || right < 0) { + // Signed/unsigned conversion rules can reverse ordered comparisons. + return false, true + } + active := match operator { + '==' { left == right } + '!=' { left != right } + '<' { left < right } + '<=' { left <= right } + '>' { left > right } + '>=' { left >= right } + else { return false, true } + } + return true, active + } + if clean.starts_with('!') { + known, active := c_header_objective_c_condition_state(clean[1..], defined, undefined, + uncertain, macro_values, strict_iso_mode, target) + return known, !active + } + literal_known, literal_active := c_header_objective_c_integer_macro_state(clean) + if literal_known { + return true, literal_active + } + if clean.len > 0 && c_identifier_start(clean[0]) && c_header_struct_tag(clean) == clean { + known, mut active := c_header_objective_c_macro_state(clean, defined, undefined, uncertain, + strict_iso_mode, target) + if known && active { + if clean in macro_values { + value_known, value_active := c_header_objective_c_macro_value_state(clean, defined, + undefined, uncertain, macro_values, strict_iso_mode, target) + if value_known { + active = value_active + } else { + return false, true + } + } + } + return known, active + } + macro_name := c_header_defined_macro_name(clean) or { return false, true } + known, active := c_header_objective_c_macro_state(macro_name, defined, undefined, uncertain, + strict_iso_mode, target) + return known, active +} + +fn c_header_objective_c_compiler_predicate_state(clean string, defined map[string]bool, undefined map[string]bool, uncertain map[string]bool, macro_values map[string]string, strict_iso_mode bool, target pref.Target) ?bool { + predicate := c_has_attribute_predicate + if !clean.starts_with(predicate) { + return none + } + rest := clean[predicate.len..].trim_space() + if rest.len < 3 || rest[0] != `(` || rest[rest.len - 1] != `)` { + return none + } + attribute := rest[1..rest.len - 1].trim_space() + if attribute.len == 0 || c_header_struct_tag(attribute) != attribute { + return none + } + if predicate in uncertain || predicate in undefined { + return none + } + if predicate in defined { + replacement := macro_values[c_has_attribute_override_key] or { return none } + value := + c_header_condition_without_outer_parens(c_header_condition_without_comments(replacement)) + literal_known, literal_active := c_header_objective_c_integer_macro_state(value) + if literal_known { + return literal_active + } + if value.len > 0 && c_identifier_start(value[0]) && c_header_struct_tag(value) == value { + known, active := c_header_objective_c_macro_value_state(value, defined, undefined, + uncertain, macro_values, strict_iso_mode, target) + if known { + return active + } + } + return none + } + return attribute.trim('_') in c_common_c_attributes +} + +fn c_header_defined_macro_name(clean string) ?string { + if !clean.starts_with('defined') || (clean.len > 'defined'.len && clean['defined'.len] != `(` + && !clean['defined'.len].is_space()) { + return none + } + rest := clean['defined'.len..].trim_space() + mut macro_name := '' + if rest.starts_with('(') { + close := rest.index_u8(`)`) + if close < 0 || (close + 1 < rest.len && rest[close + 1..].trim_space().len > 0) { + return none + } + macro_name = rest[1..close].trim_space() + } else { + parts := rest.fields() + if parts.len != 1 { + return none + } + macro_name = parts[0] + } + if macro_name.len == 0 || c_header_struct_tag(macro_name) != macro_name { + return none + } + return macro_name +} + +fn c_header_objective_c_integer_operand_value(raw string, defined map[string]bool, undefined map[string]bool, uncertain map[string]bool, macro_values map[string]string, strict_iso_mode bool, target pref.Target) ?i64 { + mut seen := map[string]bool{} + return c_header_objective_c_integer_expression_value(raw, defined, undefined, uncertain, + macro_values, strict_iso_mode, target, mut seen, 0) +} + +fn c_header_objective_c_integer_expression_value(raw string, defined map[string]bool, undefined map[string]bool, uncertain map[string]bool, macro_values map[string]string, strict_iso_mode bool, target pref.Target, mut seen map[string]bool, depth int) ?i64 { + if depth >= 64 { + return none + } + clean := c_header_condition_without_outer_parens(c_header_condition_without_comments(raw)) + if active := c_header_objective_c_compiler_predicate_state(clean, defined, undefined, + uncertain, macro_values, strict_iso_mode, target) + { + return if active { i64(1) } else { i64(0) } + } + if value := c_header_objective_c_integer_value(clean) { + return value + } + has_conditional, condition, if_true, if_false := c_header_condition_top_level_conditional(clean) + if has_conditional { + known, active := c_header_objective_c_condition_state(condition, defined, undefined, + uncertain, macro_values, strict_iso_mode, target) + if !known { + return none + } + return c_header_objective_c_integer_expression_value(if active { if_true } else { if_false }, + defined, undefined, uncertain, macro_values, strict_iso_mode, target, mut seen, depth + + 1) + } + operator_groups := [ + ['|'], + ['^'], + ['&'], + ['==', '!='], + ['<=', '>=', '<', '>'], + ['<<', '>>'], + ['+', '-'], + ['*', '/', '%'], + ] + for operators in operator_groups { + has_operator, left_text, operator, right_text := c_header_condition_top_level_binary(clean, + operators) + if !has_operator { + continue + } + left := c_header_objective_c_integer_expression_value(left_text, defined, undefined, + uncertain, macro_values, strict_iso_mode, target, mut seen, depth + 1) or { + return none + } + right := c_header_objective_c_integer_expression_value(right_text, defined, undefined, + uncertain, macro_values, strict_iso_mode, target, mut seen, depth + 1) or { + return none + } + return c_header_objective_c_checked_integer_binary(left, right, operator) + } + if clean.len > 1 && clean[0] in [`+`, `-`, `!`, `~`] { + value := c_header_objective_c_integer_expression_value(clean[1..], defined, undefined, + uncertain, macro_values, strict_iso_mode, target, mut seen, depth + 1) or { + return none + } + if clean[0] == `+` { + return value + } + if clean[0] == `!` { + return if value == 0 { i64(1) } else { i64(0) } + } + if clean[0] == `~` { + return ~value + } + if value == i64(-0x7fffffffffffffff - 1) { + return none + } + return -value + } + if macro_name := c_header_defined_macro_name(clean) { + known, active := c_header_objective_c_macro_state(macro_name, defined, undefined, + uncertain, strict_iso_mode, target) + if !known { + return none + } + return if active { i64(1) } else { i64(0) } + } + if clean.len == 0 || !c_identifier_start(clean[0]) || c_header_struct_tag(clean) != clean + || seen[clean] { + return none + } + known, active := c_header_objective_c_macro_state(clean, defined, undefined, uncertain, + strict_iso_mode, target) + if !known { + return none + } + if !active { + return i64(0) + } + replacement := macro_values[clean] or { return none } + seen[clean] = true + value := c_header_objective_c_integer_expression_value(replacement, defined, undefined, + uncertain, macro_values, strict_iso_mode, target, mut seen, depth + 1) + seen.delete(clean) + return value +} + +fn c_header_condition_top_level_binary(expression string, operators []string) (bool, string, string, string) { + mut depth := 0 + mut operator_index := -1 + mut selected_operator := '' + mut i := 0 + for i < expression.len { + c := expression[i] + if c in [`"`, `'`] { + quote := c + i++ + for i < expression.len { + if expression[i] == `\\` && i + 1 < expression.len { + i += 2 + continue + } + i++ + if expression[i - 1] == quote { + break + } + } + continue + } + if c == `(` { + depth++ + i++ + continue + } + if c == `)` { + depth-- + i++ + continue + } + if depth != 0 { + i++ + continue + } + mut matched := '' + for operator in operators { + if expression[i..].starts_with(operator) { + matched = operator + break + } + } + if matched.len == 0 { + i++ + continue + } + if matched.len == 1 && matched[0] in [`&`, `|`] + && ((i > 0 && expression[i - 1] == matched[0]) + || (i + 1 < expression.len && expression[i + 1] == matched[0])) { + i++ + continue + } + if matched.len == 1 && matched[0] in [`<`, `>`] + && ((i > 0 && expression[i - 1] == matched[0]) + || (i + 1 < expression.len && expression[i + 1] == matched[0])) { + i++ + continue + } + mut previous := i - 1 + for previous >= 0 && expression[previous].is_space() { + previous-- + } + if previous >= 0 + && (c_identifier_continue(expression[previous]) || expression[previous] in [`)`, `'`]) { + operator_index = i + selected_operator = matched + } + i += matched.len + } + if operator_index < 0 { + return false, '', '', '' + } + left := expression[..operator_index].trim_space() + right := expression[operator_index + selected_operator.len..].trim_space() + if left.len == 0 || right.len == 0 { + return false, '', '', '' + } + return true, left, selected_operator, right +} + +fn c_header_objective_c_checked_integer_binary(left i64, right i64, operator string) ?i64 { + match operator { + '|' { + if left < 0 || right < 0 { + return none + } + return left | right + } + '^' { + if left < 0 || right < 0 { + return none + } + return left ^ right + } + '&' { + if left < 0 || right < 0 { + return none + } + return left & right + } + '==' { + return if left == right { i64(1) } else { i64(0) } + } + '!=' { + return if left != right { i64(1) } else { i64(0) } + } + '<', '<=', '>', '>=' { + if left < 0 || right < 0 { + return none + } + active := match operator { + '<' { left < right } + '<=' { left <= right } + '>' { left > right } + else { left >= right } + } + return if active { i64(1) } else { i64(0) } + } + '<<', '>>' { + if left < 0 || right < 0 || right >= 63 { + return none + } + shift := u32(right) + if operator == '<<' { + if left > i64(0x7fffffffffffffff) >> shift { + return none + } + return i64(u64(left) << shift) + } + return left >> shift + } + '+', '-', '*', '/', '%' { + return c_header_objective_c_checked_arithmetic(left, right, operator[0]) + } + else { + return none + } + } +} + +fn c_header_objective_c_checked_arithmetic(left i64, right i64, operator u8) ?i64 { + max_value := i64(0x7fffffffffffffff) + min_value := i64(-0x7fffffffffffffff - 1) + match operator { + `+` { + if (right > 0 && left > max_value - right) || (right < 0 && left < min_value - right) { + return none + } + return left + right + } + `-` { + if (right > 0 && left < min_value + right) || (right < 0 && left > max_value + right) { + return none + } + return left - right + } + `*` { + if (left > 0 && right > 0 && left > max_value / right) + || (left > 0 && right < 0 && right < min_value / left) + || (left < 0 && right > 0 && left < min_value / right) + || (left < 0 && right < 0 && left < max_value / right) { + return none + } + return left * right + } + `/` { + if right == 0 || (left == min_value && right == -1) { + return none + } + return left / right + } + `%` { + if right == 0 || (left == min_value && right == -1) { + return none + } + return left % right + } + else { + return none + } + } +} + +fn c_header_objective_c_macro_value_state(name string, defined map[string]bool, undefined map[string]bool, uncertain map[string]bool, macro_values map[string]string, strict_iso_mode bool, target pref.Target) (bool, bool) { + mut current := name + mut seen := map[string]bool{} + for _ in 0 .. 64 { + if seen[current] { + return false, true + } + seen[current] = true + known, active := c_header_objective_c_macro_state(current, defined, undefined, uncertain, + strict_iso_mode, target) + if !known || !active { + return known, active + } + replacement := macro_values[current] or { return true, true } + clean := + c_header_condition_without_outer_parens(c_header_condition_without_comments(replacement)) + literal_known, literal_active := c_header_objective_c_integer_macro_state(clean) + if literal_known { + return true, literal_active + } + if clean.len == 0 || !c_identifier_start(clean[0]) || c_header_struct_tag(clean) != clean { + return false, true + } + current = clean + } + return false, true +} + +fn c_header_objective_c_integer_macro_state(raw string) (bool, bool) { + value := c_header_objective_c_integer_value(raw) or { return false, true } + return true, value != 0 +} + +fn c_header_objective_c_integer_value(raw string) ?i64 { + clean := c_header_condition_without_outer_parens(raw.trim_space()) + if clean.len == 0 { + return none + } + if value := c_header_objective_c_character_value(clean) { + return value + } + mut i := 0 + mut negative := false + if clean[i] in [`+`, `-`] { + negative = clean[i] == `-` + i++ + } + if i >= clean.len { + return none + } + mut base := 10 + if i + 2 < clean.len && clean[i] == `0` && clean[i + 1] in [`x`, `X`] { + base = 16 + i += 2 + } else if i + 1 < clean.len && clean[i] == `0` { + base = 8 + } + mut saw_digit := false + mut value := i64(0) + max_value := i64(0x7fffffffffffffff) + for i < clean.len { + c := clean[i] + mut digit := -1 + if c >= `0` && c <= `9` { + digit = int(c - `0`) + } else if base == 16 && c >= `a` && c <= `f` { + digit = int(c - `a`) + 10 + } else if base == 16 && c >= `A` && c <= `F` { + digit = int(c - `A`) + 10 + } + if digit >= 0 && digit < base { + saw_digit = true + digit_value := i64(digit) + base_value := i64(base) + if value > (max_value - digit_value) / base_value { + return none + } + value = value * base_value + digit_value + i++ + continue + } + if saw_digit && clean[i..].bytes().all(it in [`u`, `U`, `l`, `L`]) { + break + } + return none + } + if !saw_digit { + return none + } + return if negative { -value } else { value } +} + +fn c_header_objective_c_character_value(raw string) ?i64 { + mut i := 0 + if raw.starts_with('u8') { + i = 2 + } else if raw.len > 0 && raw[0] in [`L`, `u`, `U`] { + i = 1 + } + if i >= raw.len || raw[i] != `'` { + return none + } + i++ + if i >= raw.len { + return none + } + mut value := i64(0) + if raw[i] != `\\` { + value = i64(raw[i]) + i++ + } else { + i++ + if i >= raw.len { + return none + } + escape := raw[i] + if escape >= `0` && escape <= `7` { + mut digits := 0 + for i < raw.len && digits < 3 && raw[i] >= `0` && raw[i] <= `7` { + value = value * 8 + i64(raw[i] - `0`) + i++ + digits++ + } + } else if escape == `x` { + i++ + mut digits := 0 + for i < raw.len { + c := raw[i] + mut digit := -1 + if c >= `0` && c <= `9` { + digit = int(c - `0`) + } else if c >= `a` && c <= `f` { + digit = int(c - `a`) + 10 + } else if c >= `A` && c <= `F` { + digit = int(c - `A`) + 10 + } + if digit < 0 { + break + } + if value > (i64(0x7fffffffffffffff) - i64(digit)) / 16 { + return none + } + value = value * 16 + i64(digit) + i++ + digits++ + } + if digits == 0 { + return none + } + } else { + value = match escape { + `a` { i64(7) } + `b` { i64(8) } + `f` { i64(12) } + `n` { i64(10) } + `r` { i64(13) } + `t` { i64(9) } + `v` { i64(11) } + `\\` { i64(`\\`) } + `'` { i64(`'`) } + `"` { i64(`"`) } + `?` { i64(`?`) } + else { return none } + } + i++ + } + } + if i >= raw.len || raw[i] != `'` || i + 1 != raw.len { + return none + } + return value +} + +fn c_header_condition_without_outer_parens(expression string) string { + mut clean := expression.trim_space() + for clean.len >= 2 && clean[0] == `(` && clean[clean.len - 1] == `)` { + mut depth := 0 + mut closes_at_end := false + for i, c in clean.bytes() { + if c == `(` { + depth++ + } else if c == `)` { + depth-- + if depth == 0 { + closes_at_end = i == clean.len - 1 + break + } + } + } + if !closes_at_end { + break + } + clean = clean[1..clean.len - 1].trim_space() + } + return clean +} + +fn c_header_condition_top_level_conditional(expression string) (bool, string, string, string) { + mut paren_depth := 0 + mut question := -1 + mut nested_conditionals := 0 + mut colon := -1 + mut i := 0 + for i < expression.len { + if expression[i] in [`"`, `'`] { + quote := expression[i] + i++ + for i < expression.len { + if expression[i] == `\\` && i + 1 < expression.len { + i += 2 + continue + } + i++ + if expression[i - 1] == quote { + break + } + } + continue + } + if expression[i] == `(` { + paren_depth++ + i++ + continue + } + if expression[i] == `)` { + paren_depth-- + i++ + continue + } + if paren_depth != 0 { + i++ + continue + } + if expression[i] == `?` { + if question < 0 { + question = i + } else { + nested_conditionals++ + } + } else if expression[i] == `:` && question >= 0 { + if nested_conditionals > 0 { + nested_conditionals-- + } else { + colon = i + break + } + } + i++ + } + if question < 0 || colon < 0 { + return false, '', '', '' + } + condition := expression[..question].trim_space() + if_true := expression[question + 1..colon].trim_space() + if_false := expression[colon + 1..].trim_space() + if condition.len == 0 || if_true.len == 0 || if_false.len == 0 { + return false, '', '', '' + } + return true, condition, if_true, if_false +} + +fn c_header_condition_top_level_parts(expression string, operator string) []string { + if operator.len != 2 { + return [expression] + } + mut parts := []string{} + mut depth := 0 + mut start := 0 + mut i := 0 + for i + 1 < expression.len { + if expression[i] == `(` { + depth++ + i++ + continue + } + if expression[i] == `)` { + depth-- + i++ + continue + } + if depth == 0 && expression[i..i + 2] == operator { + part := expression[start..i].trim_space() + if part.len == 0 { + return [expression] + } + parts << part + i += 2 + start = i + continue + } + i++ + } + if parts.len == 0 { + return [expression] + } + last := expression[start..].trim_space() + if last.len == 0 { + return [expression] + } + parts << last + return parts +} + +fn c_header_condition_top_level_comparison(expression string) (bool, string, string, string) { + mut depth := 0 + mut found_at := -1 + mut found_operator := '' + mut i := 0 + for i < expression.len { + if expression[i] in [`"`, `'`] { + quote := expression[i] + i++ + for i < expression.len { + if expression[i] == `\\` && i + 1 < expression.len { + i += 2 + continue + } + i++ + if expression[i - 1] == quote { + break + } + } + continue + } + if expression[i] == `(` { + depth++ + i++ + continue + } + if expression[i] == `)` { + depth-- + i++ + continue + } + if depth != 0 { + i++ + continue + } + mut operator := '' + if i + 1 < expression.len && expression[i..i + 2] in ['==', '!=', '<=', '>='] { + operator = expression[i..i + 2] + } else if expression[i] in [`<`, `>`] + && (i + 1 >= expression.len || expression[i + 1] != expression[i]) { + operator = expression[i..i + 1] + } + if operator.len == 0 { + i++ + continue + } + if found_at >= 0 { + return false, '', '', '' + } + found_at = i + found_operator = operator + i += operator.len + } + if found_at < 0 { + return false, '', '', '' + } + left := expression[..found_at].trim_space() + right := expression[found_at + found_operator.len..].trim_space() + if left.len == 0 || right.len == 0 { + return false, '', '', '' + } + return true, left, found_operator, right +} + +fn c_header_text_without_identifier(text string, name string) string { + if name.len == 0 { + return text + } + mut result := strings.new_builder(text.len) + mut start := 0 + mut i := 0 + for i < text.len { + if !c_identifier_start(text[i]) { + i++ + continue + } + token_start := i + i++ + for i < text.len && c_identifier_continue(text[i]) { + i++ + } + if text[token_start..i] == name { + result.write_string(text[start..token_start]) + for _ in token_start .. i { + result.write_u8(` `) + } + start = i + } + } + result.write_string(text[start..]) + return result.str() +} + +fn c_header_skip_space_and_comments(text string, start int, limit int) int { + mut i := start + for i < limit { + if text[i].is_space() { + i++ + continue + } + if i + 1 < limit && text[i] == `/` && text[i + 1] == `/` { + i += 2 + for i < limit && text[i] != `\n` { + i++ + } + continue + } + if i + 1 < limit && text[i] == `/` && text[i + 1] == `*` { + i += 2 + for i + 1 < limit && !(text[i] == `*` && text[i + 1] == `/`) { + i++ + } + if i + 1 < limit { + i += 2 + } + continue + } + break + } + return i +} + +fn c_header_bracket_has_objective_c_message(text string, start int) bool { + if start < 0 || start >= text.len || text[start] != `[` { + return false + } + mut i := start + 1 + mut square_depth := 1 + mut paren_depth := 0 + mut brace_depth := 0 + mut atoms := 0 + mut separated := false + for i < text.len { + if text[i].is_space() { + separated = true + i++ + continue + } + if i + 1 < text.len && text[i] == `/` && text[i + 1] in [`/`, `*`] { + next := c_header_skip_space_and_comments(text, i, text.len) + if next == i { + return false + } + separated = true + i = next + continue + } + if text[i] in [`"`, `'`] { + if square_depth == 1 && paren_depth == 0 && brace_depth == 0 { + atoms++ + } + quote := text[i] + i++ + for i < text.len { + if text[i] == `\\` && i + 1 < text.len { + i += 2 + continue + } + i++ + if text[i - 1] == quote { + break + } + } + separated = false + continue + } + if text[i] == `[` { + if square_depth == 1 && paren_depth == 0 && brace_depth == 0 { + atoms++ + } + square_depth++ + separated = false + i++ + continue + } + if text[i] == `]` { + if square_depth == 1 { + return false + } + square_depth-- + separated = false + i++ + continue + } + if square_depth != 1 { + i++ + continue + } + if text[i] == `(` { + if paren_depth == 0 && brace_depth == 0 { + atoms++ + } + paren_depth++ + separated = false + i++ + continue + } + if text[i] == `)` { + if paren_depth == 0 { + return false + } + paren_depth-- + separated = false + i++ + continue + } + if text[i] == `{` { + brace_depth++ + separated = false + i++ + continue + } + if text[i] == `}` { + if brace_depth == 0 { + return false + } + brace_depth-- + separated = false + i++ + continue + } + if paren_depth != 0 || brace_depth != 0 { + i++ + continue + } + if c_identifier_start(text[i]) { + mut end := i + 1 + for end < text.len && c_identifier_continue(text[end]) { + end++ + } + if atoms > 0 && separated { + next := c_header_skip_space_and_comments(text, end, text.len) + if next < text.len && text[next] in [`]`, `:`] { + return true + } + } + atoms++ + separated = false + i = end + continue + } + if text[i] >= `0` && text[i] <= `9` { + atoms++ + separated = false + i++ + for i < text.len && (c_identifier_continue(text[i]) || text[i] == `.`) { + i++ + } + continue + } + if text[i] == `.` { + if i + 2 < text.len && text[i..i + 3] == '...' { + return false + } + separated = false + i++ + continue + } + if text[i] == `-` && i + 1 < text.len && text[i + 1] == `>` { + separated = false + i += 2 + continue + } + if text[i] in [`+`, `-`, `*`, `/`, `%`, `?`, `:`, `=`, `!`, `<`, `>`, `|`, `&`, `^`, `,`, + `;`] { + return false + } + separated = false + i++ + } + return false +} + +fn c_header_has_objective_c_qualified_type(text string, token string, token_end int, local_typedefs map[string]bool) bool { + if local_typedefs[token] { + return false + } + open := c_header_skip_space_and_comments(text, token_end, text.len) + if open >= text.len || text[open] != `<` { + return false + } + mut i := open + 1 + mut depth := 1 + mut identifiers := 0 + mut close := -1 + for i < text.len { + next := c_header_skip_space_and_comments(text, i, text.len) + if next != i { + i = next + continue + } + if c_identifier_start(text[i]) { + i++ + for i < text.len && c_identifier_continue(text[i]) { + i++ + } + identifiers++ + continue + } + if text[i] == `<` { + depth++ + i++ + continue + } + if text[i] == `>` { + depth-- + if depth == 0 { + close = i + break + } + i++ + continue + } + if text[i] == `*` { + i++ + continue + } + if text[i] == `,` { + i++ + continue + } + return false + } + if close < 0 || identifiers == 0 { + return false + } + if token in ['id', 'Class'] { + return true + } + after := c_header_skip_space_and_comments(text, close + 1, text.len) + return token.len > 0 && token[0] >= `A` && token[0] <= `Z` && after < text.len + && text[after] == `*` +} + +fn c_header_token_is_on_directive_line(text string, start int) bool { + mut line_start := start + for line_start > 0 && text[line_start - 1] != `\n` { + line_start-- + } + for line_start < start && text[line_start].is_space() { + line_start++ + } + return line_start < start && text[line_start] == `#` +} + +fn c_header_token_follows_open_parenthesis(text string, start int) bool { + mut before := start + for { + for before > 0 && text[before - 1].is_space() { + before-- + } + if before >= 2 && text[before - 2..before] == '*/' { + comment_start := text[..before - 2].last_index('/*') or { return false } + before = comment_start + continue + } + if before > 0 && c_identifier_continue(text[before - 1]) { + mut qualifier_start := before - 1 + for qualifier_start > 0 && c_identifier_continue(text[qualifier_start - 1]) { + qualifier_start-- + } + if text[qualifier_start..before] in ['const', 'volatile', 'restrict', '__restrict', + '__restrict__', '_Atomic'] { + before = qualifier_start + continue + } + return false + } + if before == 0 || text[before - 1] != `(` { + return false + } + mut prefix := before - 1 + for prefix > 0 && text[prefix - 1].is_space() { + prefix-- + } + if prefix > 0 && c_identifier_continue(text[prefix - 1]) { + mut identifier_start := prefix - 1 + for identifier_start > 0 && c_identifier_continue(text[identifier_start - 1]) { + identifier_start-- + } + if text[identifier_start..prefix] !in ['return', 'case', 'sizeof', '_Alignof', 'alignof'] { + return false + } + } else if prefix > 0 && text[prefix - 1] in [`)`, `]`] { + return false + } + return true + } + return false +} + +fn c_header_cast_has_operand(text string, close int) bool { + after := c_header_skip_space_and_comments(text, close + 1, text.len) + if after >= text.len { + return false + } + c := text[after] + return c_identifier_start(c) || (c >= `0` && c <= `9`) + || c in [`(`, `*`, `&`, `!`, `~`, `+`, `-`, `"`, `'`, `@`, `[`, `{`] +} + +fn c_header_has_bare_objective_c_type(text string, token string, token_start int, token_end int, previous_identifier string, local_typedefs map[string]bool) bool { + if token !in c_objective_c_contextual_types || local_typedefs[token] + || previous_identifier in ['struct', 'union', 'enum'] + || c_header_token_is_on_directive_line(text, token_start) { + return false + } + mut after := c_header_skip_space_and_comments(text, token_end, text.len) + if after >= text.len { + return false + } + if c_identifier_start(text[after]) { + return true + } + if text[after] == `*` { + for after < text.len && text[after] == `*` { + after = c_header_skip_space_and_comments(text, after + 1, text.len) + } + if after < text.len && text[after] == `)` + && c_header_token_follows_open_parenthesis(text, token_start) { + return c_header_cast_has_operand(text, after) + } + return after < text.len && c_identifier_start(text[after]) + } + if text[after] == `)` && c_header_token_follows_open_parenthesis(text, token_start) { + return c_header_cast_has_operand(text, after) + } + if text[after] != `(` { + return false + } + after = c_header_skip_space_and_comments(text, after + 1, text.len) + return after < text.len && text[after] == `*` +} + +fn c_header_text_has_objective_c_tokens(text string, local_typedefs map[string]bool) bool { + mut i := 0 + mut previous_can_end_expression := false + mut previous_identifier := '' + for i < text.len { + if text[i] in [`"`, `'`] { + quote := text[i] + i++ + for i < text.len { + if text[i] == `\\` && i + 1 < text.len { + i += 2 + continue + } + i++ + if text[i - 1] == quote { + break + } + } + previous_can_end_expression = true + continue + } + if i + 1 < text.len && text[i] == `/` && text[i + 1] == `/` { + i += 2 + for i < text.len && text[i] != `\n` { + i++ + } + continue + } + if i + 1 < text.len && text[i] == `/` && text[i + 1] == `*` { + i += 2 + for i + 1 < text.len && !(text[i] == `*` && text[i + 1] == `/`) { + i++ + } + if i + 1 < text.len { + i += 2 + } else { + i = text.len + } + continue + } + if text[i] == `[` { + if !previous_can_end_expression && c_header_bracket_has_objective_c_message(text, i) { + return true + } + previous_can_end_expression = false + i++ + continue + } + if text[i] == `@` { + if i + 1 < text.len { + next := text[i + 1] + if next in [`"`, `'`, `[`, `{`, `(`] || (next >= `0` && next <= `9`) + || (next in [`+`, `-`] && i + 2 < text.len && text[i + 2] >= `0` + && text[i + 2] <= `9`) { + return true + } + for literal in ['YES', 'NO', 'true', 'false'] { + end := i + 1 + literal.len + if end <= text.len && text[i + 1..end] == literal + && (end == text.len || !c_identifier_continue(text[end])) { + return true + } + } + } + for keyword in ['interface', 'implementation', 'class', 'protocol', 'property', + 'synthesize', 'dynamic', 'selector', 'encode', 'defs', 'compatibility_alias', + 'autoreleasepool', 'synchronized', 'try', 'catch', 'finally', 'throw', 'optional', + 'required', 'public', 'protected', 'private', 'package', 'import', 'available', + 'end'] { + end := i + 1 + keyword.len + if end <= text.len && text[i + 1..end] == keyword + && (end == text.len || !c_identifier_continue(text[end])) { + return true + } + } + previous_can_end_expression = false + i++ + continue + } + if !c_identifier_start(text[i]) { + if text[i] >= `0` && text[i] <= `9` { + previous_can_end_expression = true + } else if text[i] in [`)`, `]`, `}`] { + previous_can_end_expression = true + } else if !text[i].is_space() { + previous_can_end_expression = false + } + i++ + continue + } + start := i + i++ + for i < text.len && c_identifier_continue(text[i]) { + i++ + } + token := text[start..i] + if token in c_objective_c_bridge_qualifiers { + return true + } + if token in c_objective_c_ownership_qualifiers + && !c_header_token_is_on_directive_line(text, start) { + return true + } + if c_header_has_bare_objective_c_type(text, token, start, i, previous_identifier, + local_typedefs) + { + return true + } + if c_header_has_objective_c_qualified_type(text, token, i, local_typedefs) { + return true + } + previous_can_end_expression = token !in ['return', 'throw', 'case'] + previous_identifier = token + } + return false +} + +fn c_header_objective_c_framework_imports(text string) string { + mut imports := []string{} + mut context := []string{} + mut prefix := []string{} + mut in_block_comment := false + for line in text.split_into_lines() { + clean, next_in_block_comment := c_preprocessor_directive_scan_line(line, in_block_comment) + in_block_comment = next_in_block_comment + name := c_directive_name(clean) + if name in ['include', 'import'] { + arg := c_directive_arg(clean) + if c_is_apple_framework_include(arg) { + imports << c_wrap_preserved_nested_directive('#${name} ${arg}', context, prefix) + } + } + c_update_nested_include_context(clean, line, mut context) + c_update_nested_include_prefix(clean, line, mut prefix) + } + return imports.join('\n') +} + +fn c_include_is_late_source(include_arg string) bool { + clean := trimmed_space(include_arg).trim('"\'') + return clean.ends_with('.m') || clean.ends_with('.mm') +} + +fn c_late_source_system_includes(text string) string { + mut includes := []string{} + for line in text.split_into_lines() { + clean := trimmed_space(line) + if c_directive_name(clean) !in ['include', 'import'] { + continue + } + arg := c_directive_arg(clean) + if arg.starts_with('<') && arg.ends_with('>') { + includes << '#include ${arg}' + } + } + return includes.join('\n') +} + +fn c_include_should_remain_in_inlined_text(include_arg string) bool { + clean := trimmed_space(include_arg) + if clean.len == 0 { + return false + } + if clean[0] == `"` { + return true + } + // Macro includes (for example FreeType's `#include FT_FREETYPE_H`) depend + // on definitions earlier in the same header and cannot be lifted into the + // translation-unit preamble. + if clean[0] != `<` { + return true + } + // System headers whose macros have per-OS values (RTLD_*, CHAR_BIT) cannot be + // replaced by inline declarations; keep the include in place inside its #if + // context. + return clean in ['', '', '', ''] +} + +fn c_preserved_system_include_declared_fns(include_arg string) []string { + if include_arg == '' { + return ['dlclose', 'dlerror', 'dlopen', 'dlsym'] + } + if include_arg == '' { + return ['ptrace'] + } + if include_arg in ['', '', ''] { + return [ + 'host_page_size', + 'host_statistics64', + 'mach_absolute_time', + 'mach_host_self', + 'mach_port_deallocate', + 'mach_task_self', + 'mach_timebase_info', + 'task_info', + ] + } + if include_arg in ['', ''] { + return ['X509_free'] + } + if include_arg == '' { + return ['objc_msgSend'] + } + return []string{} +} + +fn c_preserved_system_include_struct_names(include_arg string) []string { + if include_arg == '' { + return ['pollfd'] + } + if include_arg in ['', '', ''] { + return [ + 'host_t', + 'mach_timebase_info_data_t', + 'task_basic_info', + 'task_t', + 'vm_size_t', + 'vm_statistics64_data_t', + ] + } + return []string{} +} + +const c_cache_system_header_declared_fns = { + '_dyld_get_image_header': true + 'getpeername': true + 'host_page_size': true + 'host_statistics64': true + 'mach_absolute_time': true + 'mach_host_self': true + 'mach_port_deallocate': true + 'mach_task_self': true + 'mach_timebase_info': true + 'inet_pton': true + 'ptrace': true + 'recvfrom': true + 'sigaddset': true + 'sigprocmask': true + 'symlink': true + 'task_info': true + 'unsetenv': true +} + +const c_cache_system_header_struct_names = { + 'host_t': true + 'mach_timebase_info_data_t': true + 'sigaction': true + 'task_basic_info': true + 'task_t': true + 'vm_size_t': true + 'vm_statistics64_data_t': true +} + +fn c_stdint_header_text() string { + return '#if !defined(__V_HEADERLESS_STDINT_H) && !defined(_STDINT_H) && !defined(_STDINT_H_) && !defined(_STDINT) && !defined(_STDINT_H_INCLUDED) && !defined(_GCC_STDINT_H) && !defined(_MSC_STDINT_H_) +#define __V_HEADERLESS_STDINT_H +typedef signed char int8_t; +typedef short int16_t; +typedef int int32_t; +typedef long long int64_t; +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; +#ifndef _INTMAX_T +#define _INTMAX_T +#if defined(__APPLE__) && defined(__LP64__) +typedef long intmax_t; +#else +typedef long long intmax_t; +#endif +#endif +#ifndef _UINTMAX_T +#define _UINTMAX_T +#if defined(__APPLE__) && defined(__LP64__) +typedef unsigned long uintmax_t; +#else +typedef unsigned long long uintmax_t; +#endif +#endif +#ifndef INTMAX_MAX +#define INTMAX_MAX 9223372036854775807LL +#endif +#ifndef UINTMAX_MAX +#define UINTMAX_MAX 18446744073709551615ULL +#endif +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767 - 1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647 - 1) +#endif +#ifndef INT64_MIN +#define INT64_MIN (-9223372036854775807LL - 1) +#endif +#ifndef INT8_MAX +#define INT8_MAX 127 +#endif +#ifndef INT16_MAX +#define INT16_MAX 32767 +#endif +#ifndef INT32_MAX +#define INT32_MAX 2147483647 +#endif +#ifndef INT64_MAX +#define INT64_MAX 9223372036854775807LL +#endif +#ifndef UINT8_MAX +#define UINT8_MAX 255U +#endif +#ifndef UINT16_MAX +#define UINT16_MAX 65535U +#endif +#ifndef UINT32_MAX +#define UINT32_MAX 4294967295U +#endif +#ifndef UINT64_MAX +#define UINT64_MAX 18446744073709551615ULL +#endif +#ifndef INT32_C +#define INT32_C(c) c +#endif +#ifndef UINT32_C +#define UINT32_C(c) c ## U +#endif +#ifndef INT64_C +#define INT64_C(c) c ## LL +#endif +#ifndef UINT64_C +#define UINT64_C(c) c ## ULL +#endif +#endif' +} + +fn (mut g FlatGen) collect_inlined_c_structs(text string) { + for line in text.split_into_lines() { + clean := trimmed_space(line) + mut rest := '' + mut requires_body := false + if clean.starts_with('typedef struct ') { + rest = clean['typedef struct '.len..] + } else if clean.starts_with('typedef union ') { + rest = clean['typedef union '.len..] + } else if clean.starts_with('struct ') { + rest = clean['struct '.len..] + requires_body = true + } else if clean.starts_with('union ') { + rest = clean['union '.len..] + requires_body = true + } else { + continue + } + tag := c_header_struct_tag(rest) + if tag.len == 0 { + continue + } + if requires_body { + // Only an actual definition (`struct tm {` or `struct tm` with the + // brace on the next line) may suppress the headerless fallback + // body; `struct tm *fn(...)` declarations and `struct tm;` forward + // declarations leave the type incomplete. + after := trimmed_space(rest[tag.len..]) + if after.len > 0 && after[0] != `{` { + continue + } + } + g.inlined_c_structs[tag] = true + } + for alias in c_typedef_struct_aliases(text) { + g.inlined_c_structs[alias] = true + g.inlined_c_typedef_names[alias] = true + } + for alias in c_typedef_union_aliases(text) { + g.inlined_c_structs[alias] = true + g.inlined_c_typedef_names[alias] = true + } + for alias in c_typedef_enum_aliases(text) { + g.inlined_c_structs[alias] = true + g.inlined_c_typedef_names[alias] = true + } + for alias in c_typedef_plain_aliases(text) { + g.inlined_c_structs[alias] = true + g.inlined_c_typedef_names[alias] = true + } + for alias in c_typedef_fn_aliases(text) { + g.inlined_c_structs[alias] = true + g.inlined_c_typedef_names[alias] = true + } +} + +fn (g &FlatGen) c_source_defines_used_c_type(text string) bool { + mut names := map[string]bool{} + for alias in c_typedef_struct_aliases(text) { + names[alias] = true + } + for alias in c_typedef_union_aliases(text) { + names[alias] = true + } + for alias in c_typedef_enum_aliases(text) { + names[alias] = true + } + for alias in c_typedef_plain_aliases(text) { + names[alias] = true + } + for alias in c_typedef_fn_aliases(text) { + names[alias] = true + } + for line in text.split_into_lines() { + clean := trimmed_space(line) + mut rest := '' + if clean.starts_with('struct ') { + rest = clean['struct '.len..] + } else if clean.starts_with('union ') { + rest = clean['union '.len..] + } else { + continue + } + name := c_header_struct_tag(rest) + if name.len > 0 { + names[name] = true + } + } + for name, _ in names { + full_name := 'C.${name}' + if full_name in g.tc.structs || full_name in g.tc.unions || full_name in g.tc.type_aliases + || full_name in g.tc.enum_names { + return true + } + } + for _, fields in g.tc.structs { + for field in fields { + if c_type_uses_declared_name(field.typ, names) { + return true + } + } + } + for _, fields in g.tc.interface_fields { + for field in fields { + if c_type_uses_declared_name(field.typ, names) { + return true + } + } + } + for _, variants in g.tc.sum_types { + for variant in variants { + mut clean := variant.trim_space() + for clean.starts_with('&') { + clean = clean[1..].trim_space() + } + if clean.starts_with('C.') && clean['C.'.len..] in names { + return true + } + } + } + for _, return_type in g.tc.fn_ret_types { + if c_type_uses_declared_name(return_type, names) { + return true + } + } + for _, param_types in g.tc.fn_param_types { + for param_type in param_types { + if c_type_uses_declared_name(param_type, names) { + return true + } + } + } + return false +} + +fn c_type_uses_declared_name(typ types.Type, names map[string]bool) bool { + match typ { + types.Array { + return c_type_uses_declared_name(typ.elem_type, names) + } + types.ArrayFixed { + return c_type_uses_declared_name(typ.elem_type, names) + } + types.Channel { + return c_type_uses_declared_name(typ.elem_type, names) + } + types.Map { + return c_type_uses_declared_name(typ.key_type, names) + || c_type_uses_declared_name(typ.value_type, names) + } + types.Pointer { + return c_type_uses_declared_name(typ.base_type, names) + } + types.FnType { + for param_type in typ.params { + if c_type_uses_declared_name(param_type, names) { + return true + } + } + return c_type_uses_declared_name(typ.return_type, names) + } + types.OptionType { + return c_type_uses_declared_name(typ.base_type, names) + } + types.ResultType { + return c_type_uses_declared_name(typ.base_type, names) + } + types.Struct, types.Interface, types.Enum, types.SumType { + return typ.name.starts_with('C.') && typ.name['C.'.len..] in names + } + types.Alias { + return (typ.name.starts_with('C.') && typ.name['C.'.len..] in names) + || c_type_uses_declared_name(typ.base_type, names) + } + types.MultiReturn { + for return_type in typ.types { + if c_type_uses_declared_name(return_type, names) { + return true + } + } + } + else {} + } + + return false +} + +// c_typedef_fn_aliases collects the alias names of function typedefs such as +// `typedef int mbedtls_ssl_send_t(void *ctx, ...)` and function-pointer +// typedefs such as `typedef int (*cb_t)(void)`. V sources may declare these as +// opaque `struct C.name {}`, and emitting a `typedef struct name name;` guess +// would clash with the real typedef from the inlined header. +fn c_typedef_fn_aliases(text string) []string { + mut aliases := []string{} + for line in text.split_into_lines() { + clean := trimmed_space(line) + if !clean.starts_with('typedef ') { + continue + } + rest := clean['typedef '.len..] + if rest.starts_with('struct ') || rest.starts_with('union ') || rest.starts_with('enum ') { + continue + } + paren := rest.index_u8(`(`) + if paren <= 0 { + continue + } + mut name := '' + after := trimmed_space(rest[paren + 1..]) + if after.starts_with('*') { + // `typedef int (*name)(args)` + inner := trimmed_space(after[1..]) + end := inner.index_u8(`)`) + if end > 0 { + name = trimmed_space(inner[..end]) + } + } else { + // `typedef int name(args)` - the alias directly precedes `(` + before := trimmed_space(rest[..paren]) + mut start_idx := before.len + for start_idx > 0 && c_ident_char(before[start_idx - 1]) { + start_idx-- + } + if start_idx > 0 && start_idx < before.len { + name = before[start_idx..] + } + } + if name.len > 0 && c_header_struct_tag(name) == name { + aliases << name + } + } + return aliases +} + +fn (mut g FlatGen) collect_inlined_c_fns(text string) { + g.collect_inlined_c_fns_for_cache(text, false, false) +} + +fn (mut g FlatGen) collect_inlined_c_fns_for_cache(text string, cache_omitted bool, cache_native bool) { + mut pending_static := false + mut pending_definition := '' + mut conditional_omissions := []CCacheConditionalOmission{} + mut native_implementation_omitted := false + for line in text.split_into_lines() { + clean := trimmed_space(line) + if clean.len == 0 { + continue + } + if cache_native && clean.starts_with('#') { + native_implementation_omitted = c_cache_native_condition_omitted(clean, mut + conditional_omissions) + } + if pending_definition.len > 0 { + brace := clean.index_u8(`{`) + close := clean.last_index_u8(`)`) + if clean.starts_with('{') || (brace >= 0 && close >= 0 && brace > close) { + g.inlined_c_fns[pending_definition] = true + if cache_omitted { + g.cache_omitted_c_fns[pending_definition] = true + } + if cache_native && native_implementation_omitted { + g.cache_omitted_c_fns[pending_definition] = true + } + pending_definition = '' + continue + } + if clean.ends_with(';') || clean.starts_with('#') { + pending_definition = '' + } + } + if clean.starts_with('static ') { + name := c_header_fn_name(clean) + if name.len > 0 { + g.inlined_c_fns[name] = true + g.inlined_c_static_fns[name] = true + if cache_omitted { + g.cache_omitted_c_fns[name] = true + } + if cache_native && native_implementation_omitted { + g.cache_omitted_c_fns[name] = true + } + pending_static = false + } else { + pending_static = c_static_fn_prefix_can_continue(clean) + } + continue + } + if pending_static { + name := c_header_fn_name(clean) + if name.len > 0 { + g.inlined_c_fns[name] = true + g.inlined_c_static_fns[name] = true + if cache_omitted { + g.cache_omitted_c_fns[name] = true + } + if cache_native && native_implementation_omitted { + g.cache_omitted_c_fns[name] = true + } + pending_static = false + continue + } + if clean.ends_with(';') || clean.contains('{') || clean.starts_with('#') { + pending_static = false + } + } + name := c_header_defined_fn_name(clean) + if name.len == 0 { + continue + } + if clean.contains('{') { + g.inlined_c_fns[name] = true + if cache_omitted { + g.cache_omitted_c_fns[name] = true + } + if cache_native && native_implementation_omitted { + g.cache_omitted_c_fns[name] = true + } + } else { + pending_definition = name + } + } +} + +struct CCacheConditionalOmission { + parent_omitted bool + later_branches_omitted bool +mut: + condition_omitted bool +} + +fn c_cache_native_condition_omitted(directive string, mut stack []CCacheConditionalOmission) bool { + name := c_directive_name(directive) + arg := c_directive_arg(directive) + if name in ['if', 'ifdef', 'ifndef'] { + parent_omitted := stack.len > 0 + && (stack.last().parent_omitted || stack.last().condition_omitted) + macro_name := arg.fields()[0] or { '' } + condition_omitted := if name == 'ifdef' { + c_cache_implementation_macro(macro_name) + } else if name == 'if' { + c_cache_condition_requires_implementation(arg) + } else { + false + } + stack << CCacheConditionalOmission{ + parent_omitted: parent_omitted + later_branches_omitted: (name == 'ifndef' && c_cache_implementation_macro(macro_name)) + || (name == 'if' && c_cache_condition_is_negated_implementation_guard(arg)) + condition_omitted: condition_omitted + } + } else if name in ['else', 'elif'] && stack.len > 0 { + last := stack.len - 1 + stack[last].condition_omitted = stack[last].later_branches_omitted + || (name == 'elif' && c_cache_condition_requires_implementation(arg)) + } else if name == 'endif' && stack.len > 0 { + stack.delete_last() + } + return stack.len > 0 && (stack.last().parent_omitted || stack.last().condition_omitted) +} + +fn c_cache_condition_requires_implementation(condition string) bool { + mut pos := 0 + for pos < condition.len { + relative := condition[pos..].index('defined') or { break } + start := pos + relative + mut before := start + for before > 0 && condition[before - 1] in [` `, `\t`] { + before-- + } + negated := before > 0 && condition[before - 1] == `!` + mut name_start := start + 'defined'.len + for name_start < condition.len && condition[name_start] in [` `, `\t`, `(`] { + name_start++ + } + mut name_end := name_start + for name_end < condition.len && c_ident_char(condition[name_end]) { + name_end++ + } + if !negated && name_end > name_start + && c_cache_implementation_macro(condition[name_start..name_end]) { + return true + } + pos = if name_end > start { name_end } else { start + 'defined'.len } + } + for field in condition.fields() { + if c_cache_implementation_macro(field.trim('()')) { + return true + } + } + return false +} + +fn c_cache_condition_is_negated_implementation_guard(condition string) bool { + mut compact := condition.replace(' ', '').replace('\t', '').replace('\r', '').replace('\n', '') + for compact.len >= 2 && compact[0] == `(` && compact[compact.len - 1] == `)` { + mut depth := 0 + mut wraps_entire_condition := true + for i, c in compact { + if c == `(` { + depth++ + } else if c == `)` { + depth-- + if depth == 0 && i < compact.len - 1 { + wraps_entire_condition = false + break + } + } + } + if !wraps_entire_condition || depth != 0 { + break + } + compact = compact[1..compact.len - 1] + } + if compact.starts_with('!defined(') && compact.ends_with(')') { + name := compact['!defined('.len..compact.len - 1] + return c_cache_condition_implementation_identifier(name) + } + if compact.starts_with('!') { + return c_cache_condition_implementation_identifier(compact[1..]) + } + return false +} + +fn c_cache_condition_implementation_identifier(name string) bool { + if name.len == 0 || !c_cache_implementation_macro(name) { + return false + } + for c in name.bytes() { + if !c_ident_char(c) { + return false + } + } + return true +} + +fn c_cache_implementation_macro(name string) bool { + return name.ends_with('_IMPLEMENTATION') + || (name.starts_with('SOKOL') && name.ends_with('_IMPL')) +} + +// c_strip_comments removes block and line comments so declaration scanning +// cannot be misled by prose in doc comments (e.g. an unbalanced `(`), while +// keeping line numbers stable for multi-line declaration accumulation. +fn c_strip_comments(text string) string { + mut sb := strings.new_builder(text.len) + mut i := 0 + mut in_block := false + for i < text.len { + c := text[i] + if in_block { + if c == `*` && i + 1 < text.len && text[i + 1] == `/` { + in_block = false + i += 2 + continue + } + if c == `\n` { + sb.write_u8(`\n`) + } + i++ + continue + } + if c == `/` && i + 1 < text.len { + if text[i + 1] == `*` { + in_block = true + i += 2 + continue + } + if text[i + 1] == `/` { + for i < text.len && text[i] != `\n` { + i++ + } + continue + } + } + sb.write_u8(c) + i++ + } + return sb.str() +} + +fn (mut g FlatGen) collect_inlined_c_declared_fns(text string) { + // Header declarations often span several lines (one parameter per line); + // accumulate a pending declaration until its terminating `;` so those are + // collected too, not just single-line prototypes. + mut pending := '' + for line in c_strip_comments(text).split_into_lines() { + clean := line.trim_space() + for name in c_macro_declared_fn_names(clean) { + g.inlined_c_declared_fns[name] = true + } + if clean.len > 0 && clean[0] == `#` && c_directive_name(clean) == 'define' { + // Any macro (object- or function-like) named like a `fn C.x` makes + // an emitted extern prototype wrong after preprocessing; the + // header's definition is authoritative. + arg := c_directive_arg(clean) + mut name_end := 0 + for name_end < arg.len && c_ident_char(arg[name_end]) { + name_end++ + } + if name_end > 0 { + g.inlined_c_declared_fns[arg[..name_end]] = true + } + } + if pending.len > 0 { + if clean.len == 0 || clean[0] == `#` || clean.contains('{') || clean.contains('}') + || pending.len > 4096 { + pending = '' + } else { + pending += ' ' + clean + if pending.ends_with(';') { + name := c_header_declared_fn_name(pending) + if name.len > 0 { + g.inlined_c_declared_fns[name] = true + } + pending = '' + } + continue + } + } + name := c_header_declared_fn_name(clean) + if name.len > 0 { + g.inlined_c_declared_fns[name] = true + continue + } + if c_header_declared_fn_start(clean) { + pending = clean + } + } +} + +fn c_macro_declared_fn_names(line string) []string { + if !line.ends_with(')') { + return []string{} + } + open := line.index_u8(`(`) + if open < 0 || open + 1 >= line.len { + return []string{} + } + args := line[open + 1..line.len - 1].split(',') + if args.len == 0 { + return []string{} + } + mut name := '' + mut prefixes := []string{} + if line.starts_with('DECLARE_PEM_') { + name = args[0].trim_space() + if name.starts_with('OSSL_') { + if args.len < 2 { + return []string{} + } + name = args[1].trim_space() + } + prefixes = ['PEM_read_bio_', 'PEM_write_bio_', 'PEM_read_', 'PEM_write_'] + } else if line.starts_with('DECLARE_ASN1_') + && (line.contains('ENCODE_FUNCTIONS') || line.starts_with('DECLARE_ASN1_FUNCTIONS(')) { + name = args.last().trim_space() + prefixes = ['d2i_', 'i2d_'] + } else { + return []string{} + } + if name.len == 0 { + return []string{} + } + for c in name { + if !c_ident_char(c) { + return []string{} + } + } + mut result := []string{cap: prefixes.len} + for prefix in prefixes { + result << prefix + name + } + return result +} + +// c_header_declared_fn_start reports whether a line looks like the opening of +// a multi-line function declaration: it introduces a parameter list that is +// not yet terminated on the same line. +fn c_header_declared_fn_start(line string) bool { + if line.len == 0 || line[0] == `#` || line.ends_with(';') || !line.contains('(') { + return false + } + if line.starts_with('typedef ') || line.contains('=') || line.contains('{') + || line.contains('}') || line.contains(')') { + return false + } + for prefix in ['return ', 'if ', 'if(', 'for ', 'for(', 'while ', 'while(', 'switch ', 'switch(', + 'case ', 'do ', 'else '] { + if line.starts_with(prefix) { + return false + } + } + return c_header_fn_name(line).len > 0 +} + +fn (mut g FlatGen) collect_preserved_c_fns(names []string) { + for name in names { + g.inlined_c_declared_fns[name] = true + } +} + +fn (mut g FlatGen) collect_preserved_c_structs(names []string) { + for name in names { + g.inlined_c_structs[name] = true + } +} + +fn c_static_fn_prefix_can_continue(line string) bool { + return line in ['static', 'static inline', 'static __inline', 'static __inline__'] + || line.starts_with('static inline ') || line.starts_with('static __inline ') + || line.starts_with('static __inline__ ') +} + +fn c_header_struct_tag(rest string) string { + mut end := 0 + for end < rest.len { + c := rest[end] + if (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || (c >= `0` && c <= `9`) || c == `_` { + end++ + continue + } + break + } + return rest[..end] +} + +fn c_header_struct_tag_at(text string, start int) string { + mut end := start + for end < text.len && c_ident_char(text[end]) { + end++ + } + return text[start..end] +} + +fn c_index_u8_after(text string, needle u8, start int) int { + for i in start .. text.len { + if text[i] == needle { + return i + } + } + return -1 +} + +fn c_typedef_struct_aliases(text string) []string { + return c_typedef_aggregate_aliases(text, 'struct') +} + +fn c_typedef_union_aliases(text string) []string { + return c_typedef_aggregate_aliases(text, 'union') +} + +fn c_typedef_enum_aliases(text string) []string { + return c_typedef_aggregate_aliases(text, 'enum') +} + +fn c_typedef_plain_aliases(text string) []string { + mut aliases := []string{} + mut start := 0 + for start < text.len { + idx := text.index_after('typedef', start) or { break } + pos := idx + 'typedef'.len + if (idx > 0 && c_ident_char(text[idx - 1])) || (pos < text.len && c_ident_char(text[pos])) { + start = pos + continue + } + semi_idx := c_index_u8_after(text, `;`, pos) + if semi_idx < 0 { + break + } + declaration := trimmed_space(text[pos..semi_idx]) + start = semi_idx + 1 + if declaration.starts_with('struct ') || declaration.starts_with('union ') + || declaration.starts_with('enum ') || declaration.contains('(') + || declaration.contains('{') { + continue + } + for part in declaration.split(',') { + mut declarator := trimmed_space(part) + bracket := declarator.index_u8(`[`) + if bracket >= 0 { + declarator = trimmed_space(declarator[..bracket]) + } + alias := c_last_ident(declarator) + if alias.len > 0 && c_header_struct_tag(alias) == alias { + aliases << alias + } + } + } + return aliases +} + +fn c_typedef_aggregate_aliases(text string, kind string) []string { + mut aliases := []string{} + prefix := 'typedef ${kind}' + mut start := 0 + for start < text.len { + idx := text.index_after(prefix, start) or { break } + mut pos := idx + prefix.len + if pos < text.len && c_ident_char(text[pos]) { + start = pos + 1 + continue + } + for pos < text.len && text[pos].is_space() { + pos++ + } + mut had_tag := false + if pos < text.len && text[pos] != `{` { + tag := c_header_struct_tag_at(text, pos) + if tag.len == 0 { + start = pos + 1 + continue + } + had_tag = true + pos += tag.len + for pos < text.len && text[pos].is_space() { + pos++ + } + } + if pos >= text.len || text[pos] != `{` { + // Bodyless alias form: `typedef struct tag Alias;` also names the + // alias, so a `struct C.Alias {}` guess typedef must be suppressed. + if had_tag { + semi_idx := c_index_u8_after(text, `;`, pos) + if semi_idx >= 0 { + for alias in c_typedef_declarator_aliases(text[pos..semi_idx]) { + aliases << alias + } + start = semi_idx + 1 + continue + } + } + start = pos + 1 + continue + } + close_idx := c_matching_brace_end(text, pos) + if close_idx < 0 { + break + } + semi_idx := c_index_u8_after(text, `;`, close_idx + 1) + if semi_idx < 0 { + break + } + for alias in c_typedef_declarator_aliases(text[close_idx + 1..semi_idx]) { + aliases << alias + } + start = semi_idx + 1 + } + return aliases +} + +fn c_matching_brace_end(text string, open_idx int) int { + mut depth := 0 + for i in open_idx .. text.len { + if text[i] == `{` { + depth++ + } else if text[i] == `}` { + depth-- + if depth == 0 { + return i + } + } + } + return -1 +} + +fn c_typedef_declarator_aliases(decl string) []string { + mut aliases := []string{} + for part in decl.split(',') { + alias := c_last_ident(part) + if alias.len > 0 { + aliases << alias + } + } + return aliases +} + +fn c_last_ident(text string) string { + mut end := text.len + for end > 0 && !c_ident_char(text[end - 1]) { + end-- + } + mut start := end + for start > 0 && c_ident_char(text[start - 1]) { + start-- + } + if start == end { + return '' + } + return text[start..end] +} + +fn c_header_fn_name(line string) string { + paren := line.index_u8(`(`) + if paren < 0 { + return '' + } + mut end := paren + for end > 0 && line[end - 1].is_space() { + end-- + } + mut start := end + for start > 0 && c_ident_char(line[start - 1]) { + start-- + } + if start == end { + return '' + } + name := line[start..end] + if name in ['if', 'for', 'while', 'switch'] { + return '' + } + return name +} + +fn c_header_declared_fn_name(line string) string { + if line.len == 0 || line[0] == `#` || !line.ends_with(';') || !line.contains('(') { + return '' + } + if line.starts_with('typedef ') || line.contains('=') || line.contains('{') + || line.contains('}') { + return '' + } + if macro_name := c_header_macro_wrapped_declared_fn_name(line) { + return macro_name + } + // Reject function-pointer variable declarations (`int (*fp)(void);`) and + // functions returning function pointers, where the identifier before the + // first `(` is not the declared name. A `(*` later in the parameter list + // is fine - fn-pointer parameters do not change where the name sits. + paren_idx := line.index_u8(`(`) + mut after := paren_idx + 1 + for after < line.len && line[after].is_space() { + after++ + } + if after < line.len && line[after] == `*` { + return '' + } + for prefix in ['return ', 'if ', 'if(', 'for ', 'for(', 'while ', 'while(', 'switch ', 'switch(', + 'case ', 'do ', 'else '] { + if line.starts_with(prefix) { + return '' + } + } + paren := line.index_u8(`(`) + mut end := paren + for end > 0 && line[end - 1].is_space() { + end-- + } + mut start := end + for start > 0 && c_ident_char(line[start - 1]) { + start-- + } + if start == 0 { + return '' + } + return c_header_fn_name(line) +} + +fn c_header_macro_wrapped_declared_fn_name(line string) ?string { + open := line.index_u8(`(`) + if open <= 0 { + return none + } + macro_name := line[..open].trim_space() + if macro_name.len == 0 || macro_name.contains(' ') || macro_name.contains('\t') { + return none + } + for c in macro_name.bytes() { + if !((c >= `A` && c <= `Z`) || (c >= `0` && c <= `9`) || c == `_`) { + return none + } + } + close := typeof_display_type_name_matching_paren(line, open) + if close < 0 || close + 1 >= line.len { + return none + } + declarator := line[close + 1..].trim_space() + if !declarator.ends_with(';') || !declarator.contains('(') { + return none + } + name := c_header_fn_name(declarator) + if name.len == 0 { + return none + } + return name +} + +fn c_header_defined_fn_name(line string) string { + if line.len == 0 || line[0] == `#` || line.ends_with(';') || !line.contains('(') + || line.contains('=') { + return '' + } + paren_idx := line.index_u8(`(`) + mut after := paren_idx + 1 + for after < line.len && line[after].is_space() { + after++ + } + // Reject a function-pointer variable/return declarator (`int (*fp)(...)`), + // while allowing ordinary functions that take a function-pointer parameter. + if after < line.len && line[after] == `*` { + return '' + } + for prefix in ['typedef ', 'enum ', 'extern ', 'return ', 'if ', 'if(', 'for ', 'for(', 'while ', + 'while(', 'switch ', 'switch(', 'case ', 'do ', 'else '] { + if line.starts_with(prefix) { + return '' + } + } + return c_header_fn_name(line) +} + +fn c_ident_char(ch u8) bool { + return (ch >= `a` && ch <= `z`) || (ch >= `A` && ch <= `Z`) + || (ch >= `0` && ch <= `9`) || ch == `_` +} + +fn c_include_file_path(include_arg string, vroot string, source_file string) string { + clean := trimmed_space(include_arg) + if clean.len < 2 { + return '' + } + if clean[0] == `<` { + return '' + } + mut path := '' + if clean[0] == `"` && clean[clean.len - 1] == `"` { + path = clean[1..clean.len - 1] + } else { + path = clean + } + path = c_resolve_pseudo_paths(path, vroot, source_file) + if path.len == 0 || os.is_abs_path(path) { + return path + } + if source_file.len == 0 { + return path + } + return os.join_path_single(os.dir(source_file), path) +} + +fn c_include_arg_is_literal(include_arg string) bool { + clean := trimmed_space(include_arg) + if clean.len < 2 { + return false + } + return (clean[0] == `"` && clean[clean.len - 1] == `"`) + || (clean[0] == `<` && clean[clean.len - 1] == `>`) +} + +fn c_include_arg_is_source_file(include_arg string) bool { + clean := trimmed_space(include_arg) + if clean.len < 3 || clean[0] != `"` || clean[clean.len - 1] != `"` { + return false + } + path := clean[1..clean.len - 1] + return path.ends_with('.c') || path.ends_with('.m') || path.ends_with('.mm') +} + +fn c_include_file_paths(include_arg string, vroot string, source_file string, include_dirs []string) []string { + clean := trimmed_space(include_arg) + if clean.len < 2 { + return []string{} + } + mut raw_path := clean + mut search_source_dir := true + if clean[0] == `"` && clean[clean.len - 1] == `"` { + raw_path = clean[1..clean.len - 1] + } else if clean[0] == `<` && clean[clean.len - 1] == `>` { + raw_path = clean[1..clean.len - 1] + search_source_dir = false + } + mut paths := []string{} + if search_source_dir { + first := c_include_file_path(include_arg, vroot, source_file) + if first.len > 0 { + paths << first + } + } + resolved_path := c_resolve_pseudo_paths(raw_path, vroot, source_file) + if os.is_abs_path(resolved_path) { + if resolved_path !in paths { + paths << resolved_path + } + return paths + } + for dir in include_dirs { + if dir.len == 0 { + continue + } + path := os.join_path_single(dir, resolved_path) + if path !in paths { + paths << path + } + } + return paths +} + +fn (mut g FlatGen) add_c_directive(module_name string, text string, before_import bool) { + g.add_c_directive_at(module_name, text, before_import, false) +} + +fn (mut g FlatGen) add_c_directive_at(module_name string, text string, before_import bool, late bool) { + if text.len == 0 { + return + } + g.c_directives << CDirective{ + module: module_name + text: text + before_import: before_import + late: late + } +} + +fn (mut g FlatGen) add_native_source_context_directive(module_name string, text string, before_import bool) { + if text.len == 0 { + return + } + mut directives := g.native_source_contexts[module_name] or { []NativeSourceContextDirective{} } + directives << NativeSourceContextDirective{ + text: text + before_import: before_import + } + g.native_source_contexts[module_name] = directives +} + +fn (g &FlatGen) ordered_native_source_context(module_name string, local_context []NativeSourceContextDirective) []string { + mut result := []string{} + mut visiting := map[string]bool{} + mut visited := map[string]bool{} + g.visit_native_source_context_module(module_name, module_name, local_context, mut visiting, mut + visited, mut result) + return result +} + +fn (g &FlatGen) native_source_context_has_macro_inputs(module_name string) bool { + mut directives_by_module := map[string][]CDirective{} + for directive in g.c_directives { + mut directives := directives_by_module[directive.module] or { []CDirective{} } + directives << directive + directives_by_module[directive.module] = directives + } + // Keep walking through modules without directives so transitive imports that do + // provide source includes remain part of the macro-input context. + for imported_module, _ in g.module_imports { + if imported_module !in directives_by_module { + directives_by_module[imported_module] = []CDirective{} + } + } + mut visiting := map[string]bool{} + mut visited := map[string]bool{} + mut directives := []string{} + g.visit_c_directive_module(module_name, directives_by_module, mut visiting, mut visited, mut + directives) + return c_native_source_context_state(directives, g.c_flags, g.c99_mode, g.target, false).source_macros_possible +} + +fn (g &FlatGen) visit_native_source_context_module(module_name string, root_module string, root_context []NativeSourceContextDirective, mut visiting map[string]bool, mut visited map[string]bool, mut result []string) { + if module_name in visited || module_name in visiting { + return + } + visiting[module_name] = true + directives := if module_name == root_module { + root_context + } else { + g.native_source_contexts[module_name] or { []NativeSourceContextDirective{} } + } + for directive in directives { + if directive.before_import { + result << directive.text + } + } + for dependency in g.module_imports[module_name] or { []string{} } { + if dependency in g.native_source_contexts || dependency in g.module_imports { + g.visit_native_source_context_module(dependency, root_module, root_context, mut + visiting, mut visited, mut result) + } + } + visiting.delete(module_name) + visited[module_name] = true + for directive in directives { + if !directive.before_import { + result << directive.text + } + } +} + +fn c_native_source_context_header_include(include_arg string, vroot string, source_file string, include_dirs []string) string { + clean := trimmed_space(include_arg) + if clean.len > 1 && clean[0] == `"` { + for path in c_include_file_paths(clean, vroot, source_file, include_dirs) { + if os.is_file(path) { + return c_native_source_context_include(path) + } + } + } + return '#include ${clean}' +} + +fn c_native_source_context_include(path string) string { + clean := os.real_path(path).replace('\\', '/').replace('"', '\\"') + return '#include "${clean}"' +} + +fn c_native_source_context_depth(directives []string) int { + mut depth := 0 + for directive in directives { + for line in directive.split_into_lines() { + name := c_directive_name(trimmed_space(line)) + if name in ['if', 'ifdef', 'ifndef'] { + depth++ + } else if name == 'endif' && depth > 0 { + depth-- + } + } + } + return depth +} + +fn c_preprocessor_invalidate_macro_state(mut defined map[string]bool, mut undefined map[string]bool, mut uncertain map[string]bool) { + for name in defined.keys() { + uncertain[name] = true + } + for name in undefined.keys() { + uncertain[name] = true + } + defined.clear() + undefined.clear() +} + +fn c_effective_strict_iso_mode(flags []string, c99_mode bool) bool { + mut strict_iso_mode := c99_mode + mut expect_standard := false + for flag in flags { + clean := trimmed_space(flag) + if expect_standard { + strict_iso_mode = !clean.to_lower().starts_with('gnu') + expect_standard = false + continue + } + if clean in ['-std', '--std'] { + expect_standard = true + continue + } + if clean == '-ansi' { + strict_iso_mode = true + continue + } + for prefix in ['-std=', '--std='] { + if clean.starts_with(prefix) && clean.len > prefix.len { + standard := clean[prefix.len..].to_lower() + strict_iso_mode = !standard.starts_with('gnu') + break + } + } + } + return strict_iso_mode +} + +struct CNativeSourceContextState { + definitely_inactive bool + source_macros_possible bool +} + +fn c_native_source_context_definitely_inactive(directives []string, flags []string, c99_mode bool, target pref.Target, source_macros_possible bool) bool { + return c_native_source_context_state(directives, flags, c99_mode, target, + source_macros_possible).definitely_inactive +} + +fn c_native_source_context_state(directives []string, flags []string, c99_mode bool, target pref.Target, source_macros_possible bool) CNativeSourceContextState { + mut defined := map[string]bool{} + mut undefined := map[string]bool{} + mut uncertain := map[string]bool{} + mut external_macros_possible := source_macros_possible || c_forced_include_inputs(flags).len > 0 + mut active_source_include := false + strict_iso_mode := c_effective_strict_iso_mode(flags, c99_mode) + mut i := 0 + for i < flags.len { + clean := trimmed_space(flags[i]) + mut definition := '' + mut is_undef := false + if clean == '-D' && i + 1 < flags.len { + definition = trimmed_space(flags[i + 1]) + i++ + } else if clean.starts_with('-D') { + definition = clean[2..] + } else if clean == '-U' && i + 1 < flags.len { + definition = trimmed_space(flags[i + 1]) + is_undef = true + i++ + } else if clean.starts_with('-U') { + definition = clean[2..] + is_undef = true + } + name := definition.all_before('=').trim_space() + if name.len > 0 { + if is_undef { + defined.delete(name) + undefined[name] = true + } else { + undefined.delete(name) + defined[name] = true + } + } + i++ + } + if external_macros_possible { + c_preprocessor_invalidate_macro_state(mut defined, mut undefined, mut uncertain) + } + mut condition_known := []bool{} + mut condition_active := []bool{} + // Keep the cumulative state of each branch chain so `#elif` and `#else` + // can distinguish an inactive condition from an earlier branch that already ran. + mut condition_taken_known := []bool{} + mut condition_taken := []bool{} + for directive in directives { + for line in directive.split_into_lines() { + clean := trimmed_space(line) + name := c_directive_name(clean) + if name in ['ifdef', 'ifndef'] { + macro_name := c_directive_arg(clean).fields()[0] or { '' } + known, mut active := c_preprocessor_macro_state(macro_name, defined, undefined, + uncertain, strict_iso_mode, target) + if name == 'ifndef' { + active = !active + } + condition_known << known + condition_active << (if known { active } else { true }) + condition_taken_known << known + condition_taken << (if known { active } else { true }) + continue + } + if name == 'if' { + arg := c_directive_arg(clean) + known, active := c_preprocessor_condition_state(arg, defined, undefined, uncertain, + external_macros_possible, strict_iso_mode, target) + condition_known << known + condition_active << (if known { active } else { true }) + condition_taken_known << known + condition_taken << (if known { active } else { true }) + continue + } + if name == 'elif' && condition_known.len > 0 { + last := condition_known.len - 1 + prior_known := condition_taken_known[last] + prior_taken := condition_taken[last] + known, active := c_preprocessor_condition_state(c_directive_arg(clean), defined, + undefined, uncertain, external_macros_possible, strict_iso_mode, target) + if (prior_known && prior_taken) || (known && !active) { + condition_known[last] = true + condition_active[last] = false + } else if prior_known && known { + condition_known[last] = true + condition_active[last] = true + } else { + condition_known[last] = false + condition_active[last] = true + } + if (prior_known && prior_taken) || (known && active) { + condition_taken_known[last] = true + condition_taken[last] = true + } else if prior_known && known { + condition_taken_known[last] = true + condition_taken[last] = false + } else { + condition_taken_known[last] = false + condition_taken[last] = true + } + continue + } + if name == 'else' && condition_known.len > 0 { + last := condition_known.len - 1 + condition_known[last] = condition_taken_known[last] + condition_active[last] = if condition_taken_known[last] { + !condition_taken[last] + } else { + true + } + condition_taken_known[last] = true + condition_taken[last] = true + continue + } + if name == 'endif' && condition_known.len > 0 { + condition_known.delete_last() + condition_active.delete_last() + condition_taken_known.delete_last() + condition_taken.delete_last() + continue + } + if name in ['include', 'insert'] { + mut possibly_active := true + for depth in 0 .. condition_known.len { + if condition_known[depth] && !condition_active[depth] { + possibly_active = false + break + } + } + if possibly_active { + if name == 'include' && c_include_arg_is_source_file(c_directive_arg(clean)) { + active_source_include = true + } + c_preprocessor_invalidate_macro_state(mut defined, mut undefined, mut uncertain) + external_macros_possible = true + } + continue + } + if name !in ['define', 'undef'] { + continue + } + parts := c_directive_arg(clean).fields() + if parts.len == 0 { + continue + } + macro_name := parts[0].all_before('(') + mut definitely_active := true + mut possibly_active := true + for depth in 0 .. condition_known.len { + if condition_known[depth] && !condition_active[depth] { + definitely_active = false + possibly_active = false + break + } + if !condition_known[depth] { + definitely_active = false + } + } + if definitely_active { + uncertain.delete(macro_name) + if name == 'define' { + undefined.delete(macro_name) + defined[macro_name] = true + } else { + defined.delete(macro_name) + undefined[macro_name] = true + } + } else if possibly_active { + defined.delete(macro_name) + undefined.delete(macro_name) + uncertain[macro_name] = true + } + } + } + for depth in 0 .. condition_known.len { + if condition_known[depth] && !condition_active[depth] { + return CNativeSourceContextState{ + definitely_inactive: true + source_macros_possible: active_source_include + } + } + } + return CNativeSourceContextState{ + source_macros_possible: active_source_include + } +} + +fn c_preprocessor_macro_state(name string, defined map[string]bool, undefined map[string]bool, uncertain map[string]bool, strict_iso_mode bool, target pref.Target) (bool, bool) { + if name in defined { + return true, true + } + if name in undefined { + return true, false + } + if name in uncertain { + return false, true + } + if name == '__linux__' { + return true, target.os in ['linux', 'android', 'termux'] + } + if name in ['__linux', 'linux'] { + if strict_iso_mode { + return false, true + } + return true, target.os in ['linux', 'android', 'termux'] + } + if name == 'unix' { + if strict_iso_mode { + return false, true + } + if target.os in ['linux', 'android', 'termux'] { + return true, true + } + if target.os in ['windows', 'macos', 'ios'] { + return true, false + } + return false, true + } + match name { + '__APPLE__', '__MACH__' { return true, target.os in ['macos', 'ios'] } + '_WIN32' { return true, target.os == 'windows' } + '_WIN64' { return true, target.os == 'windows' && target.pointer_bits == 64 } + '__FreeBSD__' { return true, target.os == 'freebsd' } + '__OpenBSD__' { return true, target.os == 'openbsd' } + '__NetBSD__' { return true, target.os == 'netbsd' } + else {} + } + + if name.starts_with('_') { + return false, true + } + return true, false +} + +fn c_preprocessor_bare_macro_state(name string, defined map[string]bool, undefined map[string]bool, uncertain map[string]bool, external_macros_possible bool, strict_iso_mode bool, target pref.Target) (bool, bool) { + if name in undefined { + return true, false + } + // Definitions collected from directives and -D flags do not retain their values. + // They are sufficient for defined(NAME), but not for evaluating #if NAME. + if name in defined || name in uncertain { + return false, true + } + if external_macros_possible && !name.starts_with('_') { + return false, true + } + return c_preprocessor_macro_state(name, defined, undefined, uncertain, strict_iso_mode, target) +} + +fn c_preprocessor_condition_state(raw string, defined map[string]bool, undefined map[string]bool, uncertain map[string]bool, external_macros_possible bool, strict_iso_mode bool, target pref.Target) (bool, bool) { + mut clean := raw.trim_space() + mut negated := false + if clean.starts_with('!') { + negated = true + clean = clean[1..].trim_space() + } + if clean in ['0', '1'] { + mut active := clean == '1' + if negated { + active = !active + } + return true, active + } + if clean.len > 0 && c_identifier_start(clean[0]) && c_header_struct_tag(clean) == clean { + known, mut active := c_preprocessor_bare_macro_state(clean, defined, undefined, uncertain, + external_macros_possible, strict_iso_mode, target) + if !known { + return false, true + } + if negated { + active = !active + } + return known, active + } + if !clean.starts_with('defined') || (clean.len > 'defined'.len && clean['defined'.len] != `(` + && !clean['defined'.len].is_space()) { + return false, true + } + rest := clean['defined'.len..].trim_space() + mut macro_name := '' + if rest.starts_with('(') { + close := rest.index_u8(`)`) + if close < 0 { + return false, true + } + if close + 1 < rest.len && rest[close + 1..].trim_space().len > 0 { + return false, true + } + macro_name = rest[1..close].trim_space() + } else { + parts := rest.fields() + if parts.len != 1 { + return false, true + } + macro_name = parts[0] + } + if macro_name.len == 0 || c_header_struct_tag(macro_name) != macro_name { + return false, true + } + known, mut active := c_preprocessor_macro_state(macro_name, defined, undefined, uncertain, + strict_iso_mode, target) + if negated { + active = !active + } + return known, active +} + +fn (mut g FlatGen) materialize_objective_cpp_sources() { + for request in g.objective_cpp_source_requests { + context_directives := g.ordered_native_source_context(request.module, request.local_context) + if context_directives.len > 0 + && c_native_source_context_definitely_inactive(context_directives, g.c_flags, g.c99_mode, g.target, request.source_macros_possible) { + continue + } + if context_directives.len > 0 + || c_source_include_has_preprocessor_context(context_directives) { + g.add_native_source_context_wrapper(request.source_path, context_directives) + } else if request.source_path !in g.c_flags { + g.c_flags << request.source_path + } + } +} + +fn (mut g FlatGen) add_native_source_context_wrapper(source_path string, directives []string) { + if g.output_path.len == 0 { + g.output_error = 'cannot materialize native source directive context without an output path' + return + } + mut wrapper_lines := directives.clone() + wrapper_lines << c_native_source_context_include(source_path) + for _ in 0 .. c_native_source_context_depth(directives) { + wrapper_lines << '#endif' + } + extension := source_path.all_after_last('.') + wrapper_path := '${g.output_path}.v3_native_source_context_${g.native_source_wrapper_index}.${extension}' + g.native_source_wrapper_index++ + os.write_file(wrapper_path, wrapper_lines.join('\n') + '\n') or { + g.output_error = err.msg() + return + } + g.c_flags << wrapper_path +} + +fn c_source_include_has_preprocessor_context(directives []string) bool { + mut conditional_depth := 0 + mut active_macros := map[string]bool{} + for directive in directives { + lines := directive.split_into_lines() + header_guard := c_header_guard_name_from_lines(lines) + for line in lines { + clean := trimmed_space(line) + name := c_directive_name(clean) + if name in ['if', 'ifdef', 'ifndef'] { + conditional_depth++ + } else if name == 'endif' && conditional_depth > 0 { + conditional_depth-- + } + if name in ['define', 'undef'] { + parts := c_directive_arg(clean).fields() + if parts.len > 0 { + macro_name := parts[0].all_before('(') + if name == 'define' { + if macro_name != header_guard { + active_macros[macro_name] = true + } + } else { + active_macros.delete(macro_name) + } + } + } + } + } + return conditional_depth > 0 || active_macros.len > 0 +} + +fn c_preprocessor_directive_line(name string, raw string) string { + clean := trimmed_space(raw) + if clean.len == 0 { + return '#${name}' + } + return '#${name} ${clean}' +} + +// note_compiler_source_file supports note compiler source file handling for FlatGen. +fn (mut g FlatGen) note_compiler_source_file(path string) { + if g.compiler_vroot.len > 0 || path.len == 0 { + return + } + mut full_path := path + if !os.is_abs_path(full_path) { + full_path = os.abs_path(full_path) + } + full_path = os.real_path(full_path) + normalized := full_path.replace('\\', '/') + suffix := '/cmd/v/v.v' + if normalized.ends_with(suffix) { + g.compiler_vroot = normalized[..normalized.len - suffix.len] + return + } + vlib_idx := normalized.index('/vlib/') or { return } + if vlib_idx > 0 { + g.compiler_vroot = normalized[..vlib_idx] + } +} + +// collect_const_init_order_from_files converts collect const init order from files data for c. +fn (mut g FlatGen) collect_const_init_order_from_files() { + mut seen := map[string]bool{} + g.const_init_order = []string{} + for node_idx in g.tc.top_level_idx { + node := g.a.nodes[node_idx] + if node_kind_id(node) != 77 || node.children_count == 0 { + continue + } + mut cur_module := 'main' + for i in 0 .. node.children_count { + child := g.a.child_node(&node, i) + kind_id := node_kind_id(child) + if kind_id == 73 { + cur_module = child.value + continue + } + if kind_id != 65 { + continue + } + for j in 0 .. child.children_count { + field := g.a.child_node(child, j) + if node_kind_id(field) != 66 || field.children_count == 0 { + continue + } + qname := g.const_storage_name(cur_module, field.value) + if qname in g.const_vals && !seen[qname] { + seen[qname] = true + g.const_init_order << qname + } + } + } + } +} + +// module_const_init_order returns the dependency-safe constant initialization +// order that declaration-only module headers must preserve. +pub fn module_const_init_order(a &flat.FlatAst, tc &types.TypeChecker) []string { + mut g := FlatGen.new() + g.a = a + g.tc = tc + old_module := tc.cur_module + old_file := tc.cur_file + defer { + g.tc.cur_module = old_module + g.tc.cur_file = old_file + } + mut cur_module := 'main' + mut cur_file := '' + for node_idx, node in a.nodes { + match node.kind { + .file { + cur_file = node.value + cur_module = 'main' + } + .module_decl { + cur_module = node.value + } + .fn_decl { + g.register_fn_decl_node(node.value, cur_module, flat.NodeId(node_idx)) + } + .const_decl { + for i in 0 .. node.children_count { + field := a.child_node(&node, i) + if field.kind != .const_field || field.children_count == 0 { + continue + } + qname := g.const_storage_name(cur_module, field.value) + g.const_vals[qname] = a.child(field, 0) + g.const_modules[qname] = cur_module + g.const_files[qname] = cur_file + if cur_module in ['', 'main', 'builtin'] && field.value !in g.const_vals { + g.const_vals[field.value] = a.child(field, 0) + g.const_modules[field.value] = cur_module + g.const_files[field.value] = cur_file + } + } + } + .import_decl { + if node.typ.len > 0 && node.value.len > 0 { + g.modules[node.typ] = node.value + } + if cur_module.len > 0 && node.value.len > 0 + && node.value !in g.module_imports[cur_module] { + g.module_imports[cur_module] << node.value + } + } + else {} + } + } + g.collect_const_init_order_from_files() + return g.const_emission_order() +} + +// ordered_module_init_fns supports ordered module init fns handling for FlatGen. +fn (g &FlatGen) ordered_module_init_fns() []string { + module_to_init := g.module_init_fn_map() + mut result := []string{} + mut visiting := map[string]bool{} + mut visited := map[string]bool{} + for init_fn in g.module_init_fns { + mod := g.module_init_fn_modules[init_fn] or { '' } + g.visit_module_init(mod, module_to_init, mut visiting, mut visited, mut result) + } + return result +} + +fn (g &FlatGen) module_init_fn_map() map[string]string { + mut module_to_init := map[string]string{} + for init_fn in g.module_init_fns { + mod := g.module_init_fn_modules[init_fn] or { '' } + module_to_init[mod] = init_fn + } + return module_to_init +} + +fn (g &FlatGen) ordered_module_cleanup_fns() []string { + module_to_cleanup := g.module_cleanup_fn_map() + mut result := []string{} + mut visiting := map[string]bool{} + mut visited := map[string]bool{} + for cleanup_fn in g.module_cleanup_fns { + mod := g.module_cleanup_fn_modules[cleanup_fn] or { '' } + g.visit_module_init(mod, module_to_cleanup, mut visiting, mut visited, mut result) + } + return result +} + +fn (g &FlatGen) module_cleanup_fn_map() map[string]string { + mut module_to_cleanup := map[string]string{} + for cleanup_fn in g.module_cleanup_fns { + mod := g.module_cleanup_fn_modules[cleanup_fn] or { '' } + module_to_cleanup[mod] = cleanup_fn + } + return module_to_cleanup +} + +fn (g &FlatGen) ordered_startup_modules(module_to_init map[string]string) []string { + mut module_order := []string{} + for init_fn in g.module_init_fns { + mod := g.module_init_fn_modules[init_fn] or { '' } + if mod !in module_order { + module_order << mod + } + } + for mod in g.const_runtime_init_modules { + if mod !in module_order { + module_order << mod + } + } + for mod in g.runtime_init_modules { + if mod !in module_order { + module_order << mod + } + } + mut startup_modules := map[string]bool{} + for mod in module_order { + startup_modules[mod] = true + } + for mod, _ in module_to_init { + startup_modules[mod] = true + } + mut result := []string{} + mut visiting := map[string]bool{} + mut visited := map[string]bool{} + for mod in module_order { + g.visit_startup_module(mod, startup_modules, mut visiting, mut visited, mut result) + } + return result +} + +fn (g &FlatGen) visit_startup_module(mod string, startup_modules map[string]bool, mut visiting map[string]bool, mut visited map[string]bool, mut result []string) { + if mod in visited || mod in visiting { + return + } + visiting[mod] = true + for dep in g.module_imports[mod] or { []string{} } { + dep_module := g.startup_dependency_module(dep, startup_modules) + g.visit_startup_module(dep_module, startup_modules, mut visiting, mut visited, mut result) + } + visiting.delete(mod) + visited[mod] = true + if mod in startup_modules { + result << mod + } +} + +fn (g &FlatGen) startup_dependency_module(dep string, startup_modules map[string]bool) string { + if dep in startup_modules || dep in g.module_imports { + return dep + } + short := startup_module_key(dep) + if short in startup_modules || short in g.module_imports { + return short + } + return dep +} + +fn (mut g FlatGen) emit_runtime_inits_for_module(mod string, mut emitted_const []bool, mut emitted_runtime []bool) { + for i, ri in g.const_runtime_inits { + if !emitted_const[i] && i < g.const_runtime_init_modules.len + && g.const_runtime_init_modules[i] == mod { + g.writeln(ri) + emitted_const[i] = true + } + } + for i, ri in g.runtime_inits { + if !emitted_runtime[i] && i < g.runtime_init_modules.len && g.runtime_init_modules[i] == mod { + g.writeln(ri) + emitted_runtime[i] = true + } + } +} + +fn (mut g FlatGen) emit_remaining_runtime_inits(mut emitted_const []bool, mut emitted_runtime []bool) { + for i, ri in g.const_runtime_inits { + if !emitted_const[i] { + g.writeln(ri) + emitted_const[i] = true + } + } + for i, ri in g.runtime_inits { + if !emitted_runtime[i] { + g.writeln(ri) + emitted_runtime[i] = true + } + } +} + +fn (mut g FlatGen) queue_const_runtime_init(line string) { + g.const_runtime_inits << line + g.const_runtime_init_modules << g.tc.cur_module +} + +fn (mut g FlatGen) queue_runtime_init(line string) { + g.runtime_inits << line + g.runtime_init_modules << g.tc.cur_module +} + +fn (mut g FlatGen) queue_runtime_init_for_module(line string, init_module string) { + g.runtime_inits << line + g.runtime_init_modules << init_module +} + +// visit_module_init updates visit module init state for FlatGen. +fn (g &FlatGen) visit_module_init(mod string, module_to_init map[string]string, mut visiting map[string]bool, mut visited map[string]bool, mut result []string) { + if mod in visited || mod in visiting { + return + } + visiting[mod] = true + for dep in g.module_imports[mod] or { []string{} } { + dep_module := if dep in module_to_init || dep in g.module_imports { + dep + } else { + startup_module_key(dep) + } + g.visit_module_init(dep_module, module_to_init, mut visiting, mut visited, mut result) + } + visiting.delete(mod) + visited[mod] = true + if init_fn := module_to_init[mod] { + result << init_fn + } +} + +fn (mut g FlatGen) ordered_c_directives(late bool) []string { + mut directives_by_module := map[string][]CDirective{} + mut module_order := []string{} + for directive in g.c_directives { + if directive.late != late { + continue + } + if directive.module !in directives_by_module { + directives_by_module[directive.module] = []CDirective{} + module_order << directive.module + } + directives_by_module[directive.module] << directive + } + // Keep traversing through modules without directives so directives from a + // transitive import are still emitted before an importer's after-import body. + for imported_module, _ in g.module_imports { + if imported_module !in directives_by_module { + directives_by_module[imported_module] = []CDirective{} + } + } + mut result := []string{} + mut visiting := map[string]bool{} + mut visited := map[string]bool{} + for mod in module_order { + g.visit_c_directive_module(mod, directives_by_module, mut visiting, mut visited, mut result) + } + ordered := dedupe_top_level_c_includes(result) + if g.c_directives_use_system_libc() { + return ordered + } + mut headerless := []string{cap: ordered.len} + for directive in ordered { + filtered := c_without_headerless_pthread_include(directive) + if filtered.trim_space().len > 0 { + headerless << filtered + } + } + return headerless +} + +fn c_without_headerless_pthread_include(directive string) string { + if !directive.contains('pthread.h') { + return directive + } + mut lines := []string{} + for line in directive.split_into_lines() { + clean := trimmed_space(line) + if c_directive_name(clean) in ['include', 'import'] + && c_directive_arg(clean) == '' { + continue + } + lines << line + } + return lines.join('\n') +} + +fn (mut g FlatGen) emit_c_directives(late bool) { + mut emitted := false + directives := g.ordered_c_directives(late) + if late { + for directive in directives { + if c_contains_preserved_system_include_directive(directive) { + continue + } + g.writeln(directive) + emitted = true + } + if emitted { + g.writeln('') + } + return + } + source_emission := c_source_directive_emission(directives, g.early_c_source_directives) + for i, directive in directives { + if i in source_emission.skip_early + || c_contains_preserved_system_include_directive(directive) + || (c_is_late_source_include_directive(directive) + && directive !in g.early_c_source_directives) { + continue + } + g.writeln(directive) + emitted = true + } + if emitted { + g.writeln('') + } +} + +fn (mut g FlatGen) emit_c_source_directives() { + mut emitted := false + directives := g.ordered_c_directives(false) + source_emission := c_source_directive_emission(directives, g.early_c_source_directives) + for i, directive in directives { + if i !in source_emission.emit_late { + continue + } + g.writeln(directive) + emitted = true + } + if emitted { + g.writeln('') + } +} + +struct CSourceDirectiveEmission { + skip_early map[int]bool + emit_late map[int]bool +} + +fn c_source_directive_emission(directives []string, early_source_directives map[string]bool) CSourceDirectiveEmission { + mut skip_early := map[int]bool{} + mut emit_late := map[int]bool{} + mut active_macro_contexts := map[string][]int{} + mut active_pragma_pushes := map[string][]int{} + mut condition_known := []bool{} + mut condition_active := []bool{} + for i, directive in directives { + clean := trimmed_space(directive) + // Inlined headers are stored as one multi-line directive entry. Their first + // line may open an internal conditional that is closed later in the same + // entry, so it must not affect the surrounding top-level context. + directive_name := if clean.contains('\n') { '' } else { c_directive_name(clean) } + if directive_name in ['if', 'ifdef', 'ifndef'] { + arg := c_directive_arg(clean) + known := directive_name == 'if' && arg in ['0', '1'] + condition_known << known + condition_active << (if known { arg == '1' } else { true }) + } else if directive_name in ['else', 'elif'] && condition_known.len > 0 { + last := condition_known.len - 1 + if directive_name == 'else' && condition_known[last] { + condition_active[last] = !condition_active[last] + } else { + condition_known[last] = false + condition_active[last] = true + } + } else if directive_name == 'endif' && condition_known.len > 0 { + condition_known.delete_last() + condition_active.delete_last() + } + mut definitely_inactive := false + mut condition_uncertain := false + for depth in 0 .. condition_known.len { + if condition_known[depth] && !condition_active[depth] { + definitely_inactive = true + break + } + if !condition_known[depth] { + condition_uncertain = true + } + } + macro_directive, macro_name := c_macro_directive_info(directive) + if macro_directive.len > 0 { + if !definitely_inactive { + if condition_uncertain { + mut contexts := active_macro_contexts[macro_name] or { []int{} } + contexts << i + active_macro_contexts[macro_name] = contexts + } else if macro_directive == 'define' { + active_macro_contexts[macro_name] = [i] + } else { + active_macro_contexts.delete(macro_name) + } + } + } + pragma_action, pragma_key := c_pragma_directive_info(directive) + if pragma_action.len > 0 && condition_uncertain && !definitely_inactive { + mut contexts := active_pragma_pushes[pragma_key] or { []int{} } + contexts << i + active_pragma_pushes[pragma_key] = contexts + } else if pragma_action == 'push' && !definitely_inactive { + mut pushes := active_pragma_pushes[pragma_key] or { []int{} } + pushes << i + active_pragma_pushes[pragma_key] = pushes + } else if pragma_action == 'pop' && !definitely_inactive { + mut pushes := active_pragma_pushes[pragma_key] or { []int{} } + if pushes.len > 0 { + pushes.delete_last() + } + if pushes.len == 0 { + active_pragma_pushes.delete(pragma_key) + } else { + active_pragma_pushes[pragma_key] = pushes + } + } + if c_is_late_source_include_directive(directive) && directive !in early_source_directives { + mut start := i + for start > 0 && c_is_source_context_directive(directives[start - 1]) { + start-- + } + mut end := i + 1 + for end < directives.len && c_is_source_context_directive(directives[end]) { + end++ + } + for delayed_index in start .. end { + emit_late[delayed_index] = true + } + skip_early[i] = true + // The early pass may emit a later `#undef` before this source is replayed. + // Re-emit the active macro contexts and their later transitions so the include + // sees its original macro state and the late pass restores the final state. + for active_name, context_indices in active_macro_contexts { + for context_index in context_indices { + emit_late[context_index] = true + } + for later_index in i + 1 .. directives.len { + _, later_name := c_macro_directive_info(directives[later_index]) + if later_name == active_name { + emit_late[later_index] = true + } + } + } + for active_key, pushes in active_pragma_pushes { + for push_index in pushes { + emit_late[push_index] = true + } + for later_index in i + 1 .. directives.len { + _, later_key := c_pragma_directive_info(directives[later_index]) + if later_key == active_key { + emit_late[later_index] = true + } + } + } + } + } + c_add_late_conditional_context(directives, mut emit_late) + return CSourceDirectiveEmission{ + skip_early: skip_early + emit_late: emit_late + } +} + +fn c_add_late_conditional_context(directives []string, mut emit_late map[int]bool) { + mut condition_starts := []int{} + mut condition_has_late_directive := []bool{} + for i, directive in directives { + name := if directive.contains('\n') { + '' + } else { + c_directive_name(trimmed_space(directive)) + } + if name in ['if', 'ifdef', 'ifndef'] { + condition_starts << i + condition_has_late_directive << false + } + if i in emit_late { + for depth in 0 .. condition_has_late_directive.len { + condition_has_late_directive[depth] = true + } + } + if name == 'endif' && condition_starts.len > 0 { + last := condition_starts.len - 1 + if condition_has_late_directive[last] { + for context_index in condition_starts[last] .. i + 1 { + if c_is_conditional_directive(directives[context_index]) { + emit_late[context_index] = true + } + } + } + condition_starts.delete_last() + condition_has_late_directive.delete_last() + } + } + for depth, start in condition_starts { + if condition_has_late_directive[depth] { + for context_index in start .. directives.len { + if c_is_conditional_directive(directives[context_index]) { + emit_late[context_index] = true + } + } + } + } +} + +fn c_macro_directive_info(directive string) (string, string) { + clean := trimmed_space(directive) + if clean.contains('\n') { + return '', '' + } + name := c_directive_name(clean) + if name !in ['define', 'undef'] { + return '', '' + } + parts := c_directive_arg(clean).fields() + if parts.len == 0 { + return '', '' + } + return name, parts[0].all_before('(') +} + +fn c_pragma_directive_info(directive string) (string, string) { + clean := trimmed_space(directive) + if clean.contains('\n') || c_directive_name(clean) != 'pragma' { + return '', '' + } + arg := c_directive_arg(clean) + compact := arg.replace(' ', '').replace('\t', '') + if push_pos := compact.index('(push') { + return 'push', compact[..push_pos] + } + if pop_pos := compact.index('(pop') { + return 'pop', compact[..pop_pos] + } + fields := arg.fields() + if fields.len < 2 || fields[fields.len - 1] !in ['push', 'pop'] { + return '', '' + } + return fields[fields.len - 1], fields[..fields.len - 1].join(' ') +} + +fn c_is_conditional_directive(directive string) bool { + clean := trimmed_space(directive) + return !clean.contains('\n') + && c_directive_name(clean) in ['if', 'ifdef', 'ifndef', 'elif', 'else', 'endif'] +} + +fn c_is_source_context_directive(directive string) bool { + clean := trimmed_space(directive) + return !clean.contains('\n') && c_directive_name(clean) in ['define', 'undef', 'pragma'] +} + +fn c_is_source_include_directive(directive string) bool { + clean := trimmed_space(directive) + if clean.contains('\n') { + for line in clean.split_into_lines() { + if c_is_source_include_directive(line) { + return true + } + } + return false + } + if c_directive_name(clean) != 'include' { + return false + } + mut arg := c_directive_arg(clean) + if arg.len < 3 || arg[0] != `"` { + return false + } + end := arg.index_after('"', 1) or { return false } + arg = arg[1..end] + return arg.ends_with('.c') || arg.ends_with('.m') || arg.ends_with('.mm') +} + +fn c_is_late_source_include_directive(directive string) bool { + clean := trimmed_space(directive) + if clean.contains('\n') { + for line in clean.split_into_lines() { + if c_is_late_source_include_directive(line) { + return true + } + } + return false + } + if !c_is_source_include_directive(clean) { + return false + } + mut arg := c_directive_arg(clean) + end := arg.index_after('"', 1) or { return false } + arg = arg[1..end] + return arg.ends_with('.m') || arg.ends_with('.mm') +} + +fn (mut g FlatGen) emit_preserved_c_directives() { + mut emitted := false + mut emitted_includes := map[string]bool{} + mut has_mach_headers := false + directives := g.ordered_c_directives(false) + use_system_libc := g.c_directives_use_system_libc() + for i, directive in directives { + if !c_contains_preserved_system_include_directive(directive) { + continue + } + if !use_system_libc && c_is_ptrace_system_include_directive(directive) { + continue + } + if directive.contains('') { + has_mach_headers = true + } + clean := trimmed_space(directive) + if directive.contains('\n') { + g.emit_preserved_c_directive(directive) + emitted = true + continue + } + prefix := if c_lifted_include_skips_context(directive) { + []string{} + } else { + c_lifted_include_context_prefix(directives, i) + } + if c_is_preserved_system_include_directive(clean) { + // Dedupe on the include *together with* its lifted guard context: the + // same header may legitimately appear under different guards (e.g. one + // `#ifdef __linux__` block and one `#ifdef __APPLE__` block), and each + // occurrence needs its own context emitted. Keying on the raw include + // line alone would drop the second, differently-guarded include. + key := prefix.join('\n') + '\x00' + clean + if emitted_includes[key] { + continue + } + emitted_includes[key] = true + } + for line in prefix { + g.writeln(line) + emitted = true + } + g.emit_preserved_c_directive(directive) + emitted = true + for _ in 0 .. c_lifted_include_context_depth(prefix) { + g.writeln('#endif') + } + } + refs := g.c_extern_referenced_symbols() + if !has_mach_headers && (refs['C.task_info'] || refs['task_info'] + || refs['C.mach_task_self'] || refs['mach_task_self']) { + g.writeln('#ifdef __APPLE__') + g.emit_preserved_c_directive('#include ') + g.emit_preserved_c_directive('#include ') + g.writeln('#endif') + emitted = true + } + if emitted { + g.writeln('') + } +} + +fn c_lifted_include_skips_context(directive string) bool { + return trimmed_space(directive) == '#include ' +} + +fn (mut g FlatGen) emit_preserved_c_directive(directive string) { + if c_preserved_directive_needs_mach_panic_alias(directive) { + g.writeln('#define panic mach_panic') + g.writeln(directive) + g.writeln('#undef panic') + return + } + g.writeln(directive) +} + +fn c_preserved_directive_needs_mach_panic_alias(directive string) bool { + for line in directive.split_into_lines() { + clean := trimmed_space(line) + if clean == '#include ' || clean == '#include ' { + return true + } + } + return false +} + +fn c_lifted_include_context_prefix(directives []string, include_index int) []string { + mut prefix := []string{} + for i := include_index - 1; i >= 0; i-- { + clean := trimmed_space(directives[i]) + if c_is_preserved_system_include_directive(clean) { + continue + } + if !c_is_liftable_include_context_directive(clean) { + break + } + prefix << directives[i] + } + prefix.reverse_in_place() + return prefix +} + +fn c_lifted_include_context_depth(prefix []string) int { + mut depth := 0 + for directive in prefix { + clean := trimmed_space(directive) + if clean.starts_with('#ifdef') || clean.starts_with('#ifndef') || clean.starts_with('#if ') { + depth++ + } + } + return depth +} + +fn c_is_liftable_include_context_directive(directive string) bool { + clean := trimmed_space(directive) + if clean.len == 0 || clean.contains('\n') || clean.starts_with('#endif') { + return false + } + return clean.starts_with('#define') || clean.starts_with('#undef') + || clean.starts_with('#ifdef') || clean.starts_with('#ifndef') || clean.starts_with('#if ') + || clean.starts_with('#elif') || clean.starts_with('#else') +} + +fn c_is_preserved_system_include_directive(directive string) bool { + clean := trimmed_space(directive) + return (clean.starts_with('#include <') || clean.starts_with('#import <')) + && clean.ends_with('>') && !clean.contains('\n') +} + +fn c_contains_preserved_system_include_directive(directive string) bool { + if c_is_preserved_system_include_directive(directive) { + return true + } + if !directive.contains('\n') { + return false + } + mut has_include := false + for line in directive.split_into_lines() { + clean := trimmed_space(line) + if clean.len == 0 { + continue + } + if c_is_preserved_system_include_directive(clean) { + has_include = true + continue + } + if clean == '#endif' || c_is_liftable_include_context_directive(clean) { + continue + } + return false + } + return has_include +} + +fn c_is_ptrace_system_include_directive(directive string) bool { + mut has_ptrace_include := false + for line in directive.split_into_lines() { + clean := trimmed_space(line) + if clean.len == 0 { + continue + } + if c_is_preserved_system_include_directive(clean) { + if c_directive_arg(clean) != '' { + return false + } + has_ptrace_include = true + continue + } + if clean == '#endif' || c_is_liftable_include_context_directive(clean) { + continue + } + return false + } + return has_ptrace_include +} + +fn (g &FlatGen) visit_c_directive_module(mod string, directives_by_module map[string][]CDirective, mut visiting map[string]bool, mut visited map[string]bool, mut result []string) { + if mod in visited || mod in visiting { + return + } + visiting[mod] = true + directives := directives_by_module[mod] or { []CDirective{} } + for directive in directives { + if directive.before_import { + result << directive.text + } + } + for dep in g.module_imports[mod] or { []string{} } { + if dep in directives_by_module { + g.visit_c_directive_module(dep, directives_by_module, mut visiting, mut visited, mut + result) + } + } + visiting.delete(mod) + visited[mod] = true + for directive in directives { + if !directive.before_import { + result << directive.text + } + } +} + +fn dedupe_top_level_c_includes(directives []string) []string { + mut result := []string{} + mut seen_includes := map[string]bool{} + mut depth := 0 + for directive in directives { + clean := trimmed_space(directive) + if depth == 0 && c_directive_name(clean) in ['include', 'import'] + && !c_is_source_include_directive(clean) { + if clean in seen_includes { + continue + } + seen_includes[clean] = true + } + result << directive + name := c_directive_name(clean) + if name in ['if', 'ifdef', 'ifndef'] { + depth++ + } else if name == 'endif' && depth > 0 { + depth-- + } + } + return result +} + +fn c_directive_name(text string) string { + if text.len == 0 || text[0] != `#` { + return '' + } + body := trimmed_space(text[1..]) + if body.len == 0 { + return '' + } + idx := body.index_u8(` `) + if idx < 0 { + return body + } + return body[..idx] +} + +fn c_directive_arg(text string) string { + if text.len == 0 || text[0] != `#` { + return '' + } + body := trimmed_space(text[1..]) + mut idx := 0 + for idx < body.len && !body[idx].is_space() { + idx++ + } + if idx >= body.len { + return '' + } + return trimmed_space(body[idx..]) +} + +fn c_include_arg(raw string, vroot string, source_file string) string { + return c_include_arg_for_target(raw, vroot, source_file, pref.host_target()) +} + +fn c_include_arg_for_target(raw string, vroot string, source_file string, target pref.Target) string { + mut clean := c_directive_arg_for_target(raw.trim_space(), target) or { return '' } + clean = c_resolve_pseudo_paths(clean.trim_space(), vroot, source_file) + if clean.len == 0 { + return '' + } + if clean[0] == `<` { + end := clean.index_u8(`>`) + if end > 0 { + return clean[..end + 1] + } + return clean + } + if clean[0] == `"` { + mut i := 1 + for i < clean.len { + if clean[i] == `"` { + return clean[..i + 1] + } + i++ + } + } + hash := clean.index_u8(`#`) + if hash > 0 { + return trimmed_space(clean[..hash]) + } + return clean +} + +// c_flag_args resolves a `#flag` directive with no explicit `-d` values. +fn c_flag_args(raw string, vroot string, source_file string, target pref.Target) []string { + return c_flag_args_with_values(raw, vroot, source_file, target, map[string]string{}) +} + +fn c_flag_args_with_values(raw string, vroot string, source_file string, target pref.Target, compile_values map[string]string) []string { + target_arg := c_directive_arg_for_target(raw.trim_space(), target) or { return []string{} } + without_comment := c_flag_strip_hash_comment(target_arg) + defaults_expanded := c_expand_default_define_macros(without_comment, compile_values) or { + return []string{} + } + clean := c_expand_existing_path_macros(defaults_expanded, vroot, source_file) or { + return []string{} + } + args := cmdexec.split_args(clean) or { return []string{} } + if args.len == 0 { + return []string{} + } + base_dir := if source_file.len > 0 { os.dir(source_file) } else { '' } + mut resolved := []string{cap: args.len} + mut resolve_next_path := false + for raw_arg in args { + arg := c_resolve_pseudo_paths(raw_arg, vroot, source_file) + if base_dir.len > 0 { + if resolve_next_path { + resolved << c_resolve_split_flag_path_token(arg, base_dir) + } else { + resolved << c_resolve_flag_path_token(arg, base_dir) + } + } else { + resolved << arg + } + resolve_next_path = c_flag_takes_path_operand(arg) + } + return resolved +} + +// c_expand_default_define_macros resolves `$d(name, fallback)` inside a C flag. +// A matching explicit `-d name[=value]` override wins; the fallback arm is +// used only when the define is absent. Without this step, spaces inside the macro +// become bogus linker input files when the expanded flag is split into arguments. +fn c_expand_default_define_macros(raw string, compile_values map[string]string) ?string { + mut result := '' + mut cursor := 0 + for { + idx := c_flag_default_define_macro_index(raw, cursor) or { return result + raw[cursor..] } + open_idx := idx + 2 + close_idx := c_flag_macro_close(raw, open_idx) + if close_idx < 0 { + return none + } + inner := raw[open_idx + 1..close_idx] + comma_idx := c_flag_top_level_comma(inner) + if comma_idx < 0 { + return none + } + name := c_flag_define_name(inner[..comma_idx]) + mut value := inner[comma_idx + 1..].trim_space() + if value.len >= 2 && value[0] in [`'`, `"`] && value[value.len - 1] == value[0] { + value = value[1..value.len - 1] + } + if override := compile_values[name] { + value = override + } + value = c_flag_quote_macro_value(value) + result += raw[cursor..idx] + value + cursor = close_idx + 1 + } + return result +} + +fn c_flag_default_define_macro_index(text string, start int) ?int { + mut quote := u8(0) + for i := start; i + 2 < text.len; i++ { + ch := text[i] + if ch in [`'`, `"`] { + if c_flag_quote_is_escaped(text, i) { + continue + } + if quote == 0 { + quote = ch + } else if quote == ch { + quote = 0 + } + continue + } + if quote == 0 && ch == `$` && text[i + 1] == `d` && text[i + 2] == `(` { + return i + } + } + return none +} + +// c_flag_quote_macro_value keeps one expanded `$d` value in one argv element +// when the value contains whitespace or shell-tokenizer metacharacters. +fn c_flag_quote_macro_value(value string) string { + if !c_flag_macro_value_needs_quote(value) { + return value + } + return '"${value.replace('\\', '\\\\').replace('"', '\\"')}"' +} + +fn c_flag_macro_value_needs_quote(value string) bool { + if value.contains_any(' \t\r\n') { + return true + } + for i := 0; i < value.len; i++ { + if value[i] == `\\` { + if i + 1 >= value.len || value[i + 1] !in [`'`, `"`] { + return true + } + i++ + continue + } + if value[i] in [`'`, `"`] && i > 0 && i + 1 < value.len { + return true + } + } + return false +} + +// c_flag_define_name unwraps the quoted name arm of a `$d(name, fallback)` macro. +fn c_flag_define_name(raw string) string { + mut name := raw.trim_space() + if name.len >= 2 && name[0] in [`'`, `"`] && name[name.len - 1] == name[0] { + name = name[1..name.len - 1] + } + return name.trim_space() +} + +fn c_flag_macro_close(text string, open_idx int) int { + mut quote := u8(0) + mut depth := 1 + for i := open_idx + 1; i < text.len; i++ { + ch := text[i] + if ch in [`'`, `"`] { + if c_flag_quote_is_escaped(text, i) { + continue + } + if quote == 0 { + quote = ch + } else if quote == ch { + quote = 0 + } + continue + } + if quote != 0 { + continue + } + if ch == `(` { + depth++ + } else if ch == `)` { + depth-- + if depth == 0 { + return i + } + } + } + return -1 +} + +fn c_flag_top_level_comma(text string) int { + mut quote := u8(0) + mut depth := 0 + for i, ch in text { + if ch in [`'`, `"`] { + if c_flag_quote_is_escaped(text, i) { + continue + } + if quote == 0 { + quote = ch + } else if quote == ch { + quote = 0 + } + continue + } + if quote != 0 { + continue + } + if ch == `(` { + depth++ + } else if ch == `)` && depth > 0 { + depth-- + } else if ch == `,` && depth == 0 { + return i + } + } + return -1 +} + +fn c_flag_quote_is_escaped(text string, quote_idx int) bool { + mut slash_count := 0 + mut i := quote_idx - 1 + for i >= 0 && text[i] == `\\` { + slash_count++ + i-- + } + return slash_count % 2 == 1 +} + +fn c_flag_strip_hash_comment(raw string) string { + mut quote := u8(0) + for i := 0; i + 1 < raw.len; i++ { + ch := raw[i] + if ch in [`'`, `"`] { + if c_flag_quote_is_escaped(raw, i) { + continue + } + if quote == 0 { + quote = ch + } else if quote == ch { + quote = 0 + } + continue + } + if quote == 0 && ch == `#` && raw[i + 1] == `#` { + return raw[..i].trim_space() + } + } + return raw.trim_space() +} + +fn c_expand_existing_path_macros(raw string, vroot string, source_file string) ?string { + mut result := raw + for { + first_idx := result.index(r'$first_existing') or { -1 } + when_idx := result.index(r'$when_first_existing') or { -1 } + if first_idx < 0 && when_idx < 0 { + return result + } + is_when := when_idx >= 0 && (first_idx < 0 || when_idx < first_idx) + idx := if is_when { when_idx } else { first_idx } + literal := if is_when { r'$when_first_existing' } else { r'$first_existing' } + open_idx := idx + literal.len + if open_idx >= result.len || result[open_idx] != `(` { + return result + } + close_idx := c_existing_path_macro_close(result, open_idx) + if close_idx < 0 { + return result + } + mut selected := '' + mut candidates := []string{} + for value in result[open_idx + 1..close_idx].split(',') { + raw_path := value.trim(' \t\r\n\'"') + path := c_resolve_pseudo_paths(raw_path, vroot, source_file) + if path.len == 0 { + continue + } + candidates << path + if selected.len == 0 && os.exists(path) { + selected = path + } + } + if selected.len == 0 { + if is_when { + return none + } + panic('none of the paths ${candidates} exist') + } + result = result[..idx] + os.quoted_path(selected) + result[close_idx + 1..] + } + return result +} + +fn c_existing_path_macro_close(text string, open_idx int) int { + mut quote := u8(0) + for i := open_idx + 1; i < text.len; i++ { + ch := text[i] + if ch in [`'`, `"`] { + if quote == 0 { + quote = ch + } else if quote == ch { + quote = 0 + } + continue + } + if ch == `)` && quote == 0 { + return i + } + } + return -1 +} + +fn c_flag_takes_path_operand(flag string) bool { + return flag in ['-I', '-L', '-isystem', '-include', '-imacros'] +} + +fn c_resolve_split_flag_path_token(tok string, base_dir string) string { + if tok.len == 0 || os.is_abs_path(tok) { + return tok + } + return os.real_path(os.join_path_single(base_dir, tok)) +} + +fn c_resolve_flag_path_token(tok string, base_dir string) string { + for prefix in ['-I', '-L'] { + if tok.starts_with(prefix) && tok.len > prefix.len { + path := tok[prefix.len..] + if c_flag_path_is_relative(path) { + return prefix + os.real_path(os.join_path_single(base_dir, path)) + } + return tok + } + } + if !tok.starts_with('-') && c_flag_path_is_relative(tok) { + return os.real_path(os.join_path_single(base_dir, tok)) + } + return tok +} + +fn c_flag_path_is_relative(p string) bool { + if p.len == 0 || os.is_abs_path(p) { + return false + } + return p.starts_with('./') || p.starts_with('../') || p.contains('/') +} + +fn c_directive_arg_for_target(raw string, target pref.Target) ?string { + clean := raw.trim_space() + if clean.len == 0 { + return none + } + mut prefix_end := 0 + for prefix_end < clean.len && !clean[prefix_end].is_space() { + prefix_end++ + } + prefix := clean[..prefix_end] + if c_flag_has_target_prefix(prefix) { + if !c_flag_target_enabled(prefix, target) || prefix_end >= clean.len { + return none + } + arg := clean[prefix_end..].trim_space() + if arg.len == 0 { + return none + } + return arg + } + return clean +} + +fn c_resolve_pseudo_paths(raw string, vroot string, source_file string) string { + mut result := raw + if result.contains('@VEXEROOT') && vroot.len > 0 { + result = result.replace('@VEXEROOT', vroot) + } + if result.contains('@VROOT') { + result = result.replace('@VROOT', '@VMODROOT') + } + if result.contains('@VMODROOT') { + vmod_result := result.replace('@VMODROOT', c_vmod_root_for_file(source_file)) + local_result := result.replace('@VMODROOT', os.real_path(os.dir(source_file))) + result = if !os.exists(vmod_result) && os.exists(local_result) { + local_result + } else { + vmod_result + } + } + if result.contains('@DIR') { + dir := if source_file.len > 0 { os.dir(source_file) } else { os.getwd() } + result = result.replace('@DIR', os.real_path(dir)) + } + return result +} + +fn c_vmod_root_for_file(source_file string) string { + mut dir := if source_file.len > 0 { os.dir(source_file) } else { os.getwd() } + if dir.len == 0 { + dir = os.getwd() + } + for { + if os.exists(os.join_path(dir, 'v.mod')) { + return os.real_path(dir) + } + parent := os.dir(dir) + if parent == dir || parent.len == 0 { + return os.real_path(dir) + } + dir = parent + } + return os.real_path(dir) +} + +fn c_pkgconfig_flags(raw string) []string { + packages := cmdexec.split_args(trimmed_space(raw)) or { return []string{} } + if packages.len == 0 { + return []string{} + } + mut args := ['--cflags', '--libs'] + args << packages + result := cmdexec.run('pkg-config', args) + if result.exit_code != 0 { + return []string{} + } + return cmdexec.split_args(trimmed_space(result.output)) or { []string{} } +} + +fn c_flag_has_target_prefix(target string) bool { + if _ := c_flag_target_os(target) { + return true + } + if _ := c_flag_target_arch(target) { + return true + } + return false +} + +fn c_flag_target_enabled(target string, platform pref.Target) bool { + if target_os := c_flag_target_os(target) { + return target_os == platform.os + } + if target_arch := c_flag_target_arch(target) { + return target_arch == platform.arch + } + return true +} + +fn c_flag_target_os(target string) ?string { + normalized := pref.normalized_os(target) + if normalized in ['windows', 'macos', 'linux', 'freebsd', 'openbsd', 'netbsd', 'dragonfly', + 'android', 'termux', 'ios', 'solaris', 'qnx', 'haiku', 'serenity', 'vinix', + 'wasm32_emscripten'] { + return normalized + } + return none +} + +fn c_flag_target_arch(target string) ?string { + normalized := pref.normalized_arch(target) + if normalized in ['amd64', 'arm64', 'x86', 'arm32', 'riscv64', 'ppc', 'ppc64', 'ppc64le', 's390x', + 'loongarch64', 'wasm32'] { + return normalized + } + return none +} + +// fn_decl_module_key returns the collision-proof per-module key for a declared +// fn/method. Method names (`Recv.method`) are dotted but not module-qualified, +// so name-based keys collide when two modules declare the same receiver/method +// pair (e.g. `io.NotExpected.msg` vs `os.NotExpected.msg`). +fn fn_decl_module_key(module_name string, name string) string { + return '${module_name}\x01${name}' +} + +fn (mut g FlatGen) register_fn_decl_signature_alias(alias string, ptypes []types.Type, shared_params []bool, is_variadic bool, is_mut bool, rt types.Type) { + if alias !in g.fn_decl_param_types { + g.fn_decl_param_types[alias] = ptypes + if is_variadic { + g.fn_decl_variadic[alias] = true + } + } + // All-false declarations are registered too: their exact-name entry stops + // short-name fallback from borrowing flags from an unrelated declaration. + if shared_params.len > 0 && alias !in g.fn_decl_shared_params { + g.fn_decl_shared_params[alias] = shared_params + } + if is_mut { + g.fn_decl_mut_receivers[alias] = true + } + if alias !in g.fn_decl_ret_types { + g.fn_decl_ret_types[alias] = rt + } +} + +// register_fn_decl_signature indexes every spelling used by CGen call lookup +// while retaining the collision-proof per-module parameter/return entries. +fn (mut g FlatGen) register_fn_decl_signature(name string, full_name string, ptypes []types.Type, shared_params []bool, is_variadic bool, is_mut bool, ret_typ string) { + rt := g.tc.parse_type(ret_typ) + g.register_fn_decl_signature_type(name, full_name, ptypes, shared_params, is_variadic, is_mut, + rt) +} + +fn (mut g FlatGen) register_fn_decl_signature_type(name string, full_name string, ptypes []types.Type, shared_params []bool, is_variadic bool, is_mut bool, rt types.Type) { + registration := g.prepare_fn_signature_registration(name, full_name, ptypes, shared_params, + is_variadic, is_mut, rt) + g.apply_fn_signature_registration_group(registration, 0) + g.apply_fn_signature_registration_group(registration, 1) + g.apply_fn_signature_registration_group(registration, 2) + g.apply_fn_signature_registration_group(registration, 3) +} + +fn (mut g FlatGen) prepare_fn_signature_registration(name string, full_name string, ptypes []types.Type, shared_params []bool, is_variadic bool, is_mut bool, rt types.Type) FnSignatureRegistration { + for flag in shared_params { + if flag { + g.has_shared_params = true + break + } + } + mut aliases := [6]string{} + mut alias_count := 0 + if !g.dedup_fn_decl_aliases { + aliases[alias_count] = name + alias_count++ + cname := g.cname(name) + aliases[alias_count] = cname + alias_count++ + if g.tc.cur_module.len > 0 && g.tc.cur_module != 'main' && g.tc.cur_module != 'builtin' { + dotted_name := '${g.tc.cur_module}.${name}' + aliases[alias_count] = dotted_name + alias_count++ + cdotted_name := g.cname(dotted_name) + aliases[alias_count] = cdotted_name + alias_count++ + } + aliases[alias_count] = full_name + alias_count++ + cfull_name := g.cname(full_name) + aliases[alias_count] = cfull_name + alias_count++ + } else { + aliases[alias_count] = name + alias_count++ + cname := g.cname(name) + if cname != name { + aliases[alias_count] = cname + alias_count++ + } + mut dotted_name := '' + mut cdotted_name := '' + if g.tc.cur_module.len > 0 && g.tc.cur_module != 'main' && g.tc.cur_module != 'builtin' { + dotted_name = '${g.tc.cur_module}.${name}' + if dotted_name != name && dotted_name != cname { + aliases[alias_count] = dotted_name + alias_count++ + } + cdotted_name = g.cname(dotted_name) + if cdotted_name != name && cdotted_name != cname && cdotted_name != dotted_name { + aliases[alias_count] = cdotted_name + alias_count++ + } + } + if full_name != name && full_name != cname && full_name != dotted_name + && full_name != cdotted_name { + aliases[alias_count] = full_name + alias_count++ + } + cfull_name := g.cname(full_name) + if cfull_name != name && cfull_name != cname && cfull_name != dotted_name + && cfull_name != cdotted_name && cfull_name != full_name { + aliases[alias_count] = cfull_name + alias_count++ + } + } + return FnSignatureRegistration{ + module_key: fn_decl_module_key(g.tc.cur_module, name) + short_name: c_short_name_view(name) + aliases: aliases + alias_count: u8(alias_count) + ptypes: ptypes + shared_params: shared_params + is_variadic: is_variadic + is_mut: is_mut + return_type: rt + } +} + +fn (mut g FlatGen) apply_fn_signature_registration_group(registration FnSignatureRegistration, group int) { + match group { + 0 { + g.fn_decl_param_types[registration.module_key] = registration.ptypes + if registration.is_variadic { + g.fn_decl_variadic[registration.module_key] = true + } + g.fn_decl_variadic_short_counts[registration.short_name] = + g.fn_decl_variadic_short_counts[registration.short_name] + 1 + for alias_idx in 0 .. registration.alias_count { + alias := registration.aliases[alias_idx] + if alias !in g.fn_decl_param_types { + g.fn_decl_param_types[alias] = registration.ptypes + if registration.is_variadic { + g.fn_decl_variadic[alias] = true + } + } + } + } + 1 { + g.fn_decl_ret_types[registration.module_key] = registration.return_type + for alias_idx in 0 .. registration.alias_count { + alias := registration.aliases[alias_idx] + if alias !in g.fn_decl_ret_types { + g.fn_decl_ret_types[alias] = registration.return_type + } + } + } + 2 { + if registration.shared_params.len > 0 { + for alias_idx in 0 .. registration.alias_count { + alias := registration.aliases[alias_idx] + if alias !in g.fn_decl_shared_params { + g.fn_decl_shared_params[alias] = registration.shared_params + } + } + } + } + 3 { + if registration.is_mut { + for alias_idx in 0 .. registration.alias_count { + alias := registration.aliases[alias_idx] + g.fn_decl_mut_receivers[alias] = true + } + } + } + else {} + } +} + +fn (mut g FlatGen) register_fn_decl_node(name string, module_name string, id flat.NodeId) { + if name !in g.fn_decl_nodes_by_name { + g.fn_decl_nodes_by_name[name] = id + } + short := c_short_name_view(name) + if short !in g.fn_decl_nodes_by_short { + g.fn_decl_nodes_by_short[short] = id + } + module_key := '${module_name}\x01${short}' + if module_key !in g.fn_decl_nodes_by_module_short { + g.fn_decl_nodes_by_module_short[module_key] = id + } +} + +fn cgen_decl_attr_arg(attrs []string, attr_name string) ?string { + for raw_attr in attrs { + if raw_attr.all_before(':').trim_space() != attr_name || !raw_attr.contains(':') { + continue + } + value := raw_attr.all_after(':').trim_space().trim('\'"') + if value.len > 0 { + return value + } + } + return none +} + +fn cgen_decl_has_attr(attrs []string, attr_name string) bool { + for raw_attr in attrs { + if raw_attr.all_before(':').trim_space() == attr_name { + return true + } + } + return false +} + +fn (mut g FlatGen) index_c_decl_attributes(target_idx int, module_name string, attrs []string) { + if target_idx < 0 || target_idx >= g.a.nodes.len { + return + } + target := g.a.nodes[target_idx] + if target.kind == .c_fn_decl { + if abi_name := cgen_decl_attr_arg(attrs, 'c') { + raw_name := target.value.trim_string_left('C.') + qualified := qualify_name_in_module(module_name, raw_name) + for name in [raw_name, 'C.${raw_name}', qualified, g.cname(raw_name), + g.cname(qualified)] { + g.c_decl_abi_names[name] = abi_name + } + } + return + } + if target.kind != .global_decl || !cgen_decl_has_attr(attrs, 'c_extern') { + return + } + for i in 0 .. target.children_count { + field := g.a.child_node(&target, i) + raw_name := field.value.trim_string_left('C.') + qualified := qualify_name_in_module(module_name, raw_name) + g.c_extern_global_names[raw_name] = raw_name + g.c_extern_global_names[qualified] = raw_name + g.c_extern_global_names[g.cname(qualified)] = raw_name + } +} + +// register_struct_decl_info updates register struct decl info state for c. +fn (mut g FlatGen) register_struct_decl_info(name string, full_name string, module_name string, source_file string, node flat.Node) { + g.register_struct_decl_info_at(-1, name, full_name, module_name, source_file, node) +} + +fn (mut g FlatGen) register_struct_decl_info_at(node_id int, name string, full_name string, module_name string, source_file string, node flat.Node) { + info := StructDeclInfo{ + node: node + node_id: node_id + module: module_name + file: source_file + full_name: full_name + } + g.struct_decl_infos[full_name] = info + if name !in g.struct_decl_short_infos { + g.struct_decl_short_infos[name] = info + } +} + +// preseed_struct_default_string_literals reserves strings that C generation can +// copy from a struct declaration into an omitted/defaulted call argument. Those +// declaration nodes are outside function subtrees, so parallel function prep +// does not otherwise see them before worker-local string IDs are assigned. +fn (mut g FlatGen) preseed_struct_default_string_literals() { + mut seen := map[int]bool{} + mut stack := []flat.NodeId{cap: 32} + for _, info in g.struct_decl_infos { + for i in 0 .. info.node.children_count { + field := g.a.child_node(&info.node, i) + if field.kind != .field_decl || field.children_count == 0 { + continue + } + stack.clear() + stack << g.a.child(field, 0) + for stack.len > 0 { + id := stack.pop() + idx := int(id) + if idx < 0 || idx >= g.a.nodes.len || seen[idx] { + continue + } + seen[idx] = true + node := g.a.nodes[idx] + if node.kind == .string_literal { + g.intern_string(node.value) + } + for child_idx := node.children_count - 1; child_idx >= 0; child_idx-- { + stack << g.a.child(&node, child_idx) + } + } + } + } +} + +// enum_value_for_type supports enum value for type handling for FlatGen. +fn (g &FlatGen) enum_value_for_type(type_name string, field_name string) ?int { + if type_name.len == 0 || field_name.len == 0 { + return none + } + key := '${type_name}.${field_name}' + if val := g.enum_vals[key] { + return val + } + if !type_name.contains('.') && g.tc.cur_module.len > 0 && g.tc.cur_module != 'main' + && g.tc.cur_module != 'builtin' { + qkey := '${g.tc.cur_module}.${type_name}.${field_name}' + if val := g.enum_vals[qkey] { + return val + } + } + if !type_name.contains('.') { + mut found := 0 + mut ok := false + for ename, val in g.enum_vals { + if !ename.ends_with('.${type_name}.${field_name}') { + continue + } + if ok { + return none + } + found = val + ok = true + } + if ok { + return found + } + } + return none +} + +fn (g &FlatGen) enum_value_expr_for_key(key string) ?string { + if expr := g.enum_value_exprs[key] { + return expr + } + if val := g.enum_vals[key] { + return '${val}' + } + return none +} + +fn (g &FlatGen) enum_value_expr_for_type(type_name string, field_name string) ?string { + if type_name.len == 0 || field_name.len == 0 { + return none + } + key := '${type_name}.${field_name}' + if expr := g.enum_value_exprs[key] { + return expr + } + if !type_name.contains('.') && g.tc.cur_module.len > 0 && g.tc.cur_module != 'main' + && g.tc.cur_module != 'builtin' { + qkey := '${g.tc.cur_module}.${type_name}.${field_name}' + if expr := g.enum_value_exprs[qkey] { + return expr + } + } + if !type_name.contains('.') { + mut found := '' + mut ok := false + for ename, expr in g.enum_value_exprs { + if !ename.ends_with('.${type_name}.${field_name}') { + continue + } + if ok { + return none + } + found = expr + ok = true + } + if ok { + return found + } + } + if val := g.enum_value_for_type(type_name, field_name) { + return '${val}' + } + return none +} + +fn (g &FlatGen) enum_selector_base_name(name string) ?string { + mut cache := g.enum_selector_cache + if !isnil(cache) { + cache.select_context(g.tc.cur_file, g.tc.cur_module) + if cache.last_valid && cache.last_name.len == name.len + && (unsafe { cache.last_name.str == name.str } || cache.last_name == name) { + if cache.last_value.len > 0 { + return cache.last_value + } + return none + } + if cached := cache.entries[name] { + cache.last_name = name + cache.last_value = cached + cache.last_valid = true + if cached.len > 0 { + return cached + } + return none + } + } + result := g.enum_selector_base_name_uncached(name) or { + if !isnil(cache) { + cache.entries[name] = '' + cache.last_name = name + cache.last_value = '' + cache.last_valid = true + } + return none + } + if !isnil(cache) { + cache.entries[name] = result + cache.last_name = name + cache.last_value = result + cache.last_valid = true + } + return result +} + +fn (g &FlatGen) enum_selector_base_name_uncached(name string) ?string { + if name in g.tc.enum_names || name in g.tc.flag_enums { + return name + } + if name.contains('.') { + alias := name.all_before('.') + if module_name := g.import_alias_module(alias) { + resolved := '${module_name}.${name.all_after('.')}' + if resolved in g.tc.enum_names || resolved in g.tc.flag_enums { + return resolved + } + if target := g.enum_selector_alias_target(resolved) { + return target + } + } + } + qname := g.tc.qualify_name(name) + if qname in g.tc.enum_names || qname in g.tc.flag_enums { + return qname + } + if target := g.enum_selector_alias_target(name) { + return target + } + if target := g.enum_selector_alias_target(qname) { + return target + } + if name.contains('.') || g.tc.cur_file.len == 0 { + return none + } + candidates := g.tc.file_selective_imports['${g.tc.cur_file}\n${name}'] or { return none } + for candidate in candidates { + if candidate in g.tc.enum_names || candidate in g.tc.flag_enums { + return candidate + } + if target := g.enum_selector_alias_target(candidate) { + return target + } + } + return none +} + +fn (g &FlatGen) enum_selector_alias_target(name string) ?string { + mut cur := name + for _ in 0 .. 16 { + target := g.tc.type_aliases[cur] or { return none } + if target == cur { + return none + } + if target in g.tc.enum_names || target in g.tc.flag_enums { + return target + } + cur = target + } + return none +} + +// expr_to_string converts expr to string data for c. +fn (mut g FlatGen) expr_to_string(id flat.NodeId) string { + orig := g.sb + orig_line_start := g.line_start + g.sb = strings.new_builder(64) + g.line_start = true + g.gen_expr(id) + result := g.sb.str() + g.sb = orig + g.line_start = orig_line_start + return result +} + +// const_block_init_to_string renders a lowered const initializer block as a +// braced statement sequence assigning the final expression to the const. +fn (mut g FlatGen) const_block_init_to_string(qname string, val_node flat.Node, expected types.Type) string { + orig := g.sb + orig_line_start := g.line_start + orig_indent := g.indent + g.sb = strings.new_builder(256) + g.line_start = true + g.indent = 1 + g.writeln('{') + g.push_scope() + g.indent++ + for i in 0 .. int(val_node.children_count) - 1 { + g.gen_node(g.a.child(&val_node, i)) + } + g.write('${qname} = ') + last_id := g.a.child(&val_node, int(val_node.children_count) - 1) + last := g.a.nodes[int(last_id)] + if last.kind == .expr_stmt && last.children_count > 0 { + g.gen_expr_with_expected_type(g.a.child(&last, 0), expected) + } else { + g.gen_expr_with_expected_type(last_id, expected) + } + g.writeln(';') + g.indent-- + g.pop_scope() + g.writeln('}') + result := g.sb.str() + g.sb = orig + g.line_start = orig_line_start + g.indent = orig_indent + return result +} + +// interface_value_to_string captures, as a string, the boxed interface value the direct return +// path emits (`(Iface){._typ = N, ._object = ...}`) — so a deferred return can save it into a +// temp without dropping `_typ`/`_object`. Mirrors that path: box a concrete value, else (already +// boxed by the transform) emit it as-is. +fn (mut g FlatGen) interface_value_to_string(id flat.NodeId, expected types.Type) string { + orig := g.sb + orig_line_start := g.line_start + g.sb = strings.new_builder(64) + // Box mid-statement (no leading indent), matching the direct return path. + g.line_start = false + if !g.gen_interface_value_expr(id, expected) { + mut actual := g.usable_expr_type(id) + node := g.a.nodes[int(id)] + if node.kind == .ident { + if param_type := g.current_param_type(node.value) { + actual = param_type + } + } + if !g.gen_embedded_interface_receiver(id, actual, expected, false) { + g.gen_expr(id) + } + } + result := g.sb.str() + g.sb = orig + g.line_start = orig_line_start + return result +} + +// fixed_array_copy_source_string captures gen_fixed_array_copy_source as a string, so a deferred +// optional/fixed-array return can embed the memcpy source when saving the value into a temp. +fn (mut g FlatGen) fixed_array_copy_source_string(value_id flat.NodeId, field_type types.Type) string { + orig := g.sb + orig_line_start := g.line_start + g.sb = strings.new_builder(64) + // Emit mid-statement (no leading indent), matching the direct return path. + g.line_start = false + g.gen_fixed_array_copy_source(value_id, field_type) + result := g.sb.str() + g.sb = orig + g.line_start = orig_line_start + return result +} + +// expr_to_string_with_expected_type converts expr to string with expected type data for c. +fn (mut g FlatGen) expr_to_string_with_expected_type(id flat.NodeId, expected types.Type) string { + orig := g.sb + orig_line_start := g.line_start + g.sb = strings.new_builder(64) + g.line_start = true + g.gen_expr_with_expected_type(id, expected) + result := g.sb.str() + g.sb = orig + g.line_start = orig_line_start + return result +} + +fn (mut g FlatGen) gen_mut_pointer_slot_expr(id flat.NodeId) { + node := g.a.nodes[int(id)] + if node.kind == .ident && g.current_param_is_mut_pointer(node.value) { + g.write(g.local_decl_cname(node.value)) + return + } + g.gen_expr(id) +} + +fn (g &FlatGen) source_mut_pointer_param_deref_type(id flat.NodeId) ?types.Type { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return none + } + node := g.a.node(id) + if node.kind in [.expr_stmt, .paren] && node.children_count > 0 { + return g.source_mut_pointer_param_deref_type(g.a.child(node, 0)) + } + if node.kind == .block && node.children_count > 0 { + return g.source_mut_pointer_param_deref_type(g.a.child(node, node.children_count - 1)) + } + if node.kind == .prefix && node.op == .mul && node.value.len == 0 && node.children_count > 0 { + return g.source_mut_pointer_param_deref_type(g.a.child(node, 0)) + } + if node.kind != .prefix || node.op != .mul || node.value != source_mut_pointer_deref_marker + || node.children_count == 0 { + return none + } + child := g.a.child_node(node, 0) + if child.kind != .ident || !g.current_param_is_mut_pointer(child.value) { + return none + } + slot_type := g.current_param_type(child.value) or { return none } + if slot_type is types.Pointer && slot_type.base_type is types.Pointer { + return slot_type.base_type.base_type + } + return none +} + +fn (mut g FlatGen) default_value_to_string(typ types.Type) string { + orig := g.sb + orig_line_start := g.line_start + g.sb = strings.new_builder(64) + g.line_start = false + g.gen_default_value_for_type(typ) + result := g.sb.str() + g.sb = orig + g.line_start = orig_line_start + return result +} + +fn (mut g FlatGen) gen_amp_c_string_literal(id flat.NodeId, node flat.Node) bool { + if node.kind == .char_literal && node.value.starts_with('c:') { + // `&c'...'` always denotes the C string pointer; emit the literal + // directly so a byte-valued expected type can't deref a single-char + // `c'\n'` into `*"\n"` here. + g.write('"${escape_c_string_literal_quotes(node.value[2..])}"') + return true + } + if node.kind != .char_literal && node.kind != .string_literal { + return false + } + expr := g.expr_to_string(id) + if expr.len >= 2 && expr[0] == `"` && expr[expr.len - 1] == `"` { + g.write(expr) + return true + } + return false +} + +fn (mut g FlatGen) gen_expr_as_string(id flat.NodeId) { + typ := g.usable_expr_type(id) + if g.gen_map_str_expr(id, typ) { + return + } + if typ is types.Pointer && typ.base_type is types.String { + if g.gen_current_mut_param_value_read(id, typ.base_type) { + return + } + g.write('*(') + g.gen_expr(id) + g.write(')') + return + } + g.gen_expr(id) +} + +fn (mut g FlatGen) gen_map_str_expr(id flat.NodeId, typ types.Type) bool { + clean := map_str_clean_type(typ) + if clean !is types.Map { + return false + } + alias_name := map_str_alias_name(typ) + if alias_name.len > 0 { + prefix_sid := g.intern_string('${alias_name}(') + g.write('string__plus(string__plus(_str_${prefix_sid}, ') + } + node := g.a.nodes[int(id)] + if node.kind == .map_init && typ !is types.Pointer { + tmp := '__map_str_tmp_${g.tmp_count}' + g.tmp_count++ + g.write('({ map ${tmp} = ') + g.gen_expr_with_expected_type(id, clean) + g.write(';') + key_kind := map_str_kind(g.tc, clean.key_type) + val_kind := map_str_kind(g.tc, clean.value_type) + fixed_len := map_str_fixed_len(clean.value_type) + g.write(' v3_map_str(${tmp}, ${key_kind}, ${val_kind}, ${fixed_len}); })') + if alias_name.len > 0 { + suffix_sid := g.intern_string(')') + g.write('), _str_${suffix_sid})') + } + return true + } + g.write('v3_map_str(') + if typ is types.Pointer { + needs_paren := g.a.nodes[int(id)].kind !in [.ident, .selector, .call] + g.write('*') + if needs_paren { + g.write('(') + } + g.gen_expr(id) + if needs_paren { + g.write(')') + } + } else { + g.gen_expr(id) + } + key_kind := map_str_kind(g.tc, clean.key_type) + val_kind := map_str_kind(g.tc, clean.value_type) + fixed_len := map_str_fixed_len(clean.value_type) + g.write(', ${key_kind}, ${val_kind}, ${fixed_len})') + if alias_name.len > 0 { + suffix_sid := g.intern_string(')') + g.write('), _str_${suffix_sid})') + } + return true +} + +fn map_str_clean_type(typ types.Type) types.Type { + clean := types.unwrap_pointer(typ) + if clean is types.Alias { + return clean.base_type + } + return clean +} + +fn map_str_alias_name(typ types.Type) string { + clean := types.unwrap_pointer(typ) + if clean is types.Alias { + if clean.base_type is types.Map { + return clean.name.all_after_last('.') + } + } + return '' +} + +fn map_str_kind(tc &types.TypeChecker, typ types.Type) int { + clean := if typ is types.Alias { typ.base_type } else { typ } + if clean is types.String { + return 1 + } + if clean is types.Rune { + return 4 + } + if clean is types.ISize || clean is types.Char { + return 2 + } + if clean is types.USize { + return 3 + } + if clean is types.Primitive { + if clean.props.has(.float) { + return if tc.c_type(types.Type(clean)) == 'float' { 8 } else { 5 } + } + name := types.Type(clean).name() + if name in ['i8', 'i16', 'i32', 'i64', 'int'] { + return 2 + } + if name in ['u8', 'byte'] { + return 3 + } + if name in ['u16', 'u32', 'u64'] { + return 3 + } + if name == 'bool' { + return 7 + } + } + if fixed := array_fixed_type(clean) { + elem := if fixed.elem_type is types.Alias { + fixed.elem_type.base_type + } else { + fixed.elem_type + } + if elem is types.Primitive && elem.props.has(.float) { + return if tc.c_type(types.Type(elem)) == 'float' { 9 } else { 6 } + } + } + return 0 +} + +fn map_str_fixed_len(typ types.Type) int { + if fixed := array_fixed_type(typ) { + if fixed.len > 0 { + return fixed.len + } + if fixed.len_expr.len > 0 && fixed.len_expr.int().str() == fixed.len_expr { + return fixed.len_expr.int() + } + } + return 0 +} + +// gen_cast_from_mut_param_address emits pointer casts for `¶m` where `param` +// is a mutable V parameter already represented as a C pointer. +fn (mut g FlatGen) gen_cast_from_mut_param_address(id flat.NodeId, ct string) bool { + node := g.a.nodes[int(id)] + if node.kind != .prefix || node.op != .amp || node.children_count != 1 { + return false + } + child_id := g.a.child(&node, 0) + child := g.a.nodes[int(child_id)] + if child.kind != .ident || !g.current_param_is_mut(child.value) { + return false + } + param_type := g.current_param_type(child.value) or { return false } + if param_type !is types.Pointer { + return false + } + g.write('(${ct})(') + if g.current_param_is_mut_pointer(child.value) { + g.gen_mut_pointer_slot_expr(child_id) + } else { + g.gen_expr(child_id) + } + g.write(')') + return true +} + +// gen_cast_from_mut_pointer_param_value reads the semantic pointer value from +// the extra ABI indirection used for an explicit `mut p &T` parameter. +fn (mut g FlatGen) gen_cast_from_mut_pointer_param_value(id flat.NodeId, ct string) bool { + node := g.a.nodes[int(id)] + if node.kind != .ident || !g.current_param_is_mut(node.value) { + return false + } + param_type := g.current_param_type(node.value) or { return false } + if param_type !is types.Pointer { + return false + } + pointer_type := param_type as types.Pointer + if pointer_type.base_type !is types.Pointer { + return false + } + g.write('(${ct})(*${g.cname(node.value)})') + return true +} + +fn (mut g FlatGen) gen_pointer_cast_from_map_value_address(id flat.NodeId, target types.Pointer) bool { + if map_str_clean_type(target.base_type) !is types.Map { + return false + } + return g.gen_map_pointer_cast_from_value_address(id, target) +} + +fn (mut g FlatGen) gen_sum_variant_pointer_cast(id flat.NodeId, target types.Pointer, ct string) bool { + source_type0 := g.sum_cast_actual_type(id) + source_ptr := match source_type0 { + types.Pointer { source_type0 } + else { return false } + } + + source_base := match source_ptr.base_type { + types.Alias { source_ptr.base_type.base_type } + else { source_ptr.base_type } + } + + source_sum := match source_base { + types.SumType { source_base } + else { return false } + } + + target_base := match target.base_type { + types.Alias { target.base_type.base_type } + else { target.base_type } + } + + sum_name := g.resolve_sum_name(source_sum.name) + variants := g.tc.sum_types[sum_name] or { return false } + variant := g.resolve_variant(sum_name, target_base.name()) + if variant !in variants { + return false + } + variant_idx := variants.index(variant) + 1 + sum_ct := g.tc.c_type(source_sum) + field := g.sum_field_name(variant) + tmp := g.tmp_count + g.tmp_count++ + g.write('({ ${sum_ct}* _sum_ptr_${tmp} = (${sum_ct}*)(') + g.gen_expr(id) + g.write('); (${ct})((_sum_ptr_${tmp}->typ == ${variant_idx}) ? _sum_ptr_${tmp}->${field} : (${ct})_sum_ptr_${tmp}); })') + return true +} + +fn (mut g FlatGen) gen_sum_pointer_cast_expr(id flat.NodeId, target types.Pointer, ct string) bool { + sum_type0 := match target.base_type { + types.Alias { target.base_type.base_type } + else { target.base_type } + } + + sum_type_name := match sum_type0 { + types.SumType { sum_type0.name } + else { return false } + } + + actual0 := g.sum_cast_actual_type(id) + mut actual_type := match actual0 { + types.Alias { actual0.base_type } + else { actual0 } + } + + if actual_type is types.Pointer { + actual_type = actual_type.base_type + } + if actual_type is types.SumType { + return false + } + sum_name := g.resolve_sum_name(sum_type_name) + variants := g.tc.sum_types[sum_name] or { return false } + variant := g.resolve_variant(sum_name, actual_type.name()) + if variant !in variants { + return false + } + g.write('(${ct})memdup(&') + g.gen_sum_cast_expr(types.SumType{ + name: sum_type_name + }, id) + g.write(', sizeof(${g.tc.c_type(g.tc.parse_type(sum_type_name))}))') + return true +} + +fn (mut g FlatGen) gen_map_pointer_cast_from_value_address(id flat.NodeId, target types.Pointer) bool { + actual0 := if int(id) >= 0 && int(id) < g.a.nodes.len && g.a.nodes[int(id)].typ.len > 0 { + g.tc.parse_type(g.a.nodes[int(id)].typ) + } else { + g.usable_expr_type(id) + } + if actual0 is types.Pointer { + ct := g.tc.c_type(target) + g.write('(${ct})(') + g.gen_expr(id) + g.write(')') + return true + } + actual := map_str_clean_type(actual0) + if actual !is types.Map { + return false + } + if g.expr_is_addressable(id) { + g.write('&') + g.gen_expr(id) + return true + } + ct := g.tc.c_type(actual) + g.write('({${ct} _t${g.tmp_count} = ') + g.gen_expr(id) + g.write('; &_t${g.tmp_count};})') + g.tmp_count++ + return true +} + +fn (mut g FlatGen) map_pointer_cast_from_value_address_string(id flat.NodeId, seen []string, ct string) ?string { + actual0 := if int(id) >= 0 && int(id) < g.a.nodes.len && g.a.nodes[int(id)].typ.len > 0 { + g.tc.parse_type(g.a.nodes[int(id)].typ) + } else { + g.usable_expr_type(id) + } + if actual0 is types.Pointer && map_str_clean_type(actual0.base_type) is types.Map { + child0 := g.const_expr_to_string(id, seen) + child := if trimmed_space(child0).len == 0 { '0' } else { child0 } + return '(${ct})(${child})' + } + actual := map_str_clean_type(actual0) + if actual !is types.Map { + return none + } + child0 := g.const_expr_to_string(id, seen) + child := if trimmed_space(child0).len == 0 { '0' } else { child0 } + if g.expr_is_addressable(id) { + return '&(${child})' + } + map_ct := g.tc.c_type(actual) + tmp := '_t${g.tmp_count}' + g.tmp_count++ + return '({${map_ct} ${tmp} = ${child}; &${tmp};})' +} + +fn (mut g FlatGen) gen_current_mut_param_address(id flat.NodeId) bool { + node := g.a.nodes[int(id)] + if node.kind != .prefix || node.op != .amp || node.children_count != 1 { + return false + } + child_id := g.a.child(&node, 0) + child := g.a.nodes[int(child_id)] + if child.kind != .ident || !g.current_param_is_mut(child.value) { + return false + } + param_type := g.current_param_type(child.value) or { return false } + if param_type !is types.Pointer { + return false + } + g.write(g.cname(child.value)) + return true +} + +fn (mut g FlatGen) gen_current_mut_param_value_read(id flat.NodeId, expected types.Type) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + if expected is types.Pointer { + return false + } + node := g.a.nodes[int(id)] + if node.kind != .ident || !g.current_param_is_mut(node.value) { + return false + } + param_type := g.current_param_type(node.value) or { return false } + if param_type is types.Pointer { + param_base := select_receive_unalias_type(param_type.base_type) + expected_base := select_receive_unalias_type(expected) + if !g.type_names_match(param_base, expected_base) + && !mut_optional_param_value_types_match(param_base, expected_base) + && g.value_c_type(param_base) != g.value_c_type(expected_base) { + return false + } + } else { + return false + } + g.write('*') + if g.current_param_is_mut_pointer(node.value) { + g.gen_mut_pointer_slot_expr(id) + } else { + g.gen_expr(id) + } + return true +} + +fn mut_optional_param_value_types_match(param_type types.Type, expected types.Type) bool { + param_payload := if param_type is types.OptionType { + param_type.base_type + } else { + return false + } + expected_payload := if expected is types.OptionType { + expected.base_type + } else { + return false + } + if expected_payload is types.Pointer { + return select_receive_unalias_type(param_payload).name() == select_receive_unalias_type(expected_payload.base_type).name() + } + return false +} + +// gen_expr_with_expected_type emits expr with expected type output for c. +@[direct_array_access] +fn (mut g FlatGen) gen_expr_with_expected_type(id flat.NodeId, expected types.Type) { + has_known_actual := g.known_expr_type_id == int(id) + known_actual := g.known_expr_type + if has_known_actual { + g.known_expr_type_id = -1 + } + semantic_expected := cgen_unalias_type(expected) + old_expected := g.expected_expr_type + old_expected_enum := g.expected_enum + g.expected_expr_type = expected + if expected is types.Enum { + g.expected_enum = expected.name + } + node := unsafe { &g.a.nodes[int(id)] } + expected_is_ierror := if node.kind == .none_expr || node.kind == .call { + g.is_ierror_type_name(semantic_expected.name()) + } else { + false + } + if expected_is_ierror && node.kind == .none_expr { + g.write(g.ierror_none_literal_string()) + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if expected_is_ierror && g.expr_is_error_call(id) { + g.gen_ierror_from_error_call(node) + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if node.kind == .dump_expr { + if node.children_count > 0 { + g.gen_expr_with_expected_type(g.a.child(node, 0), expected) + } else { + g.write('0') + } + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if g.gen_sum_pointer_default_expr(node, semantic_expected) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if semantic_expected is types.MultiReturn && node.kind == .if_expr { + g.gen_if_expr_stmt(node) + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if semantic_expected is types.MultiReturn && node.kind == .block { + if g.gen_multi_return_block_expr(node, semantic_expected) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + } + mut actual := if has_known_actual { known_actual } else { g.usable_expr_type(id) } + if deref_type := g.source_mut_pointer_param_deref_type(id) { + actual = deref_type + } + if node.kind == .ident { + if local_type := g.local_ident_type(node.value) { + actual = local_type + } + } + if expected is types.String && actual is types.Pointer + && g.pointer_stringifies_as_address(actual.base_type) + && !g.type_names_match(actual.base_type, expected) { + g.write('ptr_str(') + g.gen_expr(id) + g.write(')') + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if type_is_void_pointer(expected) && g.gen_voidptr_fn_value_arg(id, node) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + semantic_actual := cgen_unalias_type(actual) + if node.kind == .cast_expr && actual is types.Pointer + && cgen_unalias_type(types.unwrap_all_pointers(actual)) is types.Interface + && semantic_expected !is types.Interface && semantic_expected !is types.Pointer { + // A generic method body may have been transformed while its receiver was + // still a placeholder, boxing an explicit argument against the receiver + // slot. Once specialized, recover the concrete value stored in that box. + value_ct := g.value_c_type(semantic_expected) + g.write('(*(${value_ct}*)(') + g.gen_expr(id) + g.write(')->_object)') + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if semantic_expected is types.String && g.gen_map_str_expr(id, semantic_actual) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if g.gen_current_mut_param_value_read(id, semantic_expected) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if expected is types.Pointer && g.gen_pointer_alias_value_cast_expr(id, expected) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if node.kind == .cast_expr && node.children_count > 0 { + if _ := g.shared_alias_pointer_type_from_text(node.value) { + g.gen_expr_with_expected_type(g.a.child(node, 0), expected) + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if g.cast_alias_matches_expected_storage(node.value, expected) { + g.gen_expr_with_expected_type(g.a.child(node, 0), expected) + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + } + mut expected_is_shared_alias := false + if _ := g.shared_alias_pointer_type(expected) { + expected_is_shared_alias = true + } + if expected is types.OptionType || expected is types.ResultType { + actual_optional := optional_result_unalias_type(actual) + if node.kind == .none_expr || g.expr_is_optional_literal(id, expected) + || actual_optional is types.OptionType || actual_optional is types.ResultType { + g.gen_expr(id) + } else { + g.gen_optional_arg(id, semantic_expected) + } + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if _ := fn_type_from(expected) { + if g.gen_callback_fn_value_for_expected_type(id, expected) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if call_name := g.callback_direct_fn_value_name(id, expected) { + g.write(g.callback_c_fn_name(call_name)) + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + } + if expected is types.Array && node.kind == .array_literal { + g.gen_array_literal_value(node, expected.elem_type) + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if fixed := array_fixed_type(expected) { + if node.kind == .array_literal { + g.gen_fixed_array_literal_value(node, fixed) + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if node.kind == .postfix && node.op == .not && node.children_count == 1 { + child_id := g.a.child(node, 0) + child := g.a.nodes[int(child_id)] + if child.kind == .array_literal { + g.gen_fixed_array_literal_value(child, fixed) + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + } + } + if g.gen_interface_pointer_value_expr(id, expected) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + // Box concrete pointers for interface parameters before the general pointer-to-value + // conversion below. An alias-backed concrete type can otherwise look name-compatible + // with the interface and be dereferenced into an incompatible C value. + if semantic_expected is types.Interface { + if g.gen_embedded_interface_receiver(id, actual, expected, false) + || g.gen_interface_value_expr(id, expected) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + } + mut pointer_actual := actual + if node.kind == .call && actual !is types.Pointer { + declared := g.declared_call_return_type(id) + if declared is types.Pointer { + pointer_actual = declared + } + } + if !expected_is_shared_alias && expected !is types.Pointer && expected !is types.Void + && expected !is types.OptionType && expected !is types.ResultType + && pointer_actual is types.Pointer + && (g.type_names_match(pointer_actual.base_type, expected) + || g.type_names_match(pointer_actual.base_type, semantic_expected) + || g.value_c_type(pointer_actual.base_type) == g.value_c_type(semantic_expected)) + && !(node.kind == .ident && g.local_storage_is_shared(node.value)) + && !(node.kind == .char_literal && node.value.starts_with('c:')) { + needs_paren := node.kind !in [.ident, .selector, .call, .index] + g.write('*') + if needs_paren { + g.write('(') + } + g.gen_expr(id) + if needs_paren { + g.write(')') + } + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if g.gen_sum_pointer_value_expr(id, semantic_expected) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if g.gen_embedded_interface_receiver(id, actual, expected, expected is types.Pointer) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if g.gen_interface_value_expr(id, expected) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if g.gen_interface_value_expr(id, semantic_expected) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if g.gen_sum_constructor_call_with_expected_type(id, node, semantic_expected) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + if g.gen_sum_value_expr(id, semantic_expected) { + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + clean_expected := select_receive_unalias_type(expected) + if node.kind == .prefix && node.op in [.minus, .plus] && node.children_count > 0 + && clean_expected is types.Primitive && clean_expected.props.has(.float) + && clean_expected.size == 32 { + child_id := g.a.child(node, 0) + child := g.a.nodes[int(child_id)] + if child.kind == .float_literal { + g.write(g.op_str(node.op)) + g.gen_expr_with_expected_type(child_id, expected) + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + } + if node.kind == .float_literal && clean_expected is types.Primitive + && clean_expected.props.has(.float) && clean_expected.size == 32 { + g.write('(float)(') + g.gen_expr(id) + g.write(')') + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum + return + } + g.gen_expr(id) + g.expected_expr_type = old_expected + g.expected_enum = old_expected_enum +} + +fn (mut g FlatGen) gen_sum_pointer_default_expr(node flat.Node, expected types.Type) bool { + ptr_type := if expected is types.Pointer { expected } else { return false } + if node.kind != .struct_init || node.children_count != 0 { + return false + } + base_type := default_init_unalias_type(ptr_type.base_type) + if base_type !is types.SumType { + return false + } + if node.value.len > 0 { + init_type := default_init_unalias_type(g.tc.parse_type(node.value)) + if init_type is types.Pointer { + init_base := default_init_unalias_type(init_type.base_type) + if !g.type_names_match(init_base, base_type) { + return false + } + } else if !g.type_names_match(init_type, base_type) { + return false + } + } + g.gen_default_value_for_type(expected) + return true +} + +fn (g &FlatGen) pointer_stringifies_as_address(base types.Type) bool { + if base is types.Alias { + return g.pointer_stringifies_as_address(base.base_type) + } + return base is types.String || base is types.Primitive || base is types.Char + || base is types.Rune || base is types.ISize || base is types.USize +} + +fn (mut g FlatGen) gen_pointer_alias_value_cast_addr(id flat.NodeId, expected types.Pointer) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.node(id) + if node.kind != .prefix || node.op != .amp || node.children_count == 0 { + return false + } + child_id := g.a.child(node, 0) + child := g.a.node(child_id) + if child.kind != .cast_expr || child.children_count == 0 { + return false + } + target_type := g.tc.parse_type(child.value) + if target_type !is types.Alias { + return false + } + target_alias := target_type as types.Alias + base_type := target_alias.base_type + if base_type is types.Pointer { + return false + } + target_ct := g.value_c_type(base_type) + expected_ct := g.value_c_type(expected.base_type) + if expected_ct != target_ct && !g.type_names_match(expected.base_type, target_type) { + return false + } + value_expr := g.expr_to_string(g.a.child(child, 0)) + source_expr := '(${target_ct}[]){${value_expr}}[0]' + copy_expr := g.heap_local_memdup_expr(source_expr, base_type, target_ct, false) + if expected_ct == target_ct { + g.write(copy_expr) + } else { + g.write('(${expected_ct}*)(${copy_expr})') + } + return true +} + +fn (mut g FlatGen) gen_pointer_alias_value_cast_expr(id flat.NodeId, expected types.Pointer) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.node(id) + if node.kind != .cast_expr || node.children_count == 0 { + return false + } + target_type := g.tc.parse_type(node.value) + if target_type !is types.Pointer { + return false + } + target_pointer := target_type as types.Pointer + target_base := target_pointer.base_type + if target_base !is types.Alias { + return false + } + target_alias := target_base as types.Alias + base_type := target_alias.base_type + if base_type is types.Pointer { + return false + } + child_id := g.a.child(node, 0) + child := g.a.node(child_id) + if child.kind == .nil_literal { + return false + } + actual := g.usable_expr_type(child_id) + if actual is types.Pointer { + return false + } + target_ct := g.value_c_type(base_type) + expected_ct := g.value_c_type(expected.base_type) + if expected_ct != target_ct && !g.type_names_match(expected.base_type, target_base) { + return false + } + value_expr := g.expr_to_string(child_id) + source_expr := '(${target_ct}[]){${value_expr}}[0]' + copy_expr := g.heap_local_memdup_expr(source_expr, base_type, target_ct, false) + if expected_ct == target_ct { + g.write(copy_expr) + } else { + g.write('(${expected_ct}*)(${copy_expr})') + } + return true +} + +fn (g &FlatGen) array_index_type_for_expected_arg(actual types.Type, node flat.Node) types.Type { + if spread_index_expected_type_marker !in node.generic_params() { + return actual + } + expected := g.expected_expr_type + if expected is types.Void || expected is types.Unknown { + return actual + } + if actual is types.SumType || (actual is types.Alias && actual.base_type is types.SumType) { + return actual + } + if g.type_names_match(actual, expected) || g.types_numeric_compatible(actual, expected) { + return actual + } + return expected +} + +fn (g &FlatGen) cast_alias_matches_expected_storage(alias_name string, expected types.Type) bool { + if alias_name.len == 0 { + return false + } + mut target_name := g.tc.type_aliases[alias_name] or { '' } + if target_name.len == 0 { + target_name = g.tc.type_aliases[g.tc.qualify_name(alias_name)] or { '' } + } + if target_name.len == 0 { + return false + } + target := select_receive_unalias_type(g.tc.parse_type(target_name)) + expected_base := select_receive_unalias_type(expected) + return g.type_names_match(target, expected_base) + || g.tc.c_type(target) == g.tc.c_type(expected_base) +} + +fn (mut g FlatGen) gen_sum_pointer_value_expr(id flat.NodeId, expected types.Type) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + mut sum_type0 := types.Type(types.void_) + if expected is types.Pointer { + sum_type0 = expected.base_type + } else { + return false + } + + if sum_type0 is types.Alias { + sum_type0 = sum_type0.base_type + } + if sum_type0 !is types.SumType { + return false + } + sum_type_name := types.Type(sum_type0).name() + node := g.a.nodes[int(id)] + if node.kind != .prefix || node.op != .amp || node.children_count == 0 { + return false + } + child_id := g.a.child(&node, 0) + child := g.a.nodes[int(child_id)] + actual0 := g.sum_cast_actual_type(child_id) + mut actual_type := if actual0 is types.Alias { actual0.base_type } else { actual0 } + if actual_type is types.Pointer { + actual_type = actual_type.base_type + } + if actual_type is types.SumType { + if child.kind == .ident && child.value.starts_with('__sum_ref_') + && g.type_names_match(actual_type, sum_type0) { + ct := g.tc.c_type(sum_type0) + g.write('(${ct}*)memdup(&') + g.gen_expr(child_id) + g.write(', sizeof(${ct}))') + return true + } + return false + } + sum_name := g.resolve_sum_name(sum_type_name) + variant := g.resolve_variant(sum_name, actual_type.name()) + variants := g.tc.sum_types[sum_name] or { return false } + if variant !in variants { + return false + } + ct := g.tc.c_type(sum_type0) + g.write('(${ct}*)memdup(&') + g.gen_sum_cast_expr(types.SumType{ + name: sum_type_name + }, child_id) + g.write(', sizeof(${ct}))') + return true +} + +fn (mut g FlatGen) gen_sum_constructor_call_with_expected_type(id flat.NodeId, node flat.Node, expected types.Type) bool { + _ = id + sum_type0 := if expected is types.Alias { expected.base_type } else { expected } + if sum_type0 !is types.SumType || node.kind != .call || node.children_count < 2 { + return false + } + sum_type := sum_type0 as types.SumType + callee := g.a.child_node(&node, 0) + if !g.call_callee_names_sum_base(callee, sum_type.name) { + return false + } + g.gen_sum_cast_expr(sum_type, g.a.child(&node, 1)) + return true +} + +fn (g &FlatGen) call_callee_names_sum_base(callee flat.Node, sum_name string) bool { + _ = g + base := generic_sum_base_name(sum_name) + short_base := base.all_after_last('.') + if callee.kind == .ident { + return callee.value == base || callee.value == short_base + } + if callee.kind == .selector { + return callee.value == short_base || callee.value == base + } + if callee.kind == .index && callee.children_count > 0 { + return g.call_callee_names_sum_base(g.a.child_node(&callee, 0), sum_name) + } + return false +} + +fn generic_sum_base_name(name string) string { + bracket := name.index_u8(`[`) + if bracket > 0 { + return name[..bracket] + } + return name +} + +// gen_sum_value_expr emits sum value expr output for c. +fn (mut g FlatGen) gen_sum_value_expr(id flat.NodeId, expected types.Type) bool { + sum_type := g.sum_type_for_expected_value(expected) or { return false } + sum_type0 := types.Type(sum_type) + raw_actual0 := g.sum_cast_actual_type(id) + raw_actual_type := cgen_unalias_type(raw_actual0) + if raw_actual_type is types.SumType { + // A sum type can itself be a variant of a wider sum type (for example + // `ast.Stmt` inside `ast.Node`). Only skip wrapping when the value is + // already the expected sum. + if g.type_names_match(raw_actual_type, sum_type0) + || g.resolve_sum_name(raw_actual_type.name) == g.resolve_sum_name(sum_type.name) + || (raw_actual_type.name !in g.tc.sum_types + && raw_actual_type.name.all_after_last('.') == sum_type.name.all_after_last('.')) { + return false + } + } + if declared := g.selector_declared_type(id) { + declared0 := cgen_unalias_type(declared) + if declared0 is types.SumType && g.type_names_match(declared0, sum_type0) { + return false + } + } + sum_name := g.resolve_sum_name(sum_type.name) + mut actual_type := raw_actual0 + variant := g.sum_variant_for_actual(sum_name, actual_type) or { return false } + ct := g.tc.c_type(sum_type0) + idx := g.sum_type_index(sum_name, variant) + field := g.sum_field_name(variant) + variant_type := g.tc.parse_type(variant) + clean_variant_type := select_receive_unalias_type(variant_type) + actual_value_type := select_receive_unalias_type(actual_type) + if clean_variant_type is types.Pointer && actual_value_type is types.Pointer + && g.type_names_match(actual_value_type.base_type, clean_variant_type.base_type) { + g.write('(${ct}){.typ = ${idx}, .${field} = ') + if g.pointer_variant_expr_needs_heap_copy(id) { + pointer_ct := g.value_c_type(clean_variant_type.base_type) + g.write('(${pointer_ct}*)memdup(') + g.gen_expr(id) + g.write(', sizeof(${pointer_ct}))') + } else { + g.gen_expr(id) + } + if g.pointer_variant_expr_creates_owned_value(id) { + g.write(', ._pointer_variant_is_owned = true') + } + g.write('}') + return true + } + if g.variant_references_sum(variant, sum_name) { + inner_ct := g.value_c_type(variant_type) + g.write('(${ct}){.typ = ${idx}, .${field} = ') + if actual_value_type is types.Pointer && clean_variant_type is types.Pointer + && g.type_names_match(actual_value_type.base_type, clean_variant_type.base_type) { + if g.pointer_variant_expr_needs_heap_copy(id) { + g.write('(${inner_ct}*)memdup(') + g.gen_expr(id) + g.write(', sizeof(${inner_ct}))') + } else { + g.gen_expr(id) + } + } else { + g.write('(${inner_ct}*)memdup(') + g.gen_sum_variant_memdup_source(id, variant_type) + g.write(', sizeof(${inner_ct}))') + if clean_variant_type is types.Pointer && g.pointer_variant_expr_creates_owned_value(id) { + g.write(', ._pointer_variant_is_owned = true') + } + } + g.write('}') + return true + } + g.write('(${ct}){.typ = ${idx}, .${field} = ') + g.gen_expr(id) + g.write('}') + return true +} + +fn (g &FlatGen) sum_type_for_expected_value(expected types.Type) ?types.SumType { + clean := if expected is types.Alias { expected.base_type } else { expected } + if clean is types.SumType { + return clean + } + if clean is types.Struct { + resolved := g.resolve_sum_name(clean.name) + if resolved in g.tc.sum_types { + return types.SumType{ + name: resolved + } + } + } + return none +} + +fn (g &FlatGen) sum_variant_for_actual(sum_name0 string, actual types.Type) ?string { + sum_name := g.resolve_sum_name(sum_name0) + variants := g.tc.sum_types[sum_name] or { return none } + mut variant := g.resolve_variant(sum_name, actual.name()) + if variant in variants { + return variant + } + actual_clean := if actual is types.Alias { actual.base_type } else { actual } + variant = g.resolve_variant(sum_name, actual_clean.name()) + if variant in variants { + return variant + } + for candidate in variants { + candidate_type := g.tc.parse_type(candidate) + if g.sum_variant_accepts_actual(actual, candidate_type) { + return candidate + } + } + return none +} + +fn (g &FlatGen) sum_variant_accepts_actual(actual types.Type, variant types.Type) bool { + if g.type_names_match(actual, variant) { + return true + } + actual_clean := if actual is types.Alias { actual.base_type } else { actual } + variant_clean := if variant is types.Alias { variant.base_type } else { variant } + if g.type_names_match(actual_clean, variant_clean) { + return true + } + if actual_clean is types.Pointer && variant_clean is types.Pointer { + if actual_clean.base_type is types.Void || variant_clean.base_type is types.Void { + return true + } + return g.type_names_match(actual_clean.base_type, variant_clean.base_type) + } + if actual_clean is types.FnType && variant_clean is types.FnType { + return g.tc.c_type(actual_clean) == g.tc.c_type(variant_clean) + } + if variant_clean is types.SumType { + if _ := g.sum_variant_for_actual(variant_clean.name, actual) { + return true + } + } + return false +} + +fn (mut g FlatGen) sum_cast_actual_type(id flat.NodeId) types.Type { + mut actual_type := g.tc.resolve_type(id) + if int(id) < 0 || int(id) >= g.a.nodes.len { + return actual_type + } + node := g.a.nodes[int(id)] + // Expected-type checking records a bare literal assigned to a sum as the sum + // itself. Imported struct defaults reach cgen without transform-time wrapping, + // so retain the literal's intrinsic type here and let gen_sum_value_expr box it. + match node.kind { + .int_literal { + return types.Type(types.int_) + } + .float_literal { + return types.Type(types.f64_) + } + .bool_literal { + return types.Type(types.bool_) + } + .char_literal { + return types.Type(types.rune_) + } + .string_literal, .string_interp { + return types.Type(types.String{}) + } + .paren, .expr_stmt { + if node.children_count == 1 { + return g.sum_cast_actual_type(g.a.child(&node, 0)) + } + } + else {} + } + if node.kind == .call { + declared := g.declared_call_return_type(id) + if declared !is types.Void && declared !is types.Unknown { + return declared + } + } + if node.kind == .ident { + if param_type := g.current_param_type(node.value) { + return param_type + } + if param_type := g.current_param_map_type(node.value) { + return param_type + } + if fn_name := g.direct_callback_ident_name(id) { + if fn_type := g.callback_fn_value_type(fn_name) { + return types.Type(fn_type) + } + } + // The local's declared type wins over checker expected-type propagation. + // This is most visible for `return bare` in a `!Sum` function, but a sum + // variant can also leak back as the apparent type of an already-materialized + // sum local (for example `[]Any` onto an `Any` parameter). + if g.tc != unsafe { nil } && g.tc.cur_scope != unsafe { nil } { + if scope_type := g.tc.cur_scope.lookup(node.value) { + if scope_type !is types.Void && scope_type !is types.Unknown { + return scope_type + } + } + if const_type := g.const_ident_type(node.value) { + if const_type !is types.Void && const_type !is types.Unknown { + return const_type + } + } + } + } + if node.kind == .struct_init && node.value.len > 0 { + // A variant literal (`SNull{}`) may carry the checker's expected-type + // propagation (the sum type itself); the literal names its own type. + lit_type := g.tc.parse_type(node.value) + if lit_type !is types.Unknown { + return lit_type + } + } + return actual_type +} + +// gen_sum_cast_expr emits sum cast expr output for c. +fn (mut g FlatGen) gen_sum_cast_expr(target_type types.SumType, inner_id flat.NodeId) { + inner := g.a.nodes[int(inner_id)] + actual_type := g.sum_cast_actual_type(inner_id) + actual_unaliased := cgen_unalias_type(actual_type) + if actual_unaliased is types.SumType && g.type_names_match(actual_unaliased, target_type) { + g.gen_expr(inner_id) + return + } + actual_clean := types.unwrap_pointer(actual_type) + variant_name0 := if inner.kind == .struct_init { + inner.value + } else { + actual_clean.name() + } + variant_name := g.sum_variant_for_actual(target_type.name, actual_type) or { + g.resolve_variant(target_type.name, variant_name0) + } + idx := g.sum_type_index(target_type.name, variant_name) + field := g.sum_field_name(variant_name) + ct := g.tc.c_type(target_type) + variant_type := g.tc.parse_type(variant_name) + clean_variant_type := select_receive_unalias_type(variant_type) + actual_value_type := select_receive_unalias_type(actual_type) + variant_is_pointer := clean_variant_type is types.Pointer + pointer_base_type := if clean_variant_type is types.Pointer { + clean_variant_type.base_type + } else { + types.Type(types.void_) + } + variant_is_pointer_arg := if actual_value_type is types.Pointer { + variant_is_pointer && g.type_names_match(actual_value_type.base_type, pointer_base_type) + } else { + false + } + if g.variant_references_sum(variant_name, target_type.name) { + inner_ct := g.value_c_type(variant_type) + if variant_is_pointer_arg { + g.write('(${ct}){.typ = ${idx}, .${field} = ') + if g.pointer_variant_expr_needs_heap_copy(inner_id) { + pointer_ct := g.value_c_type(pointer_base_type) + g.write('(${pointer_ct}*)memdup(') + g.gen_expr(inner_id) + g.write(', sizeof(${pointer_ct}))') + } else { + g.gen_expr(inner_id) + } + if variant_is_pointer && g.pointer_variant_expr_creates_owned_value(inner_id) { + g.write(', ._pointer_variant_is_owned = true') + } + g.write('}') + } else if inner.kind == .struct_init + && g.resolve_sum_name(inner.value) == g.resolve_sum_name(target_type.name) { + g.write('(${ct}){') + for si in 0 .. inner.children_count { + sf := g.a.child_node(&inner, si) + if si > 0 { + g.write(', ') + } + g.write('.${g.cname(sf.value)} = ') + g.gen_lowered_sum_field_value(target_type.name, sf) + } + g.write('}') + } else if inner.kind == .struct_init { + g.write('(${ct}){.typ = ${idx}, .${field} = (${inner_ct}*)memdup(&(${inner_ct}){') + for si in 0 .. inner.children_count { + sf := g.a.child_node(&inner, si) + if si > 0 { + g.write(', ') + } + g.write('.${g.cname(sf.value)} = ') + g.gen_expr(g.a.child(sf, 0)) + } + g.write('}, sizeof(${inner_ct}))}') + } else { + g.write('(${ct}){.typ = ${idx}, .${field} = (${inner_ct}*)memdup(') + g.gen_sum_variant_memdup_source(inner_id, variant_type) + g.write(', sizeof(${inner_ct}))') + if variant_is_pointer && g.pointer_variant_expr_creates_owned_value(inner_id) { + g.write(', ._pointer_variant_is_owned = true') + } + g.write('}') + } + } else { + g.write('(${ct}){.typ = ${idx}, .${field} = ') + if variant_is_pointer_arg { + g.write('*') + } + g.gen_expr(inner_id) + g.write('}') + } +} + +fn (mut g FlatGen) gen_sum_variant_memdup_source(value_id flat.NodeId, inner_type types.Type) { + if fixed := array_fixed_type(inner_type) { + source := g.fixed_array_runtime_copy_source_expr(value_id, fixed) + if trimmed_space(source).len > 0 { + g.write(source) + return + } + } + inner_ct := g.value_c_type(inner_type) + g.write('(${inner_ct}[]){') + g.gen_expr_with_expected_type(value_id, inner_type) + g.write('}') +} + +// pointer_variant_arg_needs_heap_copy supports pointer_variant_arg_needs_heap_copy handling in c. +fn (g &FlatGen) pointer_variant_arg_needs_heap_copy(node flat.Node) bool { + if node.kind != .prefix || node.op != .amp || node.children_count == 0 { + return false + } + child_id := g.a.child(&node, 0) + child := g.a.nodes[int(child_id)] + if child.kind != .ident { + return false + } + if _ := g.current_param_type(child.value) { + return true + } + if _ := g.current_param_map_type(child.value) { + return true + } + if _ := g.tc.cur_scope.lookup(child.value) { + return true + } + return false +} + +fn (g &FlatGen) pointer_variant_expr_needs_heap_copy(id flat.NodeId) bool { + mut expr_id := id + for int(expr_id) >= 0 && int(expr_id) < g.a.nodes.len { + node := g.a.nodes[int(expr_id)] + if node.kind in [.paren, .expr_stmt, .cast_expr] && node.children_count > 0 { + expr_id = g.a.child(&node, 0) + continue + } + return g.pointer_variant_arg_needs_heap_copy(node) + } + return false +} + +// pointer_variant_expr_creates_owned_value reports pointer expressions whose pointee is +// independently owned by the sum. Borrowed call/selector results remain unmarked. +fn (g &FlatGen) pointer_variant_expr_creates_owned_value(id flat.NodeId) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + if g.tc.ownership_expr_creates_owned_value(id) { + return true + } + mut expr_id := id + for int(expr_id) >= 0 { + node := g.a.nodes[int(expr_id)] + if node.kind in [.paren, .expr_stmt, .cast_expr] && node.children_count > 0 { + expr_id = g.a.child(&node, 0) + continue + } + if g.pointer_variant_expr_needs_heap_copy(expr_id) { + return true + } + if node.kind != .prefix || node.op != .amp || node.children_count == 0 { + return false + } + child := g.a.child_node(&node, 0) + return child.kind in [.struct_init, .assoc, .array_init, .array_literal] + } + return false +} + +// selector_declared_type supports selector declared type handling for FlatGen. +fn (g &FlatGen) selector_declared_type(id flat.NodeId) ?types.Type { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return none + } + node := g.a.nodes[int(id)] + if node.kind != .selector || node.children_count == 0 { + return none + } + base_id := g.a.child(&node, 0) + mut resolved_base_type := g.selector_base_expr_type(base_id) + base_node := g.a.nodes[int(base_id)] + if base_node.typ.len > 0 { + annotated_base_type := g.parse_node_type(&base_node) + if annotated_base_type !is types.Unknown && annotated_base_type !is types.Void + && !g.type_contains_generic_placeholder(annotated_base_type) + && (resolved_base_type is types.Unknown + || resolved_base_type is types.Void + || g.type_contains_generic_placeholder(resolved_base_type)) { + resolved_base_type = annotated_base_type + } + } + base_type0 := types.unwrap_pointer(resolved_base_type) + base_type := if base_type0 is types.Alias { base_type0.base_type } else { base_type0 } + if base_type is types.Struct { + return g.struct_field_type(base_type.name, node.value) + } + return none +} + +fn (g &FlatGen) selector_base_expr_type(id flat.NodeId) types.Type { + if int(id) >= 0 && int(id) < g.a.nodes.len { + node := g.a.nodes[int(id)] + if node.kind == .or_expr && node.children_count > 0 { + source_id := g.a.child(&node, 0) + source_type := g.or_expr_source_type(source_id, g.a.nodes[int(source_id)]) + if source_type is types.OptionType { + return source_type.base_type + } + if source_type is types.ResultType { + return source_type.base_type + } + } + } + return g.usable_expr_type(id) +} + +fn (g &FlatGen) sum_type_name_for_type(base_type0 types.Type) ?string { + mut clean := types.unwrap_pointer(base_type0) + if clean is types.Alias { + clean = clean.base_type + } + if clean is types.SumType { + for candidate in [g.shared_qualify_type_text(clean.name, g.tc.cur_module), clean.name] { + sum_name := g.resolve_sum_name(candidate) + if sum_name in g.tc.sum_types { + return sum_name + } + } + } + if clean is types.Struct { + for candidate in [g.shared_qualify_type_text(clean.name, g.tc.cur_module), clean.name] { + if candidate in g.tc.sum_types { + return candidate + } + if !candidate.contains('.') { + sum_name := g.resolve_sum_name(candidate) + if sum_name in g.tc.sum_types { + return sum_name + } + } + } + } + return none +} + +fn (g &FlatGen) sum_shared_field_type(base_type0 types.Type, field string) ?types.Type { + return g.sum_shared_field_type_inner(base_type0, field, []string{}) +} + +fn (g &FlatGen) sum_shared_field_type_inner(base_type0 types.Type, field string, seen []string) ?types.Type { + sum_name := g.sum_type_name_for_type(base_type0) or { return none } + if sum_name in seen { + return none + } + variants := g.tc.sum_types[sum_name] or { return none } + if variants.len == 0 { + return none + } + mut common_type := types.Type(types.void_) + mut has_common := false + mut next_seen := seen.clone() + next_seen << sum_name + for variant in variants { + variant_field_type := g.sum_variant_shared_field_type(variant, field, next_seen) or { + return none + } + if !has_common { + common_type = variant_field_type + has_common = true + continue + } + if g.tc.c_type(variant_field_type) != g.tc.c_type(common_type) { + return none + } + } + if g.tc.c_type(common_type) == 'void' { + return none + } + return common_type +} + +fn (g &FlatGen) sum_variant_shared_field_type(variant string, field string, seen []string) ?types.Type { + if variant_field_type := g.usable_struct_field_type(variant, field) { + return variant_field_type + } + if variant_field_type := g.struct_promoted_field_type(variant, field) { + return variant_field_type + } + if nested_sum := g.sum_type_name_for_type(g.tc.parse_type(variant)) { + return g.sum_shared_field_type_inner(g.tc.parse_type(nested_sum), field, seen) + } + return none +} + +fn (g &FlatGen) struct_promoted_field_type(type_name string, field string) ?types.Type { + path := g.embedded_field_path_for_promoted_field(type_name, field) or { return none } + if path.len == 0 { + return none + } + owner := g.embedded_field_type_name(path[path.len - 1]) + if owner.len == 0 { + return none + } + return g.usable_struct_field_type(owner, field) +} + +fn (g &FlatGen) struct_promoted_field_suffix(type_name string, field string, initial_ptr bool) ?string { + path := g.embedded_field_path_for_promoted_field(type_name, field) or { return none } + if path.len == 0 { + return none + } + mut suffix := '' + mut is_ptr := initial_ptr + for embedded in path { + suffix += if is_ptr { '->' } else { '.' } + suffix += g.cname(embedded.name) + is_ptr = embedded.typ is types.Pointer || cgen_unalias_type(embedded.typ) is types.Pointer + } + suffix += if is_ptr { '->' } else { '.' } + suffix += g.cname(field) + return suffix +} + +fn (g &FlatGen) sum_unique_variant_field_info(base_type0 types.Type, field string) ?SumUniqueFieldInfo { + sum_name := g.sum_type_name_for_type(base_type0) or { return none } + return g.sum_unique_variant_field_info_inner(sum_name, field, []string{}) +} + +fn (g &FlatGen) sum_unique_variant_field_info_inner(sum_name string, field string, seen []string) ?SumUniqueFieldInfo { + if sum_name in seen { + return none + } + variants := g.tc.sum_types[sum_name] or { return none } + mut found := SumUniqueFieldInfo{} + mut found_count := 0 + for variant0 in variants { + variant := g.resolve_variant(sum_name, variant0) + variant_field_type := g.struct_field_type(variant, field) or { continue } + found = SumUniqueFieldInfo{ + variant: variant + typ: variant_field_type + } + found_count++ + if found_count > 1 { + return none + } + } + if found_count == 1 { + return found + } + return none +} + +fn (mut g FlatGen) gen_sum_unique_variant_field_selector(base_id flat.NodeId, base_type0 types.Type, field string) bool { + info := g.sum_unique_variant_field_info(base_type0, field) or { return false } + sum_field := g.sum_field_name(info.variant) + g.write('(') + g.gen_expr(base_id) + g.write(')') + if base_type0 is types.Pointer { + g.write('->') + } else { + g.write('.') + } + g.write('${sum_field}->${c_field_name(field)}') + return true +} + +fn (mut g FlatGen) gen_pointer_pointer_struct_selector(base_id flat.NodeId, base_type0 types.Type, field string) bool { + if base_type0 !is types.Pointer { + return false + } + inner_ptr := (base_type0 as types.Pointer).base_type + + if inner_ptr !is types.Pointer { + return false + } + inner_base := (inner_ptr as types.Pointer).base_type + + struct_type := types.unwrap_pointer(inner_base) + if struct_type !is types.Struct { + return false + } + struct_name := (struct_type as types.Struct).name + base := g.a.nodes[int(base_id)] + base_is_mut_pointer_param := base.kind == .ident && g.current_param_is_mut_pointer(base.value) + + if _ := g.struct_field_type(struct_name, field) { + g.write('(*(') + if base_is_mut_pointer_param { + g.gen_mut_pointer_slot_expr(base_id) + } else { + g.gen_expr(base_id) + } + g.write('))->${g.cname(field)}') + return true + } + if embedded_path := g.embedded_field_path_for_promoted_selector(inner_base, field) { + g.write('(*(') + if base_is_mut_pointer_param { + g.gen_mut_pointer_slot_expr(base_id) + } else { + g.gen_expr(base_id) + } + g.write('))') + for embedded in embedded_path { + g.write('->${g.cname(embedded.name)}') + } + g.write('.${g.cname(field)}') + return true + } + return false +} + +fn (mut g FlatGen) gen_sum_shared_field_selector(base_id flat.NodeId, base_type0 types.Type, field string) bool { + sum_name := g.sum_type_name_for_type(base_type0) or { return false } + common_type := g.sum_shared_field_type(base_type0, field) or { return false } + ct := g.value_c_type(common_type) + sum_ct := g.tc.c_type(g.interface_concrete_type(sum_name)) + g.write('({ ${sum_ct} __sum = ') + if base_type0 is types.Pointer { + g.write('*(') + g.gen_expr(base_id) + g.write(')') + } else { + g.gen_expr(base_id) + } + g.writeln('; ${ct} __field = {0};') + g.gen_sum_shared_field_switch('__sum', sum_name, field, []string{}) + g.write('__field; })') + return true +} + +fn (g &FlatGen) pointer_pointer_selector_base_type(base &flat.Node, fallback types.Type) types.Type { + if base.kind == .ident { + if local_type := g.local_ident_type(base.value) { + return local_type + } + } + return fallback +} + +fn (mut g FlatGen) gen_sum_type_tag_selector(base_id flat.NodeId, base_type0 types.Type, op flat.Op) bool { + sum_name := g.sum_type_name_for_type(base_type0) or { return false } + sum_ct := g.tc.c_type(g.interface_concrete_type(sum_name)) + g.write('({ ${sum_ct} __sum = ') + if op == .arrow || base_type0 is types.Pointer { + g.write('*(') + g.gen_expr(base_id) + g.write(')') + } else { + g.gen_expr(base_id) + } + g.write('; __sum.typ; })') + return true +} + +fn cgen_type_pointer_depth(t types.Type) int { + mut cur := t + mut depth := 0 + for _ in 0 .. 32 { + if cur is types.Alias { + cur = cur.base_type + continue + } + if cur is types.Pointer { + depth++ + cur = cur.base_type + continue + } + break + } + return depth +} + +fn cgen_unalias_unwrap_all_pointers(t types.Type) types.Type { + mut cur := t + for _ in 0 .. 32 { + if cur is types.Alias { + cur = cur.base_type + continue + } + if cur is types.Pointer { + cur = cur.base_type + continue + } + break + } + return cur +} + +fn cgen_c_type_pointer_depth(ct string) int { + mut depth := 0 + for ch in ct { + if ch == `*` { + depth++ + } + } + return depth +} + +fn (mut g FlatGen) gen_is_expr_subject(expr_id flat.NodeId, extra_deref int) { + for _ in 0 .. extra_deref { + g.write('(*') + } + g.gen_expr(expr_id) + for _ in 0 .. extra_deref { + g.write(')') + } +} + +fn (mut g FlatGen) gen_sum_shared_field_switch(sum_var string, sum_name string, field string, seen []string) { + if sum_name in seen { + return + } + variants := g.tc.sum_types[sum_name] or { return } + mut next_seen := seen.clone() + next_seen << sum_name + g.writeln('switch (${sum_var}.typ) {') + for variant in variants { + idx := g.sum_type_index(sum_name, variant) + sum_field := g.sum_field_name(variant) + if _ := g.struct_field_type(variant, field) { + g.writeln('case ${idx}: if (${sum_var}.${sum_field} != NULL) __field = ${sum_var}.${sum_field}->${c_field_name(field)}; break;') + } else if suffix := g.struct_promoted_field_suffix(variant, field, true) { + g.writeln('case ${idx}: if (${sum_var}.${sum_field} != NULL) __field = ${sum_var}.${sum_field}${suffix}; break;') + } else if nested_sum := g.sum_type_name_for_type(g.tc.parse_type(variant)) { + nested_ct := g.tc.c_type(g.tc.parse_type(nested_sum)) + nested_var := '__nested_sum_${next_seen.len}' + g.writeln('case ${idx}: if (${sum_var}.${sum_field} != NULL) { ${nested_ct} ${nested_var} = *${sum_var}.${sum_field};') + g.gen_sum_shared_field_switch(nested_var, nested_sum, field, next_seen) + g.writeln('} break;') + } + } + g.writeln('default: break; }') +} + +fn (g &FlatGen) c_typedef_cast_call_name(node flat.Node) string { + if node.kind != .call || node.children_count == 0 { + return '' + } + callee := g.a.child_node(&node, 0) + match callee.kind { + .ident { + if callee.value.contains('__') { + return callee.value + } + } + .selector { + if callee.children_count > 0 { + base := g.a.child_node(callee, 0) + if base.kind == .ident && base.value == 'C' { + return callee.value + } + } + } + else {} + } + + return '' +} + +// gen_expr_with_possible_enum_type emits expr with possible enum type output for c. +fn (mut g FlatGen) gen_expr_with_possible_enum_type(id flat.NodeId, expected types.Type) { + node := g.a.nodes[int(id)] + mut is_signed_numeric_literal := false + if node.kind == .prefix && node.op in [.minus, .plus] && node.children_count > 0 { + child := g.a.child_node(&node, 0) + is_signed_numeric_literal = child.kind in [.int_literal, .float_literal] + } + if expected is types.Enum || node.kind in [.int_literal, .float_literal] + || is_signed_numeric_literal { + g.gen_expr_with_expected_type(id, expected) + return + } + g.gen_expr(id) +} + +fn (g &FlatGen) expected_expr_is_optional_struct() bool { + if g.expected_expr_type is types.Struct { + return g.expected_expr_type.name.starts_with('Optional') + } + return false +} + +fn (mut g FlatGen) type_name_c_type(type_name string) string { + if _ := g.tc.cur_scope.lookup(type_name) { + return g.cname(type_name) + } + if type_name.starts_with('fn_ptr:') { + return g.resolve_fn_ptr_type(type_name) + } + t := g.tc.parse_type(type_name) + ct := if t is types.OptionType || t is types.ResultType { + g.optional_type_name(t) + } else if t is types.Enum { + g.enum_value_c_type(t) + } else { + g.tc.c_type(t) + } + if ct.starts_with('fn_ptr:') { + return g.resolve_fn_ptr_type(ct) + } + return ct +} + +fn (mut g FlatGen) sizeof_target(value string) string { + if value.starts_with('fn_ptr:') { + return g.resolve_fn_ptr_type(value) + } + // Transformer-produced fixed-array names use postfix dimensions. For an + // array of pointers `[N]&Elem`, that canonical spelling is `&Elem[N]`. + // Keep the pointer on the element when forming a C type declarator. + if value.starts_with('&') && value.ends_with(']') { + parsed_pointer := g.tc.parse_type(value) + if parsed_pointer is types.Pointer { + if fixed := array_fixed_type(parsed_pointer.base_type) { + c_elem, dims := g.fixed_array_decl_parts(fixed) + return '${c_elem}*${dims}' + } + } + } + // Canonical fixed-array pointer element spellings can reach sizeof as + // `Elem[N]*`; C declares an array of pointers as `Elem*[N]`. + if value.ends_with('*') { + parsed_array := g.tc.parse_type(value[..value.len - 1]) + if fixed := array_fixed_type(parsed_array) { + c_elem, dims := g.fixed_array_decl_parts(fixed) + return '${c_elem}*${dims}' + } + } + if value.starts_with('&') { + return '${g.sizeof_target(value[1..].trim_space())}*' + } + if value.starts_with('[]') || value == 'array' { + return 'Array' + } + if fixed_target := c_fixed_array_typedef_sizeof_target(value) { + return fixed_target + } + // Values of explicitly backed enums use their declared C typedef instead of the + // integer ABI type. `sizeof` must therefore follow value storage semantics too. + parsed := g.tc.parse_type(value) + if parsed is types.Enum { + return g.value_sizeof_target(parsed) + } + // An exact registered type name remains a type even when its module qualifier is + // shadowed by a local (for example `sizeof(hash.Hash)` inside `mut hash := h()`). + // Only unresolved dotted spellings should fall through to selector lookup below. + if (value in g.tc.structs || value in g.tc.interface_names || value in g.tc.sum_types + || value in g.tc.type_aliases) && (parsed is types.Struct + || parsed is types.Interface || parsed is types.SumType + || parsed is types.Alias) { + return g.value_sizeof_target(parsed) + } + // A dotted `sizeof` target can be either a qualified type (`time.Time`) or a + // selector expression (`bf.p`). Resolve visible values before interpreting the + // spelling as a type; parse_type accepts both shapes and cannot disambiguate them. + if value.contains('.') { + parts := value.split('.') + if parts.len > 1 { + if g.cur_scope_has_local_name(parts[0]) { + return sizeof_selector_target(parts[0], parts[1..]) + } + if global := g.sizeof_global_selector_base(parts[0]) { + return sizeof_selector_target(global, parts[1..]) + } + } + } + if (value.contains('.') || value in g.tc.structs || value in g.tc.interface_names + || value in g.tc.sum_types || value in g.tc.type_aliases) && (parsed is types.Struct + || parsed is types.Interface || parsed is types.SumType + || parsed is types.Alias) { + return g.value_sizeof_target(parsed) + } + if fixed := array_fixed_type(g.tc.parse_type(value)) { + c_elem, dims := g.fixed_array_decl_parts(fixed) + return '${c_elem}${dims}' + } + return g.type_name_c_type(value) +} + +fn c_fixed_array_typedef_sizeof_target(value string) ?string { + if !value.starts_with('Array_fixed_') { + return none + } + payload := value['Array_fixed_'.len..] + if !payload.contains('_') { + return none + } + elem := payload.all_before_last('_') + len := payload.all_after_last('_') + if elem.len == 0 || len.len == 0 { + return none + } + return '${elem}[${len}]' +} + +fn sizeof_selector_target(base string, fields []string) string { + mut expr := c_name(base) + for field in fields { + expr += '.${c_field_name(field)}' + } + return expr +} + +fn (g &FlatGen) cur_scope_has_local_name(name string) bool { + mut scope := g.tc.cur_scope + for scope != unsafe { nil } && voidptr(scope) != voidptr(g.tc.file_scope) { + $if !ownership ? { + if name in scope.name_indexes { + return true + } + } $else { + for existing in scope.names { + if existing == name { + return true + } + } + } + scope = scope.parent + } + return false +} + +fn (g &FlatGen) sizeof_global_selector_base(name string) ?string { + if name.len == 0 || name.contains('.') { + return none + } + current_qname := qualify_name_in_module(g.tc.cur_module, name) + if current_qname in g.global_types { + return current_qname + } + if mod := g.global_modules[name] { + if mod.len == 0 || mod == 'main' || mod == 'builtin' || mod == g.tc.cur_module { + return if mod.len > 0 && mod != 'main' && mod != 'builtin' { + '${mod}.${name}' + } else { + name + } + } + } + return none +} + +// optional_none_type supports optional none type handling for FlatGen. +fn (mut g FlatGen) optional_none_type(id flat.NodeId) types.Type { + expected := optional_result_unalias_type(g.expected_expr_type) + if expected is types.OptionType || expected is types.ResultType { + return expected + } + if int(id) >= 0 && int(id) < g.a.nodes.len { + node := g.a.nodes[int(id)] + if node.typ.starts_with('?') || node.typ.starts_with('!') { + return g.parse_node_type(&node) + } + } + if typ := g.tc.expr_type(id) { + if typ is types.OptionType || typ is types.ResultType { + return typ + } + } + if g.cur_fn_ret_is_optional { + return g.cur_fn_ret + } + return types.Type(types.OptionType{ + base_type: types.Type(types.void_) + }) +} + +// array_index_info supports array index info handling for c. +fn array_index_info(t types.Type) (bool, bool, types.Array) { + if t is types.Array { + return true, false, t + } + if t is types.Alias { + base := t.base_type + if base is types.Array { + return true, false, base + } + } + if t is types.Pointer { + base := t.base_type + if base is types.Array { + return true, true, base + } + if base is types.Alias { + alias_base := base.base_type + if alias_base is types.Array { + return true, true, alias_base + } + } + } + return false, false, types.Array{} +} + +// valid_node_id supports valid node id handling for FlatGen. +fn (g &FlatGen) valid_node_id(id flat.NodeId) bool { + return g.a != unsafe { nil } && int(id) >= 0 && int(id) < g.a.nodes.len +} + +// const_storage_name supports const storage name handling for FlatGen. +fn (g &FlatGen) const_storage_name(module_name string, name string) string { + if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' + && !name.contains('.') { + return '${module_name}.${name}' + } + return name +} + +// const_primary_name supports const primary name handling for FlatGen. +fn (g &FlatGen) const_primary_name(name string) string { + mod := if name in g.const_modules { g.const_modules[name] } else { '' } + qname := g.const_storage_name(mod, name) + if qname != name && qname in g.const_vals { + return qname + } + return name +} + +// is_const_alias_name reports whether is const alias name applies in c. +fn (g &FlatGen) is_const_alias_name(name string) bool { + return g.const_primary_name(name) != name +} + +// const_ref_name supports const ref name handling for FlatGen. +fn (g &FlatGen) const_ref_name(name string) string { + if !name.contains('.') { + cur_qname := g.const_storage_name(g.tc.cur_module, name) + if cur_qname in g.const_vals { + return cur_qname + } + if name in g.const_vals { + mod := g.const_modules[name] or { '' } + if mod.len == 0 || mod == g.tc.cur_module || mod == 'builtin' + || (g.tc.cur_module in ['', 'main', 'builtin'] && mod in ['', 'main', 'builtin']) { + return g.const_primary_name(name) + } + } + if !name.contains('__') { + if unique := g.unique_const_ref_name(name) { + return unique + } + return '' + } + } + if name in g.const_vals { + return g.const_primary_name(name) + } + if name.contains('.') { + if name in g.const_vals { + return g.const_primary_name(name) + } + } + if name.contains('__') { + dotted := name.replace('__', '.') + if dotted in g.const_vals { + return g.const_primary_name(dotted) + } + } + sep := if name.contains('.') { + '.' + } else if name.contains('__') { + '__' + } else { + return '' + } + short_name := name.all_after_last(sep) + if short_name !in g.const_vals { + return '' + } + resolved := g.const_primary_name(short_name) + mod := if resolved in g.const_modules { g.const_modules[resolved] } else { '' } + if mod.len == 0 { + return resolved + } + ref_mod := name.all_before_last(sep) + if ref_mod == mod || ref_mod == mod.all_after_last('.') { + return resolved + } + return '' +} + +fn (g &FlatGen) unique_const_ref_name(short_name string) ?string { + // Iterating every const per query cost ~70us a call; the short-name index + // is built once (const_vals is complete after collect_gen_info) and maps a + // short name to its unique primary const ('' = ambiguous). + if isnil(g.const_short_index) { + return g.unique_const_ref_name_scan(short_name) + } + mut idx := g.const_short_index + if !idx.built { + idx.built = true + for name, _ in g.const_vals { + if !name.contains('.') { + continue + } + short := name.all_after_last('.') + resolved := g.const_primary_name(name) + if existing := idx.entries[short] { + if existing != resolved { + idx.entries[short] = '' + } + continue + } + idx.entries[short] = resolved + } + } + found := idx.entries[short_name] or { return none } + if found.len == 0 { + return none + } + return found +} + +fn (g &FlatGen) unique_const_ref_name_scan(short_name string) ?string { + mut found := '' + for name, _ in g.const_vals { + if !name.contains('.') || name.all_after_last('.') != short_name { + continue + } + resolved := g.const_primary_name(name) + if found.len > 0 && found != resolved { + return none + } + found = resolved + } + if found.len == 0 { + return none + } + return found +} + +// const_ref_name_from_node converts const ref name from node data for c. +fn (g &FlatGen) const_ref_name_from_node(node flat.Node) string { + if node.kind == .paren && node.children_count > 0 { + return g.const_ref_name_from_node(g.a.child_node(&node, 0)) + } + if node.kind == .ident { + return g.const_ref_name(node.value) + } + if node.kind == .selector && node.children_count > 0 { + base := g.a.child_node(&node, 0) + if base.kind == .ident { + return g.const_ref_name('${base.value}.${node.value}') + } + } + return '' +} + +fn (g &FlatGen) build_unique_const_ref_names() map[string]string { + mut unique_index := map[string]string{} + for name, _ in g.const_vals { + if !name.contains('.') { + continue + } + short_name := name.all_after_last('.') + resolved := g.const_primary_name(name) + if short_name in unique_index { + if unique_index[short_name] != resolved { + unique_index[short_name] = '' + } + } else { + unique_index[short_name] = resolved + } + } + return unique_index +} + +fn (g &FlatGen) const_ref_name_fast_for_collect(name string, unique_index map[string]string) string { + if name.contains('.') || name.contains('__') { + return g.const_ref_name(name) + } + cur_qname := g.const_storage_name(g.tc.cur_module, name) + if cur_qname in g.const_vals { + return cur_qname + } + if name in g.const_vals { + mod := g.const_modules[name] or { '' } + if mod.len == 0 || mod == g.tc.cur_module || mod == 'builtin' + || (g.tc.cur_module in ['', 'main', 'builtin'] && mod in ['', 'main', 'builtin']) { + return g.const_primary_name(name) + } + } + if name in unique_index { + return unique_index[name] + } + return '' +} + +fn (g &FlatGen) const_ref_name_from_node_cached_for_collect(node flat.Node, unique_index map[string]string, mut cache map[string]string) string { + if node.kind == .paren { + if node.children_count == 0 { + return '' + } + return g.const_ref_name_from_node_cached_for_collect(g.a.child_node(&node, 0), + unique_index, mut cache) + } + if node.kind == .ident { + cache_key := '${g.tc.cur_module}|ident|${node.value}' + if cache_key in cache { + return cache[cache_key] + } + const_name := g.const_ref_name_fast_for_collect(node.value, unique_index) + cache[cache_key] = const_name + return const_name + } + if node.kind == .selector { + if node.children_count == 0 { + return '' + } + base := g.a.child_node(&node, 0) + if base.kind != .ident { + return '' + } + cache_key := '${g.tc.cur_file}|${g.tc.cur_module}|selector|${base.value}|${node.value}' + if cache_key in cache { + return cache[cache_key] + } + resolved_base := g.import_alias_module(base.value) or { base.value } + const_name := g.const_ref_name_fast_for_collect('${resolved_base}.${node.value}', + unique_index) + cache[cache_key] = const_name + return const_name + } + return '' +} + +@[direct_array_access] +fn (g &FlatGen) fixed_storage_candidate_short_name(name string) string { + mut sep := -1 + mut i := name.len - 1 + for i >= 0 { + if name[i] == `.` { + return unsafe { name.substr_unsafe(i + 1, name.len) } + } + if sep < 0 && i > 0 && name[i] == `_` && name[i - 1] == `_` { + sep = i - 1 + i-- + } + i-- + } + if sep >= 0 { + return unsafe { name.substr_unsafe(sep + 2, name.len) } + } + return name +} + +fn (g &FlatGen) add_fixed_storage_candidate_ref(name string, mut refs map[string]bool, mut shorts map[string]bool) { + if name.len == 0 { + return + } + refs[name] = true + short_name := g.fixed_storage_candidate_short_name(name) + if short_name.len > 0 { + shorts[short_name] = true + } +} + +fn (mut g FlatGen) collect_fixed_storage_const_candidates(mut candidates map[string]bool, mut refs map[string]bool, mut shorts map[string]bool, mut storage_cache map[string]bool, mut primary_cache map[string]string) { + for const_name, _ in g.const_vals { + if !g.const_ref_has_fixed_array_literal_storage(const_name, mut storage_cache) { + continue + } + primary := g.const_primary_name_cached(const_name, mut primary_cache) + candidates[primary] = true + g.add_fixed_storage_candidate_ref(const_name, mut refs, mut shorts) + g.add_fixed_storage_candidate_ref(primary, mut refs, mut shorts) + } +} + +struct FixedStorageCandidateNameFilter { +mut: + lengths_by_initial [256]u64 +} + +struct FixedStorageNodeScanArgs { + g &FlatGen + start int + end int + file string + module string + fixed_candidate_idents map[string]bool + fixed_candidate_shorts map[string]bool + ident_filter &FixedStorageCandidateNameFilter + short_filter &FixedStorageCandidateNameFilter +mut: + address_items []FixedStorageConstRefItem + ref_items []FixedStorageConstRefItem + call_base_items []FixedStorageConstRefItem + index_base_items []FixedStorageConstRefItem + fixed_safe_refs map[int]bool +} + +@[direct_array_access] +fn fixed_storage_candidate_name_filter(names map[string]bool) FixedStorageCandidateNameFilter { + mut filter := FixedStorageCandidateNameFilter{} + for name, _ in names { + if name.len == 0 { + continue + } + length_bit := if name.len < 63 { name.len } else { 63 } + filter.lengths_by_initial[name[0]] |= u64(1) << length_bit + } + return filter +} + +@[direct_array_access; inline] +fn (filter &FixedStorageCandidateNameFilter) may_match(name string) bool { + if name.len == 0 { + return false + } + length_bit := if name.len < 63 { name.len } else { 63 } + return filter.lengths_by_initial[name[0]] & (u64(1) << length_bit) != 0 +} + +fn (g &FlatGen) const_ref_node_may_match_fixed_candidate(node &flat.Node, ident_refs map[string]bool, shorts map[string]bool, ident_filter &FixedStorageCandidateNameFilter, short_filter &FixedStorageCandidateNameFilter) bool { + if node.kind == .paren { + if node.children_count == 0 { + return false + } + return g.const_ref_node_may_match_fixed_candidate(g.a.child_node(node, 0), ident_refs, + shorts, ident_filter, short_filter) + } + if node.kind == .ident { + if ident_filter.may_match(node.value) && node.value in ident_refs { + return true + } + short_name := g.fixed_storage_candidate_short_name(node.value) + if short_name.len != node.value.len { + return short_filter.may_match(short_name) && short_name in shorts + } + return false + } + if node.kind == .selector { + if node.children_count == 0 { + return false + } + if !short_filter.may_match(node.value) || node.value !in shorts { + return false + } + base := g.a.child_node(node, 0) + if base.kind != .ident { + return false + } + return true + } + return false +} + +fn (g &FlatGen) const_primary_name_cached(name string, mut cache map[string]string) string { + if name in cache { + return cache[name] + } + primary := g.const_primary_name(name) + cache[name] = primary + return primary +} + +fn (mut g FlatGen) const_ref_has_fixed_array_literal_storage(const_name string, mut cache map[string]bool) bool { + if const_name in cache { + return cache[const_name] + } + mut has_fixed := false + if val_id := g.const_vals[const_name] { + if _ := g.const_array_literal_storage_type_for_name(const_name, val_id, g.const_value_type(const_name, + val_id)) + { + has_fixed = true + } + } + cache[const_name] = has_fixed + return has_fixed +} + +fn (g &FlatGen) fixed_storage_candidate_primary_from_matched_node_for_collect(node flat.Node, fixed_storage_candidates map[string]bool, unique_index map[string]string, mut ref_cache map[string]string, mut primary_cache map[string]string) string { + const_name := g.const_ref_name_from_node_cached_for_collect(node, unique_index, mut ref_cache) + if const_name.len == 0 { + return '' + } + primary := g.const_primary_name_cached(const_name, mut primary_cache) + if primary !in fixed_storage_candidates { + return '' + } + return primary +} + +fn (mut g FlatGen) const_address_can_force_fixed_storage(const_name string) bool { + val_id := g.const_vals[const_name] or { return false } + const_type := cgen_unalias_type(g.const_value_type(const_name, val_id)) + return const_type !is types.Array && const_type !is types.Unknown && const_type !is types.Void +} + +fn fixed_storage_node_scan_thread(arg voidptr) voidptr { + mut scan := unsafe { &FixedStorageNodeScanArgs(arg) } + scan.g.scan_fixed_storage_node_range(mut scan) + return unsafe { nil } +} + +fn (g &FlatGen) scan_fixed_storage_node_range(mut scan FixedStorageNodeScanArgs) { + mut cur_module := scan.module + mut cur_file := scan.file + for idx := scan.start; idx < scan.end; idx++ { + node := unsafe { &g.a.nodes[idx] } + kind_id := int(node.kind) + if kind_id == 77 { + cur_file = node.value + cur_module = 'main' + continue + } + if kind_id == 73 { + cur_module = node.value + continue + } + match node.kind { + .prefix { + if node.op == .amp && node.children_count > 0 { + scan.address_items << FixedStorageConstRefItem{ + id: g.a.child(node, 0) + file: cur_file + module: cur_module + } + } + } + .index { + if node.children_count == 0 { + continue + } + base_id := g.a.child(node, 0) + base_node := g.a.node(base_id) + if g.const_ref_node_may_match_fixed_candidate(base_node, + scan.fixed_candidate_idents, scan.fixed_candidate_shorts, scan.ident_filter, + scan.short_filter) + { + g.mark_const_ref_descendants(mut scan.fixed_safe_refs, base_id) + scan.index_base_items << FixedStorageConstRefItem{ + id: base_id + file: cur_file + module: cur_module + } + } + } + .selector { + if node.value == 'len' && node.children_count > 0 { + base_id := g.a.child(node, 0) + base_node := g.a.node(base_id) + if g.const_ref_node_may_match_fixed_candidate(base_node, + scan.fixed_candidate_idents, scan.fixed_candidate_shorts, + scan.ident_filter, scan.short_filter) + { + g.mark_const_ref_descendants(mut scan.fixed_safe_refs, base_id) + } + } + if g.const_ref_node_may_match_fixed_candidate(node, scan.fixed_candidate_idents, + scan.fixed_candidate_shorts, scan.ident_filter, scan.short_filter) + { + scan.ref_items << FixedStorageConstRefItem{ + id: flat.NodeId(idx) + file: cur_file + module: cur_module + } + } + } + .call { + if node.children_count == 0 { + continue + } + fn_node := g.a.child_node(node, 0) + if fn_node.kind == .selector && fn_node.children_count > 0 { + base_id := g.a.child(fn_node, 0) + base_node := g.a.node(base_id) + if g.const_ref_node_may_match_fixed_candidate(base_node, + scan.fixed_candidate_idents, scan.fixed_candidate_shorts, + scan.ident_filter, scan.short_filter) + { + scan.call_base_items << FixedStorageConstRefItem{ + id: base_id + file: cur_file + module: cur_module + } + } + } + // A const passed as a plain call argument (e.g. the lowered + // `array_get(const, i)` from a containment loop) needs dynamic + // array representation even when another use site aliases the + // same node as an index or `.len` base and marked it safe. + for ai in 1 .. node.children_count { + arg_id := g.a.child(node, ai) + arg_node := g.a.node(arg_id) + if g.const_ref_node_may_match_fixed_candidate(arg_node, + scan.fixed_candidate_idents, scan.fixed_candidate_shorts, + scan.ident_filter, scan.short_filter) + { + scan.call_base_items << FixedStorageConstRefItem{ + id: arg_id + file: cur_file + module: cur_module + } + } + } + } + .ident, .paren { + if g.const_ref_node_may_match_fixed_candidate(node, scan.fixed_candidate_idents, + scan.fixed_candidate_shorts, scan.ident_filter, scan.short_filter) + { + scan.ref_items << FixedStorageConstRefItem{ + id: flat.NodeId(idx) + file: cur_file + module: cur_module + } + } + } + else {} + } + } +} + +fn (g &FlatGen) fixed_storage_node_scan_bounds(max_jobs int) []int { + mut bounds := []int{cap: max_jobs + 1} + bounds << 0 + for job in 1 .. max_jobs { + bounds << g.a.nodes.len * job / max_jobs + } + bounds << g.a.nodes.len + return bounds +} + +fn (mut g FlatGen) collect_fixed_storage_consts(allow_parallel bool) { + // Cached module headers deliberately materialize inferred array constants as + // dynamic arrays. Keep cached objects on the same ABI: promoting one of those + // constants to a C fixed array would make warm users read an Array header as + // element storage. + if g.cache_split { + return + } + mut fssw := time.new_stopwatch() + old_module := g.tc.cur_module + old_file := g.tc.cur_file + mut fixed_storage_candidates := map[string]bool{} + mut fixed_candidate_refs := map[string]bool{} + mut fixed_candidate_shorts := map[string]bool{} + mut dynamic_uses := map[string]bool{} + mut indexed_candidates := map[string]bool{} + mut fixed_safe_refs := map[int]bool{} + mut fixed_storage_cache := map[string]bool{} + mut primary_name_cache := map[string]string{} + g.collect_fixed_storage_const_candidates(mut fixed_storage_candidates, mut + fixed_candidate_refs, mut fixed_candidate_shorts, mut fixed_storage_cache, mut + primary_name_cache) + unique_const_ref_names := g.build_unique_const_ref_names() + mut const_ref_name_cache := map[string]string{} + mut address_items := []FixedStorageConstRefItem{} + mut ref_items := []FixedStorageConstRefItem{} + mut call_base_items := []FixedStorageConstRefItem{} + mut index_base_items := []FixedStorageConstRefItem{} + mut fixed_candidate_idents := fixed_candidate_refs.clone() + for short_name, _ in fixed_candidate_shorts { + fixed_candidate_idents[short_name] = true + } + fixed_candidate_ident_filter := fixed_storage_candidate_name_filter(fixed_candidate_idents) + fixed_candidate_short_filter := fixed_storage_candidate_name_filter(fixed_candidate_shorts) + g.timing_profile(' [ttime] fs setup ${f64(fssw.elapsed().microseconds()) / 1000.0:7.2f} ms (candidates: ${fixed_storage_candidates.len})') + fssw.restart() + mut scan_jobs := if !isnil(g.a.worker_pool) { g.a.worker_pool.size() + 1 } else { 2 } + if scan_jobs > 8 { + scan_jobs = 8 + } + bounds := if allow_parallel && os.getenv('V3_NO_PAR_FIXED_STORAGE_SCAN') == '' { + g.fixed_storage_node_scan_bounds(scan_jobs) + } else { + [0, g.a.nodes.len] + } + mut scans := []FixedStorageNodeScanArgs{cap: bounds.len - 1} + for i in 0 .. bounds.len - 1 { + start := bounds[i] + mut scan_file := '' + if start < g.a.nodes.len { + if source_file := g.a.source_files[g.a.nodes[start].pos.id] { + scan_file = source_file.name + } + } + scans << FixedStorageNodeScanArgs{ + g: g + start: start + end: bounds[i + 1] + file: scan_file + module: g.tc.file_modules[scan_file] or { 'main' } + fixed_candidate_idents: fixed_candidate_idents + fixed_candidate_shorts: fixed_candidate_shorts + ident_filter: &fixed_candidate_ident_filter + short_filter: &fixed_candidate_short_filter + fixed_safe_refs: map[int]bool{} + } + } + mut scan_threads := []thread voidptr{cap: scans.len - 1} + for scan_idx in 1 .. scans.len { + scan_threads << spawn fixed_storage_node_scan_thread(unsafe { voidptr(&scans[scan_idx]) }) + } + g.scan_fixed_storage_node_range(mut scans[0]) + scan_threads.wait() + for scan in scans { + address_items << scan.address_items + ref_items << scan.ref_items + call_base_items << scan.call_base_items + index_base_items << scan.index_base_items + for id, safe in scan.fixed_safe_refs { + if safe { + fixed_safe_refs[id] = true + } + } + } + g.timing_profile(' [ttime] fs node scan ${f64(fssw.elapsed().microseconds()) / 1000.0:7.2f} ms (refs: ${ref_items.len}, calls: ${call_base_items.len}, indexes: ${index_base_items.len})') + fssw.restart() + for item in address_items { + g.tc.cur_file = item.file + g.tc.cur_module = item.module + child := g.a.nodes[int(item.id)] + const_name := g.const_ref_name_from_node_cached_for_collect(child, unique_const_ref_names, mut + const_ref_name_cache) + if const_name.len > 0 && g.const_address_can_force_fixed_storage(const_name) { + primary := g.const_primary_name_cached(const_name, mut primary_name_cache) + g.fixed_storage_consts[primary] = true + } + } + if fixed_storage_candidates.len == 0 { + g.tc.cur_module = old_module + g.tc.cur_file = old_file + return + } + for item in call_base_items { + g.tc.cur_file = item.file + g.tc.cur_module = item.module + node := g.a.nodes[int(item.id)] + primary := g.fixed_storage_candidate_primary_from_matched_node_for_collect(node, + fixed_storage_candidates, unique_const_ref_names, mut const_ref_name_cache, mut + primary_name_cache) + if primary.len > 0 { + dynamic_uses[primary] = true + } + } + for item in ref_items { + if fixed_safe_refs[int(item.id)] or { false } { + continue + } + g.tc.cur_file = item.file + g.tc.cur_module = item.module + node := g.a.nodes[int(item.id)] + primary := g.fixed_storage_candidate_primary_from_matched_node_for_collect(node, + fixed_storage_candidates, unique_const_ref_names, mut const_ref_name_cache, mut + primary_name_cache) + if primary.len > 0 { + dynamic_uses[primary] = true + } + } + for item in index_base_items { + g.tc.cur_file = item.file + g.tc.cur_module = item.module + node := g.a.nodes[int(item.id)] + primary := g.fixed_storage_candidate_primary_from_matched_node_for_collect(node, + fixed_storage_candidates, unique_const_ref_names, mut const_ref_name_cache, mut + primary_name_cache) + if primary.len > 0 { + indexed_candidates[primary] = true + } + } + for const_name, _ in indexed_candidates { + if dynamic_uses[const_name] { + continue + } + g.fixed_storage_consts[const_name] = true + } + g.timing_profile(' [ttime] fs replay ${f64(fssw.elapsed().microseconds()) / 1000.0:7.2f} ms') + g.tc.cur_module = old_module + g.tc.cur_file = old_file +} + +fn (g &FlatGen) mark_const_ref_descendants(mut ids map[int]bool, id flat.NodeId) { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return + } + ids[int(id)] = true + node := g.a.nodes[int(id)] + if node.kind == .paren && node.children_count > 0 { + g.mark_const_ref_descendants(mut ids, g.a.child(&node, 0)) + } +} + +fn (mut g FlatGen) const_storage_type_from_node(node flat.Node) ?types.Type { + if node.kind == .ident + && (g.current_param_type(node.value) != none || g.cur_scope_has_local_name(node.value)) { + return none + } + const_name := g.const_ref_name_from_node(node) + if const_name.len > 0 { + return g.const_storage_type_from_name(const_name) + } + if node.kind == .ident && node.value.len > 0 { + return g.const_storage_type_from_ident(node.value) + } + return none +} + +fn (mut g FlatGen) const_storage_type_from_ident(name string) ?types.Type { + mut candidates := []string{cap: 4} + candidates << name + if g.tc.cur_module.len > 0 && g.tc.cur_module != 'main' && g.tc.cur_module != 'builtin' { + candidates << '${g.tc.cur_module}.${name}' + } + if name.contains('__') { + candidates << name.replace('__', '.') + } + for candidate in candidates { + if typ := g.const_storage_type_from_name(candidate) { + return typ + } + } + if name.contains('__') { + for const_name, _ in g.const_vals { + if g.const_ident_c_name(const_name) == name { + if typ := g.const_storage_type_from_name(const_name) { + return typ + } + } + } + } + return none +} + +fn (mut g FlatGen) const_storage_type_from_name(const_name string) ?types.Type { + val_id := g.const_vals[const_name] or { return none } + fallback := g.const_value_type(const_name, val_id) + if fallback is types.ArrayFixed { + return fallback + } + if !g.fixed_storage_consts[g.const_primary_name(const_name)] { + return none + } + return g.const_array_literal_storage_type_for_name(const_name, val_id, fallback) +} + +fn (mut g FlatGen) gen_const_fixed_storage_len(node flat.Node) bool { + if const_type := g.const_storage_type_from_node(node) { + if fixed := array_fixed_type(const_type) { + g.write(g.fixed_array_len_value(fixed)) + return true + } + } + return false +} + +fn (mut g FlatGen) const_value_type(const_name string, val_id flat.NodeId) types.Type { + old_module := g.tc.cur_module + if mod := g.const_modules[const_name] { + g.tc.cur_module = mod + } + fallback := g.tc.resolve_type(val_id) + g.tc.cur_module = old_module + return fallback +} + +fn (mut g FlatGen) const_storage_type_for_value(name string, val_id flat.NodeId, fallback types.Type) types.Type { + mut base := fallback + if typ := g.tc.const_types[name] { + if typ !is types.Unknown && typ !is types.Void { + base = typ + } + } + if !g.fixed_storage_consts[g.const_primary_name(name)] { + return base + } + if fixed := g.const_array_literal_storage_type_for_name(name, val_id, base) { + return fixed + } + return base +} + +fn (mut g FlatGen) const_array_literal_storage_type_for_name(name string, val_id flat.NodeId, fallback types.Type) ?types.Type { + old_module := g.tc.cur_module + if mod := g.const_modules[name] { + g.tc.cur_module = mod + } + defer { + g.tc.cur_module = old_module + } + if int(val_id) < 0 || int(val_id) >= g.a.nodes.len { + return none + } + node := g.a.nodes[int(val_id)] + if node.kind != .array_literal || node.children_count == 0 { + return none + } + if fallback is types.ArrayFixed { + return fallback + } + mut elem_type := types.Type(types.void_) + if fallback is types.Array { + elem_type = fallback.elem_type + } else { + elem_type = g.tc.resolve_type(g.a.child(&node, 0)) + } + if elem_type is types.Array || elem_type is types.Map || elem_type is types.Struct + || elem_type is types.Interface || elem_type is types.SumType || elem_type is types.Void + || elem_type is types.Unknown { + return none + } + return types.Type(types.ArrayFixed{ + elem_type: elem_type + len: node.children_count + }) +} + +// const_expr_to_string converts const expr to string data for c. +fn (mut g FlatGen) const_expr_to_string(id flat.NodeId, seen []string) string { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return '0' + } + node := g.a.nodes[int(id)] + return match node.kind { + .ident, .selector { + const_name := g.const_ref_name_from_node(node) + if const_name.len > 0 && const_name !in seen { + mut next_seen := seen.clone() + next_seen << const_name + old_module := g.tc.cur_module + old_file := g.tc.cur_file + if mod := g.const_modules[const_name] { + g.tc.cur_module = mod + } + if file := g.const_files[const_name] { + g.tc.cur_file = file + } + dep_expr := g.const_expr_to_string(g.const_vals[const_name], next_seen) + g.tc.cur_file = old_file + g.tc.cur_module = old_module + if trimmed_space(dep_expr).len > 0 { + return dep_expr + } + } + if node.kind == .selector && node.children_count > 0 { + base := g.a.child_node(&node, 0) + if base.kind == .ident { + fn_name := '${base.value}.${node.value}' + if fn_name in g.tc.fn_ret_types || fn_name in g.tc.fn_param_types { + return g.cname(fn_name) + } + } + } + g.expr_to_string(id) + } + .infix { + lhs := g.const_expr_to_string(g.a.child(&node, 0), seen) + rhs := g.const_expr_to_string(g.a.child(&node, 1), seen) + // An int-literal shift by >= 31 would be performed at C `int` width + // and wrap (`1 << 51`); widen the lhs so the shift happens in 64 bits. + if node.op == .power { + g.power_expr_string(lhs, rhs, g.usable_expr_type(id)) + } else if node.op == .left_shift && g.shift_needs_64bit_widening(&node) { + '((u64)(${lhs})) << (${rhs})' + } else if node.op == .right_shift_unsigned { + // `>>>` must stay a logical shift in const initializers too; + // op_str would map it to a plain arithmetic `>>`. The operands + // are constant expressions, so repeating the rhs in the clamp + // is side-effect free (statement expressions are not valid in + // static initializers). + ut, bits := g.unsigned_shift_type_parts(g.usable_expr_type(g.a.child(&node, 0))) + '((u64)(${rhs}) >= ${bits} ? (${ut})0 : (${ut})((${ut})(${lhs}) >> (${rhs})))' + } else { + '(${lhs}) ${g.op_str(node.op)} (${rhs})' + } + } + .prefix { + child := g.const_expr_to_string(g.a.child(&node, 0), seen) + '${g.op_str(node.op)}(${child})' + } + .paren { + child := g.const_expr_to_string(g.a.child(&node, 0), seen) + '(${child})' + } + .cast_expr { + target_type := g.tc.parse_type(node.value) + mut ct := if node.value.starts_with('fn_ptr:') { + g.resolve_fn_ptr_type(node.value) + } else { + g.tc.c_type(target_type) + } + if ct.starts_with('fn_ptr:') { + ct = g.resolve_fn_ptr_type(ct) + } + if node.value in g.interfaces || g.tc.qualify_name(node.value) in g.interfaces { + return '(${ct}){0}' + } + if target_type is types.SumType { + inner_id := g.a.child(&node, 0) + inner := g.a.nodes[int(inner_id)] + variant_name0 := if inner.kind == .struct_init { + inner.value + } else { + g.tc.resolve_type(inner_id).name() + } + variant_name := g.resolve_variant(target_type.name, variant_name0) + idx := g.sum_type_index(target_type.name, variant_name) + field := g.sum_field_name(variant_name) + inner_val := g.const_expr_to_string(inner_id, seen) + payload := if trimmed_space(inner_val).len == 0 { '0' } else { inner_val } + variant_type := select_receive_unalias_type(g.tc.parse_type(variant_name)) + if variant_type is types.Pointer { + return '(${ct}){.typ = ${idx}, .${field} = ${payload}}' + } + inner_ct := g.value_c_type(variant_type) + return '(${ct}){.typ = ${idx}, .${field} = (${inner_ct}[]){${payload}}}' + } + if target_type is types.Alias && target_type.base_type is types.String { + return g.const_expr_to_string(g.a.child(&node, 0), seen) + } + if ct == 'map*' { + child_id := g.a.child(&node, 0) + if map_addr := g.map_pointer_cast_from_value_address_string(child_id, seen, ct) { + return map_addr + } + child_node := g.a.nodes[int(child_id)] + child0 := g.const_expr_to_string(child_id, seen) + child := if trimmed_space(child0).len == 0 { '0' } else { child0 } + if child_node.kind == .prefix && child_node.op == .amp { + return '(${ct})(${child})' + } + return '&(${child})' + } + if target_type !is types.Primitive && target_type !is types.Char + && target_type !is types.Rune && target_type !is types.ISize + && target_type !is types.USize && target_type !is types.Pointer + && target_type !is types.Enum { + return g.expr_to_string(id) + } + child0 := g.const_expr_to_string(g.a.child(&node, 0), seen) + child := if trimmed_space(child0).len == 0 { '0' } else { child0 } + '((${ct})(${child}))' + } + .array_literal { + mut parts := []string{} + for i in 0 .. node.children_count { + parts << g.const_expr_to_string(g.a.child(&node, i), seen) + } + '{${parts.join(', ')}}' + } + .struct_init { + ct := g.struct_init_c_type_name(node.value) + sum_name := g.resolve_sum_name(node.value) + is_sum_literal := sum_name in g.tc.sum_types + mut parts := []string{} + for i in 0 .. node.children_count { + field := g.a.child_node(&node, i) + if field.kind == .field_init && field.children_count > 0 { + val_id := g.a.child(field, 0) + val_node := g.a.nodes[int(val_id)] + val := if field.value.len == 0 { + const_val := g.const_expr_to_string(val_id, seen) + if trimmed_space(const_val).len > 0 { + const_val + } else { + if ftyp := g.struct_field_type_at(node.value, i) { + g.expr_to_string_with_expected_type(val_id, ftyp) + } else { + g.expr_to_string(val_id) + } + } + } else if is_sum_literal && field.value != 'typ' { + mut variant := '' + if field.typ.starts_with('&') { + variant = field.typ[1..] + } else if field.typ.len > 0 { + variant = field.typ + } else { + for v in g.tc.sum_types[sum_name] { + if g.sum_field_name(v) == field.value { + variant = v + break + } + } + } + variant = g.resolve_variant(sum_name, variant) + inner_ct := g.value_c_type(g.tc.parse_type(variant)) + const_val := g.const_expr_to_string(val_id, seen) + payload := if trimmed_space(const_val).len > 0 { + const_val + } else { + g.expr_to_string_with_expected_type(val_id, g.tc.parse_type(variant)) + } + '(${inner_ct}[]){${payload}}' + } else if ct.starts_with('Optional_') && ct.ends_with('ptr') + && field.value == 'value' { + g.expr_to_string(val_id) + } else if ftyp := g.struct_field_type(node.value, field.value) { + if val_node.kind == .enum_val { + g.expr_to_string_with_expected_type(val_id, ftyp) + } else { + const_val := g.const_expr_to_string(val_id, seen) + if trimmed_space(const_val).len > 0 { + const_val + } else { + g.expr_to_string_with_expected_type(val_id, ftyp) + } + } + } else { + const_val := g.const_expr_to_string(val_id, seen) + if trimmed_space(const_val).len > 0 { + const_val + } else { + g.expr_to_string(val_id) + } + } + if field.value.len == 0 { + parts << val + } else { + cfield := if is_sum_literal { + g.lowered_sum_c_field_name(sum_name, field) + } else { + g.cname(field.value) + } + parts << '.${cfield} = ${val}' + } + } else { + parts << g.const_expr_to_string(g.a.child(&node, i), seen) + } + } + '(${ct}){${parts.join(', ')}}' + } + .string_literal { + '(string){"${c_escape(node.value)}", ${node.value.len}, 1}' + } + .typeof_expr { + type_name := g.typeof_type_name(node) + '(string){"${c_escape(type_name)}", ${type_name.len}, 1}' + } + .sizeof_expr { + 'sizeof(${g.sizeof_target(node.value)})' + } + .int_literal, .float_literal, .bool_literal, .char_literal, .enum_val { + g.expr_to_string(id) + } + .offsetof_expr { + ct := g.sizeof_target(node.value) + 'offsetof(${ct}, ${g.cname(node.typ)})' + } + else { + g.expr_to_string(id) + } + } +} + +// const_ident_c_name converts const ident c name data for c. +fn (g &FlatGen) const_ident_c_name(name string) string { + if name.contains('.') { + return g.cname(name) + } + mod := if name in g.const_modules { g.const_modules[name] } else { '' } + if mod.len > 0 && mod != 'main' { + return g.cname('${mod}.${name}') + } + if (mod == '' || mod == 'main') && name in g.const_modules { + return g.cname('main.${name}') + } + return g.cname(name) +} + +// fixed_array_len_expr supports fixed array len expr handling for FlatGen. +fn (mut g FlatGen) fixed_array_len_expr(type_name string, fallback int) string { + if type_name.len > 0 { + typ := g.tc.parse_type(type_name) + if typ is types.ArrayFixed { + return g.fixed_array_len_value(typ) + } + } + mut raw_len := '' + if type_name.starts_with('[') { + idx := type_name.index_u8(`]`) + if idx > 1 { + raw_len = type_name[1..idx] + } + } else if type_name.contains('[') && type_name.ends_with(']') { + idx := type_name.index_u8(`[`) + if idx >= 0 && idx < type_name.len - 1 { + raw_len = type_name[idx + 1..type_name.len - 1] + } + } + return g.fixed_array_len_raw(raw_len, fallback) +} + +// fixed_array_len_value supports fixed array len value handling for FlatGen. +fn (mut g FlatGen) fixed_array_len_value(arr types.ArrayFixed) string { + // Prefer the evaluated integer length: a const-expression size (`[segs + 1]f32`) + // otherwise reaches the raw fallback and is c_name-mangled into garbage. + if v := g.tc.fixed_array_len_value(arr) { + return v.str() + } + return g.fixed_array_len_raw(arr.len_expr, arr.len) +} + +// fixed_array_len_is_zero supports fixed array len is zero handling for FlatGen. +fn (mut g FlatGen) fixed_array_len_is_zero(arr types.ArrayFixed) bool { + if value := g.tc.fixed_array_len_value(arr) { + return value == 0 + } + return trimmed_space(g.fixed_array_len_value(arr)) == '0' +} + +// fixed_array_len_raw supports fixed array len raw handling for FlatGen. +fn (mut g FlatGen) fixed_array_len_raw(raw_len string, fallback int) string { + if raw_len.len == 0 { + return '${fallback}' + } + // A literal or const-expression size (`8`, `SEGS + 1`, `1 << 2`, `8 >>> 1`) folds to an + // integer; emit that literal so the C dimension is always valid — `>>>` has no C form, + // so a digit-leading expression like `8 >>> 1` must not be passed through raw — and a + // non-numeric expr isn't c_name-mangled (`SEGS_+_1`) into an undeclared identifier. + if v := g.tc.const_int_value(raw_len, []string{}) { + return v.str() + } + clean_len := raw_len.replace('_', '') + if clean_len.len > 0 && clean_len[0] >= `0` && clean_len[0] <= `9` { + return clean_len + } + if expr := g.fixed_array_len_c_expr(raw_len) { + return expr + } + const_name := g.const_ref_name(raw_len) + if const_name.len > 0 { + expr := g.const_expr_to_string(g.const_vals[const_name], []string{}) + if trimmed_space(expr).len > 0 { + return expr + } + return g.const_ident_c_name(const_name) + } + return g.cname(raw_len) +} + +fn (mut g FlatGen) fixed_array_len_c_expr(raw_len string) ?string { + clean := raw_len.trim_space() + if clean.len == 0 || (!clean.contains('sizeof') && !fixed_array_len_has_operator(clean)) { + return none + } + mut out := strings.new_builder(clean.len) + mut changed := false + mut i := 0 + for i < clean.len { + ch := clean[i] + if fixed_array_len_ident_start(ch) { + start := i + i++ + for i < clean.len && fixed_array_len_ident_char(clean[i]) { + i++ + } + ident := clean[start..i] + mut next := i + for next < clean.len && clean[next] == ` ` { + next++ + } + if ident == 'sizeof' && next < clean.len && clean[next] == `(` { + close := fixed_array_len_matching_paren(clean, next) + if close > next { + target := clean[next + 1..close].trim_space() + out.write_string('sizeof(${g.sizeof_target(target)})') + i = close + 1 + changed = true + continue + } + } + const_name := g.const_ref_name(ident) + if const_name.len > 0 { + expr := g.const_expr_to_string(g.const_vals[const_name], []string{}) + if trimmed_space(expr).len > 0 { + out.write_string(expr) + } else { + out.write_string(g.const_ident_c_name(const_name)) + } + changed = true + continue + } + out.write_string(ident) + continue + } + out.write_u8(ch) + i++ + } + result := out.str() + unsafe { out.free() } + if !changed { + return none + } + return result +} + +fn fixed_array_len_has_operator(text string) bool { + for i, ch in text { + if ch in [`+`, `*`, `/`, `%`, `|`, `^`, `<`, `>`] || ((ch == `-` || ch == `&`) && i > 0) { + return true + } + } + return false +} + +fn fixed_array_len_matching_paren(text string, open int) int { + if open < 0 || open >= text.len || text[open] != `(` { + return -1 + } + mut depth := 0 + for i in open .. text.len { + if text[i] == `(` { + depth++ + } else if text[i] == `)` { + depth-- + if depth == 0 { + return i + } + } + } + return -1 +} + +fn fixed_array_len_ident_start(ch u8) bool { + return (ch >= `a` && ch <= `z`) || (ch >= `A` && ch <= `Z`) || ch == `_` +} + +fn fixed_array_len_ident_char(ch u8) bool { + return fixed_array_len_ident_start(ch) || (ch >= `0` && ch <= `9`) || ch == `.` +} + +fn (mut g FlatGen) fixed_array_decl_parts(arr types.ArrayFixed) (string, string) { + len_expr := g.fixed_array_len_value(arr) + if arr.elem_type is types.ArrayFixed { + base_ct, suffix := g.fixed_array_decl_parts(arr.elem_type) + return base_ct, '[${len_expr}]${suffix}' + } + elem_ct := g.fixed_array_elem_c_type(arr.elem_type) + return elem_ct, '[${len_expr}]' +} + +fn (mut g FlatGen) fixed_array_elem_c_type(elem types.Type) string { + if elem is types.ArrayFixed { + return g.fixed_array_c_type(elem) + } + if elem is types.OptionType || elem is types.ResultType { + return g.optional_type_name(elem) + } + return g.value_c_type(elem) +} + +fn (mut g FlatGen) fixed_array_c_type(arr types.ArrayFixed) string { + // Function signatures use TypeChecker.c_type(), whose fixed-array name preserves + // the V spelling of pointer sizeof targets. Keep the emitted typedef identical. + if arr.len_expr.contains('sizeof(&') { + return g.tc.c_type(arr) + } + len_text := g.fixed_array_len_value(arr) + // Const-expression rendering can inherit the current writer indentation. + // Whitespace is immaterial to the C dimension and must not change the typedef name. + len_name := + naming.type_name_part(len_text.replace(' ', '').replace('\t', '').replace('\n', '').replace('\r', '')) + elem_name := g.fixed_array_elem_name_part(arr.elem_type) + return 'Array_fixed_${naming.type_name_part(elem_name)}_${len_name}' +} + +fn (mut g FlatGen) fixed_array_elem_name_part(elem types.Type) string { + if elem is types.Pointer && elem.base_type is types.Void { + return 'voidptr' + } + if elem is types.FnType { + return g.tc.c_type(elem) + } + return g.fixed_array_elem_c_type(elem) +} + +// infix_can_skip_child_parens reports whether a child infix operand needs no +// surrounding parentheses. For associative logical chains (`||`, `&&`) a child of +// the same operator is safe unparenthesised; this keeps long lowered chains (e.g. +// a `match` over hundreds of enum values → `a || b || c || ...`) from nesting +// parentheses past the C compiler's bracket-depth limit. +fn infix_can_skip_child_parens(parent_op flat.Op, child_op flat.Op) bool { + return (parent_op == .logical_or && child_op == .logical_or) + || (parent_op == .logical_and && child_op == .logical_and) +} + +// assoc_infix_chain_len counts how many same-operator infix nodes hang off the left +// spine of `node` (its nesting depth). Capped early since only "very deep" matters. +fn (g &FlatGen) assoc_infix_chain_len(node flat.Node) int { + op := node.op + mut cur := node + mut depth := 0 + for { + if cur.children_count < 1 { + break + } + lhs_id := g.a.child(&cur, 0) + if !g.valid_node_id(lhs_id) { + break + } + lhs := g.a.nodes[int(lhs_id)] + if lhs.kind == .infix && lhs.op == op { + depth++ + if depth > 101 { + break + } + cur = lhs + } else { + break + } + } + return depth +} + +// gen_assoc_infix_chain emits a left-nested `||`/`&&` chain iteratively, producing the +// same flat `a || b || c …` C as the recursive path but without growing the stack per +// link (a big match's condition chain can be hundreds deep). +fn (mut g FlatGen) gen_assoc_infix_chain(node flat.Node) { + op := node.op + op_s := g.op_str(op) + mut operands := []flat.NodeId{cap: 256} + mut cur := node + for { + operands << g.a.child(&cur, 1) + lhs_id := g.a.child(&cur, 0) + lhs := g.a.nodes[int(lhs_id)] + if lhs.kind == .infix && lhs.op == op && g.valid_node_id(g.a.child(&lhs, 0)) { + cur = lhs + } else { + operands << lhs_id + break + } + } + for i := operands.len - 1; i >= 0; i-- { + if i != operands.len - 1 { + g.write(' ${op_s} ') + } + oid := operands[i] + onode := g.a.nodes[int(oid)] + if onode.kind == .infix && !infix_can_skip_child_parens(op, onode.op) { + g.write('(') + g.gen_expr(oid) + g.write(')') + } else { + g.gen_expr(oid) + } + } +} + +fn (g &FlatGen) infix_channel_type(id flat.NodeId, fallback types.Type) types.Type { + clean_fallback := concrete_receiver_type(fallback) + if clean_fallback is types.Channel { + return clean_fallback + } + if int(id) >= 0 && int(id) < g.a.nodes.len { + node := g.a.nodes[int(id)] + if node.typ.len > 0 { + annotated := concrete_receiver_type(g.parse_node_type(&node)) + if annotated is types.Channel { + return annotated + } + } + } + return fallback +} + +// gen_expr emits expr output for c. +@[direct_array_access] +fn (mut g FlatGen) gen_expr(id flat.NodeId) { + if int(id) < 0 { + g.write('0') + return + } + if replacement := g.assert_expr_overrides[int(id)] { + g.write(replacement) + return + } + node := unsafe { &g.a.nodes[int(id)] } + match node.kind { + .int_literal { + v := node.value.replace('_', '') + if v.starts_with('0o') { + g.write('0${v[2..]}') + } else { + g.write(v) + } + } + .float_literal { + g.write(node.value.replace('_', '')) + } + .bool_literal { + g.write(node.value) + } + .char_literal { + v := node.value + if v.starts_with('c:') { + cv := v[2..] + // A `c'...'` literal is a C string pointer (`C.fputs(c'\n', f)`). + // Only when a single-character literal is used where a byte-sized + // value is expected (`data[0] = c'g'`) is it dereferenced to its + // first byte (reference cgen emits `*"g"` there). + expected_ct := g.value_c_type(g.expected_expr_type) + if byte_value := c_char_literal_byte_value(cv) { + if g.expected_expr_type !is types.Pointer + && expected_ct in ['u8', 'i8', 'char', 'u16', 'i16', 'u32', 'i32', 'int', 'u64', 'i64', 'rune', 'usize', 'isize'] { + if byte_value > 0x7f { + g.write('((u8)*"${cv}")') + } else { + g.write('*"${cv}"') + } + } else { + g.write('"${escape_c_string_literal_quotes(cv)}"') + } + } else { + g.write('"${escape_c_string_literal_quotes(cv)}"') + } + } else if v.len == 0 { + g.write("' '") + } else if v.len == 1 { + if v[0] == `\\` { + g.write("'\\\\'") + } else if v[0] == `'` { + g.write("'\\''") + } else { + g.write("'${v}'") + } + } else if v.starts_with('\\') { + if codepoint := char_escape_codepoint(v) { + g.write(codepoint.str()) + } else { + g.write("'${v}'") + } + } else { + runes := v.runes() + if runes.len == 0 { + g.write('0') + } else { + g.write(int(runes[0]).str()) + } + } + } + .string_literal { + sid := g.intern_string(node.value) + g.write('_str_${sid}') + } + .string_interp { + g.gen_string_interp(node) + } + .dump_expr { + if node.children_count > 0 { + g.gen_expr(g.a.child(node, 0)) + } else { + g.write('0') + } + } + .defer_result { + defer_index := types.defer_result_index(node) or { -1 } + has_return_tmp := g.defer_return_tmp_var.len > 0 + if has_return_tmp { + g.write(g.defer_return_tmp_var) + } else { + g.write('((${g.value_c_type(g.cur_fn_ret)}){0})') + } + if defer_index >= 0 { + g.write('.arg${defer_index}') + } else if has_return_tmp { + if _ := array_fixed_type(g.cur_fn_ret) { + // A fixed-array function returns an ABI wrapper, while `$res()` + // exposes the semantic array stored in that wrapper. + g.write('.ret_arr') + } + } + } + .ident { + if g.current_param_is_mut_pointer(node.value) { + // A `mut p &T` parameter is stored as a `T**` slot; its value in an + // expression is the `&T` pointer `*p`. Slot/lvalue consumers emit the + // raw parameter name through gen_mut_pointer_slot_expr instead. + g.write('(*') + g.gen_mut_pointer_slot_expr(id) + g.write(')') + return + } + if c_fn_name := g.test_user_main_fn_value_c_name(id, node) { + g.write(c_fn_name) + return + } + if node.value.contains('sizeof') || fixed_array_len_has_operator(node.value) { + if expr := g.fixed_array_len_c_expr(node.value) { + g.write(expr) + return + } + } + is_current_param := node.value in g.cur_param_names + || g.current_param_type(node.value) != none + is_local := if is_current_param { + true + } else if owner := g.tc.cur_scope.lookup_owner(node.value) { + !owner.belongs_to_scope(g.tc.file_scope) + } else { + false + } + current_global_name := qualify_name_in_module(g.tc.cur_module, node.value) + is_current_module_global := current_global_name in g.global_types + const_name := if !is_local && !is_current_module_global { + g.const_ref_name(node.value) + } else { + '' + } + if const_name.len > 0 { + g.write(g.const_ident_c_name(const_name)) + } else if g.local_storage_is_shared(node.value) { + g.write(g.local_cname(node.value)) + g.write('->val') + } else if is_current_param && g.local_name_needs_global_suffix(node.value) { + g.write(g.local_decl_cname(node.value)) + } else if is_local && g.local_name_needs_global_suffix(node.value) { + g.write(g.local_decl_cname(node.value)) + } else if g.local_shadows_global(node.value) { + g.write(g.local_cname(node.value)) + } else if is_local && local_name_shadows_c_runtime(node.value) { + g.write(g.local_cname(node.value)) + } else if is_local && g.local_name_shadows_c_typedef(node.value) { + g.write(g.local_cname(node.value)) + } else if is_current_module_global { + g.write(g.global_c_name(current_global_name)) + } else if node.value in g.global_modules { + mod := g.global_modules[node.value] + if mod.len > 0 && mod != 'main' && mod != 'builtin' { + g.write(g.global_c_name('${mod}.${node.value}')) + } else { + g.write(g.global_c_name(node.value)) + } + } else if fn_c_name := g.ident_fn_value_c_name(id, node) { + g.write(fn_c_name) + } else { + g.write(g.cname(node.value)) + } + } + .enum_val { + if expr := g.enum_value_expr_for_key(node.value) { + g.write(expr) + return + } + if node.typ.len > 0 { + short_name := node.value.trim_left('.').all_after_last('.') + if expr := g.enum_value_expr_for_type(node.typ, short_name) { + g.write(expr) + return + } + } + if g.expected_enum.len > 0 { + ekey := '${g.expected_enum}.${node.value}' + if expr := g.enum_value_expr_for_key(ekey) { + g.write(expr) + return + } + if !g.expected_enum.contains('.') && g.tc.cur_module.len > 0 + && g.tc.cur_module != 'main' && g.tc.cur_module != 'builtin' { + qkey := '${g.tc.cur_module}.${g.expected_enum}.${node.value}' + if expr := g.enum_value_expr_for_key(qkey) { + g.write(expr) + return + } + } + } + for ename, expr in g.enum_value_exprs { + if ename.ends_with('.${node.value}') { + g.write(expr) + return + } + } + for ename, eval in g.enum_vals { + if ename.ends_with('.${node.value}') { + g.write('${eval}') + return + } + } + g.write('0') + } + .call { + if g.string_plus_call_is_nested(id, node) { + g.gen_owned_string_plus_chain(id) + return + } + // A call to a fixed-array-returning function yields the wrapper struct; + // unwrap `.ret_arr` so the result behaves as the array value everywhere + // (indexing, arg passing, memcpy into a destination). + ret_t := g.declared_call_return_type(id) + if _ := array_fixed_type(ret_t) { + g.write('(') + g.gen_call(id, node) + g.write(').ret_arr') + return + } + g.gen_call(id, node) + } + .spawn_expr { + g.gen_spawn_expr(node) + } + .lock_expr { + g.gen_lock_expr(id, node) + } + .select_stmt { + g.gen_select(id, node, true) + } + .infix { + // A very long left-nested `||`/`&&` chain (e.g. from a big match condition or + // a `!in [...]` over many values) would recurse once per link and overflow the + // stack; emit those iteratively. Only pathologically long chains take this path, + // so ordinary code keeps the existing per-node generation unchanged. + if (node.op == .logical_or || node.op == .logical_and) + && g.assoc_infix_chain_len(node) > 100 { + g.gen_assoc_infix_chain(node) + return + } + lhs_id := g.a.child(node, 0) + rhs_id := g.a.child(node, 1) + old_expected_enum := g.expected_enum + lhs_type := g.usable_expr_type(lhs_id) + rhs_type := g.usable_expr_type(rhs_id) + if node.op == .power { + lhs_node := g.a.nodes[int(lhs_id)] + if lhs_node.kind == .prefix && lhs_node.op == .minus && lhs_node.children_count == 1 { + g.write('-') + g.gen_power_expr(g.a.child(&lhs_node, 0), rhs_id, g.usable_expr_type(id)) + } else { + g.gen_power_expr(lhs_id, rhs_id, g.usable_expr_type(id)) + } + g.expected_enum = old_expected_enum + return + } + // An int-literal shift by >= 31 would be performed at C `int` width + // and wrap (`u64(1 << 40)`); widen the lhs so the shift is 64-bit. + if node.op == .left_shift && g.shift_needs_64bit_widening(node) { + g.write('((u64)(') + g.gen_expr(lhs_id) + g.write(') << (') + g.gen_expr(rhs_id) + g.write('))') + g.expected_enum = old_expected_enum + return + } + if node.op in [.left_shift, .right_shift, .right_shift_unsigned] { + g.gen_guarded_shift(lhs_id, rhs_id, lhs_type, node.op) + g.expected_enum = old_expected_enum + return + } + if node.op == .arrow { + channel_type := g.infix_channel_type(lhs_id, lhs_type) + if channel_type is types.Channel { + rhs_node := g.a.nodes[int(rhs_id)] + if rhs_node.kind == .or_expr && rhs_node.children_count >= 2 { + g.gen_channel_send_or(lhs_id, channel_type, rhs_node) + g.expected_enum = old_expected_enum + return + } + elem_ct := g.value_c_type(channel_type.elem_type) + g.write('sync__Channel__push(') + g.gen_channel_try_receiver(lhs_id) + g.write(', &(${elem_ct}[]){') + g.gen_expr_with_expected_type(rhs_id, channel_type.elem_type) + g.write('})') + g.expected_enum = old_expected_enum + return + } + } + if g.gen_array_infix_eq(node, lhs_id, rhs_id, lhs_type, rhs_type) { + g.expected_enum = old_expected_enum + return + } + if g.gen_map_infix_eq(node, lhs_id, rhs_id, lhs_type, rhs_type) { + g.expected_enum = old_expected_enum + return + } + if g.gen_thread_infix_eq(node, lhs_id, rhs_id, lhs_type, rhs_type) { + g.expected_enum = old_expected_enum + return + } + if lhs_type is types.String || rhs_type is types.String { + if g.gen_string_infix_fallback(node, lhs_id, rhs_id) { + g.expected_enum = old_expected_enum + return + } + } + if g.gen_checked_integer_infix(node, lhs_id, rhs_id, lhs_type) { + g.expected_enum = old_expected_enum + return + } + lhs_node := g.a.nodes[int(lhs_id)] + rhs_node := g.a.nodes[int(rhs_id)] + if node.op in [.eq, .ne, .lt, .gt, .le, .ge] + && g.gen_mixed_sign_integer_comparison(lhs_id, rhs_id, lhs_type, rhs_type, node.op) { + g.expected_enum = old_expected_enum + return + } + if node.op in [.eq, .ne] + && ((rhs_node.kind == .char_literal && rhs_node.value.starts_with('c:') + && lhs_type !is types.Pointer) + || (lhs_node.kind == .char_literal && lhs_node.value.starts_with('c:') + && rhs_type !is types.Pointer)) { + if lhs_node.kind == .char_literal && lhs_node.value.starts_with('c:') { + g.write('*') + } + g.gen_expr(lhs_id) + g.write(' ${g.op_str(node.op)} ') + if rhs_node.kind == .char_literal && rhs_node.value.starts_with('c:') { + g.write('*') + } + g.gen_expr(rhs_id) + g.expected_enum = old_expected_enum + return + } + if lhs_type is types.Enum { + g.expected_enum = lhs_type.name + } else if rhs_type is types.Enum { + g.expected_enum = rhs_type.name + } + if lhs_type is types.Struct { + op_name := match node.op { + .minus { '__minus' } + .plus { '__plus' } + .eq { '__eq' } + .ne { '__ne' } + .lt { '__lt' } + .gt { '__gt' } + .le { '__le' } + .ge { '__ge' } + else { '' } + } + + if op_name.len > 0 { + method_name := '${lhs_type.name}${op_name}' + if method_name in g.tc.fn_param_types { + panic('internal error: struct operator overload reached C backend after transform: ${lhs_type.name} op=${node.op}') + } + } + g.gen_expr(lhs_id) + g.write(' ${g.op_str(node.op)} ') + g.gen_expr_with_possible_enum_type(rhs_id, lhs_type) + } else { + // In a comparison, a small-int arithmetic operand must wrap at + // its V width first: C promotes `u8 + u8` to int, so + // `a + b == 0` would see 256 where V semantics require 0. + is_comparison := node.op in [.eq, .ne, .lt, .gt, .le, .ge] + if is_comparison + && g.gen_small_int_arith_operand_truncated(lhs_id, lhs_node, lhs_type) { + } else if lhs_node.kind == .infix + && !infix_can_skip_child_parens(node.op, lhs_node.op) && !(g.tc.autofree_mode + && node.op == .minus && lhs_node.op == .plus) { + g.write('(') + g.gen_expr_with_possible_enum_type(lhs_id, rhs_type) + g.write(')') + } else { + g.gen_expr_with_possible_enum_type(lhs_id, rhs_type) + } + g.write(' ${g.op_str(node.op)} ') + if is_comparison + && g.gen_small_int_arith_operand_truncated(rhs_id, rhs_node, rhs_type) { + } else if rhs_node.kind == .infix + && !infix_can_skip_child_parens(node.op, rhs_node.op) { + g.write('(') + g.gen_expr_with_possible_enum_type(rhs_id, lhs_type) + g.write(')') + } else { + g.gen_expr_with_possible_enum_type(rhs_id, lhs_type) + } + } + g.expected_enum = old_expected_enum + } + .prefix { + child_id := g.a.child(node, 0) + child := g.a.nodes[int(child_id)] + if node.op == .mul && node.value.len == 0 + && g.source_mut_pointer_param_deref_type(child_id) != none { + g.gen_expr(child_id) + return + } + if node.value == 'shared' { + g.gen_expr(child_id) + return + } + if node.op == .arrow { + child_type0 := g.usable_expr_type(child_id) + child_type := concrete_receiver_type(child_type0) + if child_type is types.Channel { + elem_ct := g.tc.c_type(child_type.elem_type) + tmp := g.tmp_name() + g.write('({${elem_ct} ${tmp} = (${elem_ct}){0}; sync__Channel__pop(') + if child_type0 is types.Pointer { + g.write('*(') + g.gen_expr(child_id) + g.write(')') + } else { + g.gen_expr(child_id) + } + g.write(', &${tmp}); ${tmp};})') + return + } + g.gen_expr(child_id) + return + } + if node.op == .mul && child.kind == .ident { + if g.current_param_is_mut_pointer(child.value) { + // A source `*item` must dereference both the `T**` ABI slot and its + // semantic `&T` value. Synthetic dereferences only read the slot. + g.write('(*') + if g.source_mut_pointer_param_deref_type(id) != none { + g.gen_expr(child_id) + } else { + g.gen_mut_pointer_slot_expr(child_id) + } + g.write(')') + return + } + if typ := g.current_param_type(child.value) { + if typ !is types.Pointer { + g.gen_expr(child_id) + return + } + } else if typ := g.current_param_map_type(child.value) { + if typ !is types.Pointer { + g.gen_expr(child_id) + return + } + } + } + if node.op == .mul && child.kind == .index && child.children_count > 1 { + base_id := g.a.child(&child, 0) + base_type := g.usable_expr_type(base_id) + _, fixed_is_ptr, _ := fixed_array_index_info(base_type) + if fixed_is_ptr { + g.write('(*') + g.gen_expr(base_id) + g.write(')[') + g.gen_expr(g.a.child(&child, 1)) + g.write(']') + return + } + } + if node.op == .mul && child.kind == .paren && child.children_count > 0 { + inner_id := g.a.child(&child, 0) + inner := g.a.node(inner_id) + if inner.kind == .ident { + g.write('*') + g.gen_expr(inner_id) + return + } + } + if node.op == .amp && child.kind == .prefix && child.op == .mul + && child.children_count > 0 { + g.gen_expr(g.a.child(&child, 0)) + return + } + if node.op == .amp && g.gen_amp_c_string_literal(child_id, child) { + return + } else if node.op == .amp && g.gen_current_mut_param_address(id) { + return + } else if node.op == .amp && node.typ.len > 0 + && g.gen_sum_pointer_value_expr(id, g.parse_node_type(node)) { + return + } else if node.op == .amp && child.kind == .struct_init { + g.gen_heap_struct_init(child) + } else if node.op == .amp && child.kind == .assoc { + g.gen_heap_assoc_expr(child) + } else if node.op == .amp && child.kind == .cast_expr { + target_type := g.tc.parse_type(child.value) + ct := g.cast_c_type(target_type) + cast_arg := g.a.child_node(&child, 0) + if cast_arg.kind == .nil_literal { + g.write('(${ct}*)NULL') + return + } + if target_type is types.Pointer { + if map_str_clean_type(target_type.base_type) is types.Map + && g.gen_map_pointer_cast_from_value_address(g.a.child(&child, 0), target_type) { + return + } + } + if target_type is types.SumType { + g.write('(${ct}*)memdup(&') + g.gen_sum_cast_expr(target_type, g.a.child(&child, 0)) + g.write(', sizeof(${ct}))') + return + } + if target_type is types.Alias && target_type.base_type !is types.Pointer { + base_ct := g.value_c_type(target_type.base_type) + value_expr := g.expr_to_string(g.a.child(&child, 0)) + source_expr := '(${base_ct}[]){${value_expr}}[0]' + g.write(g.heap_local_memdup_expr(source_expr, target_type.base_type, base_ct, + false)) + return + } + g.write('(${ct}*)(') + g.gen_expr(g.a.child(&child, 0)) + g.write(')') + } else if node.op == .amp && child.kind == .call + && g.gen_array_accessor_lvalue_address(child_id, child) { + return + } else if node.op == .amp && child.kind == .index && child.value == 'range' { + child_type := g.usable_expr_type(child_id) + g.gen_addressed_rvalue_arg(child_id, types.Type(types.Pointer{ + base_type: child_type + })) + } else if node.op == .amp && child.kind == .call { + fn_child := g.a.child_node(&child, 0) + if fn_child.kind == .selector { + base_child := g.a.child_node(fn_child, 0) + if base_child.kind == .ident && base_child.value == 'C' { + c_struct_prefix := if fn_child.value.len > 0 && fn_child.value[0] >= `a` + && fn_child.value[0] <= `z` && !fn_child.value.ends_with('_t') { + 'struct ' + } else { + '' + } + g.write('(${c_struct_prefix}${fn_child.value}*)(') + if child.children_count > 1 { + g.gen_expr(g.a.child(&child, 1)) + } else { + g.write('0') + } + g.write(')') + } else { + child_type := g.usable_expr_type(child_id) + g.gen_addressed_rvalue_arg(child_id, types.Type(types.Pointer{ + base_type: child_type + })) + } + } else { + child_type := g.usable_expr_type(child_id) + g.gen_addressed_rvalue_arg(child_id, types.Type(types.Pointer{ + base_type: child_type + })) + } + } else { + g.gen_prefix_op_operand(node.op, child_id) + } + } + .in_expr { + // NOTE: range membership, inline-array-literal membership, dynamic- and + // fixed-array membership, and `!in` negation are lowered by the + // transformer (transform.transform_in_expr). Map membership stays as an + // in_expr so each backend can lower it directly. + lhs_id := g.a.child(node, 0) + rhs_id := g.a.child(node, 1) + rhs := g.a.nodes[int(rhs_id)] + rhs_type := g.usable_expr_type(rhs_id) + clean_rhs := types.unwrap_pointer(rhs_type) + if clean_rhs is types.Map { + c_key := g.map_key_temp_c_type(clean_rhs.key_type) + is_ptr := rhs_type is types.Pointer + if is_ptr { + g.write('map__exists(') + } else { + g.write('map__exists(&') + } + g.gen_expr(rhs_id) + g.write(', &(${c_key}[]){') + g.gen_expr(lhs_id) + g.write('})') + } else if rhs.kind == .array_literal { + if rhs.children_count == 0 { + g.write('false') + } else { + lhs_type := g.usable_expr_type(lhs_id) + g.write('(') + for i in 0 .. rhs.children_count { + if i > 0 { + g.write(' || ') + } + elem_id := g.a.child(&rhs, i) + elem_type := g.usable_expr_type(elem_id) + if (lhs_type is types.String || elem_type is types.String) + && !g.expr_is_non_string_scalar_value(lhs_id) + && !g.expr_is_non_string_scalar_value(elem_id) { + g.write('string__eq(') + g.gen_expr(lhs_id) + g.write(', ') + g.gen_expr(elem_id) + g.write(')') + } else { + g.gen_expr(lhs_id) + g.write(' == ') + g.gen_expr(elem_id) + } + } + g.write(')') + } + } else if clean_rhs is types.Array { + fn_name := array_membership_fn_name(clean_rhs.elem_type, false) + g.write('${fn_name}(') + // A `mut []T` param (or any `&[]T`) is a pointer in C; the membership + // helper takes the array by value, so dereference it first. + if rhs_type is types.Pointer { + g.write('*') + } + g.gen_expr(rhs_id) + g.write(', ') + g.gen_expr(lhs_id) + g.write(')') + } else if clean_rhs is types.ArrayFixed { + fn_name := array_membership_fn_name(clean_rhs.elem_type, true) + len_expr := g.fixed_array_len_value(clean_rhs) + g.write('${fn_name}(') + g.gen_expr(rhs_id) + g.write(', ${len_expr}, ') + g.gen_expr(lhs_id) + g.write(')') + } else if clean_rhs is types.Struct && clean_rhs.name == 'array' { + lhs_type := g.usable_expr_type(lhs_id) + fn_name := array_membership_fn_name(lhs_type, false) + g.write('${fn_name}(') + g.gen_expr(rhs_id) + g.write(', ') + g.gen_expr(lhs_id) + g.write(')') + } else { + panic('internal error: non-map membership reached C backend in ${g.cur_fn_name}: rhs=${rhs_type.name()} kind=${rhs.kind} value=${rhs.value}') + } + } + .postfix { + child_id := g.a.child(node, 0) + child := g.a.nodes[int(child_id)] + if node.op in [.inc, .dec] { + if atomic_type := g.atomic_selector_type(child_id) { + op := if node.op == .inc { 'add' } else { 'sub' } + g.write('atomic_fetch_${op}_${g.atomic_helper_suffix(atomic_type)}(&(') + g.gen_expr(child_id) + g.write('), 1)') + return + } + } + if child.kind == .ident && g.current_param_is_mut(child.value) { + g.write('(*') + if g.current_param_is_mut_pointer(child.value) { + g.gen_mut_pointer_slot_expr(child_id) + } else { + g.gen_expr(child_id) + } + g.write(')') + } else { + g.gen_expr(child_id) + } + g.write(g.op_str(node.op)) + } + .paren { + g.write('(') + g.gen_expr(g.a.child(node, 0)) + g.write(')') + } + .selector { + base_id := g.a.child(node, 0) + base := g.a.nodes[int(base_id)] + if base.kind == .typeof_expr { + if node.value == 'name' { + g.gen_typeof_name(base) + return + } + if node.value == 'idx' { + g.write(g.typeof_type_index(base).str()) + return + } + } + mut base_type0 := g.usable_expr_type(base_id) + base_is_source_mut_pointer_deref := g.source_mut_pointer_param_deref_type(base_id) != none + if deref_type := g.source_mut_pointer_param_deref_type(base_id) { + base_type0 = deref_type + } + base_type_clean := types.unwrap_pointer(base_type0) + if base_type0 is types.Channel && node.value in ['closed', 'len', 'cap'] { + if node.value == 'closed' { + g.write('(atomic_load_u16(&') + g.gen_expr(base_id) + g.write('->closed) != 0)') + } else if node.value == 'cap' { + g.write('((int)(') + g.gen_expr(base_id) + g.write('->cap))') + } else { + g.write('sync__Channel__len(') + g.gen_expr(base_id) + g.write(')') + } + return + } + base_is_local := if base.kind == .ident { + g.selector_base_is_value(base.value) + } else { + false + } + // An exact import in the active file takes precedence over unrelated + // file-scope symbols with the same short name. Keep lexical locals as + // the authority so a local can still shadow an import alias. + mut imported_selector_module := '' + if base.kind == .ident && base.value != 'C' { + mut is_lexical_local := false + if owner := g.tc.cur_scope.lookup_owner(base.value) { + is_lexical_local = !owner.belongs_to_scope(g.tc.file_scope) + } + if !is_lexical_local { + imported_selector_module = g.tc.file_imports['${g.tc.cur_file}\n${base.value}'] or { + '' + } + } + } + mut enum_selector_qbase := if base.kind == .ident && base.value != 'C' && !base_is_local { + g.enum_selector_base_name(base.value) or { '' } + } else { + '' + } + // Fully qualified enum value: `mod.Enum.field` — the base is itself a + // selector over a module ident, not a plain ident. Enum type names are + // capitalized and module names are not, which filters out ordinary + // `a.b.c` field chains before any lookup. + if enum_selector_qbase.len == 0 && base.kind == .selector && base.children_count > 0 + && base.value.len > 0 && base.value[0] >= `A` && base.value[0] <= `Z` { + base_base := g.a.child_node(&base, 0) + if base_base.kind == .ident && base_base.value != 'C' && base_base.value.len > 0 + && base_base.value[0] >= `a` && base_base.value[0] <= `z` + && !g.selector_base_is_value(base_base.value) { + enum_selector_qbase = g.enum_selector_base_name('${base_base.value}.${base.value}') or { + '' + } + } + } + // Enum fields take precedence when a method has the same name. Only a + // non-enum selector can be lowered as a bound method value. + mut clone_receiver_fn := '' + for param in node.generic_params() { + if param.starts_with(flat.method_value_clone_receiver_marker_prefix) { + clone_receiver_fn = + param.all_after(flat.method_value_clone_receiver_marker_prefix) + break + } + } + if enum_selector_qbase.len == 0 + && '__v3_generated_variant_access' !in node.generic_params() + && g.gen_method_value_closure(id, base_id, base_type0, node.value, flat.method_value_borrow_receiver_marker in node.generic_params(), clone_receiver_fn) { + return + } + // The expected type belongs to the selected field, not to its base. In + // particular, propagating a sum payload expectation into an `Optional{}` + // base rewrites the wrapper literal as the sum itself before `.ok`/`.value`. + old_selector_expected := g.expected_expr_type + old_selector_enum := g.expected_enum + g.expected_expr_type = types.Type(types.void_) + g.expected_enum = '' + defer { + g.expected_expr_type = old_selector_expected + g.expected_enum = old_selector_enum + } + if base.kind == .ident && base.value == 'C' { + g.write(c_winapi_wide_export_name(node.value)) + } else if enum_selector_qbase.len > 0 { + ekey := '${enum_selector_qbase}.${node.value}' + if expr := g.enum_value_expr_for_key(ekey) { + g.write(expr) + } else { + g.write('0') + } + } else if imported_selector_module.len > 0 { + short_mod := if imported_selector_module.contains('.') { + imported_selector_module.all_after_last('.') + } else { + imported_selector_module + } + full_qname := g.const_storage_name(imported_selector_module, node.value) + if full_qname in g.const_vals { + g.write(g.cname(full_qname)) + } else { + g.write(g.cname('${short_mod}.${node.value}')) + } + } else if g.gen_local_shared_value_selector(base_id, node.value) { + // handled + } else if g.gen_shared_field_value_selector(base_id, base_type0, node.value, node.op) { + // handled + } else if node.value == 'len' && g.gen_const_fixed_storage_len(base) { + // handled + } else if node.value == 'len' && base.kind == .array_literal { + // The length of an array literal is known without materializing its + // temporary storage. This also covers literals whose inferred type was + // narrowed to a fixed array by selector context. + g.write(int(base.children_count).str()) + } else if node.value == 'len' && array_fixed_type(base_type_clean) != none { + fixed := array_fixed_type(base_type_clean) or { types.ArrayFixed{} } + g.write(g.fixed_array_len_value(fixed)) + } else if base_type0 is types.String && node.value == 'len' { + // A smartcast variant base is a deref (`*f._string`); without parens + // the member access would bind first (`*f._string.len`). + // Array string indexing also emits a dereference (`*(string*)array_get(...)`). + str_needs_paren := base.kind !in [.ident, .selector] + if str_needs_paren { + g.write('(') + } + g.gen_expr(base_id) + if str_needs_paren { + g.write(')') + } + g.write('.len') + } else if node.value == 'len' && (base_type_clean is types.Array + || base_type_clean is types.Map || (base_type_clean is types.Struct + && base_type_clean.name in ['array', 'map'])) { + // Array accessors such as `last()` are calls in the flat tree, but + // emit a dereference expression in C. Parenthesize that value before + // selecting `.len`, otherwise the member access binds inside `array_get`. + needs_paren := base.kind !in [.ident, .selector] + if needs_paren { + g.write('(') + } + g.gen_expr(base_id) + if needs_paren { + g.write(')') + } + if base_type0 is types.Pointer { + g.write('->len') + } else { + g.write('.len') + } + } else if node.value == '__v_sum_type_tag__' + && g.gen_sum_type_tag_selector(base_id, base_type0, node.op) { + // handled + } else if g.gen_generated_variant_access_selector(node, base_id, base_type0) { + // handled + } else if g.gen_sum_unique_variant_field_selector(base_id, base_type0, node.value) { + // handled + } else if g.gen_sum_shared_field_selector(base_id, base_type0, node.value) { + // handled + } else if g.gen_pointer_pointer_struct_selector(base_id, g.pointer_pointer_selector_base_type(&base, + base_type0), node.value) + { + // handled + } else if base.kind == .call && base.children_count == 2 + && g.c_typedef_cast_call_name(base).len > 0 { + cast_name := g.c_typedef_cast_call_name(base) + cast_arg_id := g.a.child(&base, 1) + g.write('((${g.cname(cast_name)}*)') + g.gen_expr(cast_arg_id) + g.write(')->${g.cname(node.value)}') + } else if base.kind == .cast_expr && base.children_count > 0 + && (base.value.starts_with('C.') || base.value.starts_with('&C.') + || (base.value.contains('__') && !base.value.starts_with('&'))) { + cast_child_id := g.a.child(&base, 0) + cast_type := g.tc.parse_type(base.value) + if cast_type is types.Pointer { + ct := g.cast_c_type(cast_type) + g.write('((${ct})') + g.gen_expr(cast_child_id) + g.write(')->${g.cname(node.value)}') + } else { + cast_name := if base.value.starts_with('C.') { + base.value[2..] + } else { + base.value + } + g.write('((${g.cname(cast_name)}*)') + g.gen_expr(cast_child_id) + g.write(')->${g.cname(node.value)}') + } + } else if base.kind == .cast_expr && base.children_count > 0 { + needs_paren := base.kind !in [.ident, .selector] + if needs_paren { + g.write('(') + } + g.gen_expr(base_id) + if needs_paren { + g.write(')') + } + if node.op == .arrow || base_type0 is types.Pointer { + g.write('->') + } else { + g.write('.') + } + g.write(g.cname(node.value)) + } else if node.value == 'len' && base.kind == .ident { + base_type := g.tc.resolve_type(base_id) + if fixed := array_fixed_type(types.unwrap_pointer(base_type)) { + g.write(g.fixed_array_len_value(fixed)) + } else { + raw_type := g.tc.cur_scope.lookup(base.value) or { base_type } + g.gen_expr(base_id) + if raw_type is types.Pointer { + g.write('->len') + } else { + g.write('.len') + } + } + } else if base.kind == .ident && !base_is_local && g.selector_base_is_module(base.value) { + mod := g.selector_base_module(base.value) or { '' } + short_mod := if mod.contains('.') { + mod.all_after_last('.') + } else { + mod + } + // A module-level const is stored under the importing module's full path + // (e.g. `v3.gen.wasm`), matching its function naming. Reference it by that + // exact storage name rather than the short alias, otherwise we'd emit an + // undeclared `wasm__x` for a const defined as `v3__gen__wasm__x`. + full_qname := g.const_storage_name(mod, node.value) + if full_qname in g.const_vals { + g.write(g.cname(full_qname)) + } else { + g.write(g.cname('${short_mod}.${node.value}')) + } + } else if base.kind == .selector && base.children_count > 0 + && g.is_module_qualified_enum(base) { + inner_base := g.a.child_node(&base, 0) + mod := g.import_alias_module(inner_base.value) or { inner_base.value } + short_mod := if mod.contains('.') { + mod.all_after_last('.') + } else { + mod + } + qname := '${short_mod}.${base.value}' + if qname in g.tc.enum_names || base.value in g.tc.enum_names { + ekey := '${qname}.${node.value}' + ekey2 := '${base.value}.${node.value}' + if expr := g.enum_value_expr_for_key(ekey) { + g.write(expr) + } else if expr := g.enum_value_expr_for_key(ekey2) { + g.write(expr) + } else { + g.write(g.cname('${qname}.${node.value}')) + } + } else { + g.write(g.cname('${qname}.${node.value}')) + } + } else if g.gen_struct_default_global_selector(base, node.value, node.op) { + // handled + } else if embedded := g.direct_embedded_field_for_selector(base_type0, node.value) { + needs_paren := base.kind !in [.ident, .selector] + if needs_paren { + g.write('(') + } + g.gen_expr(base_id) + if needs_paren { + g.write(')') + } + if node.op == .arrow || base_type0 is types.Pointer { + g.write('->') + } else { + g.write('.') + } + g.write(g.cname(embedded.name)) + } else if embedded_path := g.embedded_field_path_for_promoted_selector(base_type0, + node.value) + { + needs_paren := base.kind !in [.ident, .selector] + if needs_paren { + g.write('(') + } + g.gen_expr(base_id) + if needs_paren { + g.write(')') + } + mut is_ptr := node.op == .arrow || base_type0 is types.Pointer + mut embedded_owner := types.unwrap_pointer(base_type0) + for embedded in embedded_path { + op := if is_ptr { '->' } else { '.' } + g.write('${op}${g.cname(embedded.name)}') + is_ptr = embedded.typ is types.Pointer + || cgen_unalias_type(embedded.typ) is types.Pointer + embedded_owner = types.unwrap_pointer(embedded.typ) + } + final_op := if is_ptr { '->' } else { '.' } + g.write('${final_op}${g.cname(node.value)}') + if embedded_owner is types.Struct { + if _ := g.shared_field_info(embedded_owner.name, node.value) { + g.write('->val') + } + } + } else { + needs_paren := base.kind !in [.ident, .selector] + if needs_paren { + g.write('(') + } + g.gen_expr(base_id) + if needs_paren { + g.write(')') + } + mut is_ptr := false + mut local_type_known := base_is_source_mut_pointer_deref + if base.kind == .ident { + if typ := g.tc.cur_scope.lookup(base.value) { + local_type_known = true + is_ptr = typ is types.Pointer || cgen_unalias_type(typ) is types.Pointer + } + } else if base.kind == .selector { + if declared := g.selector_declared_type(base_id) { + is_ptr = declared is types.Pointer + || cgen_unalias_type(declared) is types.Pointer + } else { + resolved := g.tc.resolve_type(base_id) + is_ptr = resolved is types.Pointer + || cgen_unalias_type(resolved) is types.Pointer + } + } else { + mut stable_base_type := base_type0 + if base.kind == .call { + if base.children_count > 0 { + callee := g.a.child_node(&base, 0) + if callee.kind == .selector && callee.children_count > 0 + && callee.value in ['first', 'last', 'pop', 'pop_left'] { + receiver_type := + types.unwrap_pointer(g.usable_expr_type(g.a.child(callee, 0))) + if receiver_array := array_like_type(receiver_type) { + stable_base_type = receiver_array.elem_type + } + } + } + if resolved_name := g.tc.resolved_call_name(base_id) { + if resolved_type := g.tc.fn_ret_types[resolved_name] { + stable_base_type = resolved_type + } + } + } + // A transformed selector can retain an `.arrow` hint from an + // earlier inference pass. Once the base's semantic type is known, + // let that type decide value (`.`) versus pointer (`->`). + local_type_known = stable_base_type !is types.Unknown + && stable_base_type !is types.Void + is_ptr = stable_base_type is types.Pointer + || cgen_unalias_type(stable_base_type) is types.Pointer + } + if (node.op == .arrow && !local_type_known) || is_ptr { + g.write('->') + } else { + g.write('.') + } + g.write(g.cname(node.value)) + } + } + .index { + if g.gen_explicit_generic_callee_index(node) { + return + } + base_id := g.a.child(node, 0) + mut base_type := g.usable_expr_type(base_id) + if storage_type := g.const_storage_type_from_node(g.a.nodes[int(base_id)]) { + base_type = storage_type + } + if info := g.tc.index_overload_call_info(base_type, false) { + g.gen_index_overload_call(node, base_id, base_type, info) + } else if node.value == 'range' { + g.gen_slice_expr(node, base_id, base_type) + } else if base_type is types.Map { + c_key := g.map_key_temp_c_type(base_type.key_type) + c_val := g.value_c_type(base_type.value_type) + g.write('(*(${c_val}*)map__get(&') + g.gen_expr(base_id) + g.write(', &(${c_key}[]){') + g.gen_expr(g.a.child(node, 1)) + g.write('}, ') + g.gen_default_value_addr_for_type(base_type.value_type) + g.write('))') + } else if g.gen_index_operator_get_call(node) { + return + } else { + mut index_base_type := base_type + if fixed_lit := g.fixed_array_literal_index_type(base_id, node) { + g.gen_expr_with_expected_type(base_id, types.Type(fixed_lit)) + g.write('[') + g.gen_expr(g.a.child(node, 1)) + g.write(']') + return + } + initial_is_fixed_array_index, _, _ := fixed_array_index_info(index_base_type) + if !initial_is_fixed_array_index { + base_node := g.a.nodes[int(base_id)] + if const_type := g.const_storage_type_from_node(base_node) { + const_is_fixed, _, _ := fixed_array_index_info(const_type) + if const_is_fixed { + index_base_type = const_type + } + } + } + is_fixed_array_index, fixed_is_ptr, _ := fixed_array_index_info(index_base_type) + if is_fixed_array_index { + if fixed_is_ptr { + g.write('(*') + g.gen_expr(base_id) + g.write(')') + } else { + needs_paren := g.fixed_array_index_base_needs_paren(base_id) + if needs_paren { + g.write('(') + } + g.gen_expr(base_id) + if needs_paren { + g.write(')') + } + } + g.write('[') + g.gen_expr(g.a.child(node, 1)) + g.write(']') + } else { + is_array_index, is_ptr, arr_type := array_index_info(index_base_type) + if is_array_index { + if g.gen_shared_array_index_value_expr(base_id, g.a.child(node, 1)) { + return + } + index_type := if node.typ.starts_with('?') || node.typ.starts_with('!') { + g.parse_node_type(node) + } else { + g.array_index_type_for_expected_arg(arr_type.elem_type, node) + } + c_elem := g.value_c_type(index_type) + if g.direct_array_access || g.unsafe_depth > 0 { + g.write('(*((${c_elem}*)((') + g.gen_expr(base_id) + g.write(if is_ptr { ')->data' } else { ').data' }) + g.write(') + (') + g.gen_expr(g.a.child(node, 1)) + g.write(')))') + return + } + g.write('(*(${c_elem}*)array_get(') + base_node := g.a.nodes[int(base_id)] + if is_ptr && !(base_node.kind == .ident + && g.local_ident_is_shared_wrapper(base_node.value)) { + g.write('*') + } + g.gen_expr(base_id) + g.write(', ') + g.gen_expr(g.a.child(node, 1)) + g.write('))') + } else { + is_runtime_array, runtime_is_ptr := + runtime_array_struct_index_info(index_base_type) + base_node := g.a.nodes[int(base_id)] + local_is_runtime_array := if base_node.kind == .ident { + local_ct := g.local_storage_c_type(base_node.value) or { '' } + local_ct == 'Array' || local_ct == 'array' + } else { + false + } + if is_runtime_array || local_is_runtime_array { + mut index_type := g.usable_expr_type(id) + if index_type is types.Unknown || index_type is types.Void { + if node.typ.len > 0 && node.typ != 'unknown' { + index_type = g.parse_node_type(node) + } + } + index_type = g.array_index_type_for_expected_arg(index_type, node) + c_elem := g.value_c_type(index_type) + g.write('(*(${c_elem}*)array_get(') + if runtime_is_ptr { + g.write('*') + } + g.gen_expr(base_id) + g.write(', ') + g.gen_expr(g.a.child(node, 1)) + g.write('))') + } else if base_type is types.String { + // Parenthesize the base: a smartcast sum variant yields a deref + // like `*v._string`, and `*v._string.str[i]` would bind as + // `*(v._string.str[i])`. `(*v._string).str[i]` is what we want. + g.write('(') + g.gen_expr(base_id) + g.write(').str[') + g.gen_expr(g.a.child(node, 1)) + g.write(']') + } else if base_type is types.Pointer { + ptr_type := base_type + if ptr_type.base_type is types.Void { + g.write('((u8*)') + g.gen_expr(base_id) + g.write(')[') + g.gen_expr(g.a.child(node, 1)) + g.write(']') + } else { + g.write('(') + g.gen_expr(base_id) + g.write(')[') + g.gen_expr(g.a.child(node, 1)) + g.write(']') + } + } else { + g.gen_expr(base_id) + g.write('[') + g.gen_expr(g.a.child(node, 1)) + g.write(']') + } + } + } + } + } + .array_init { + raw_init_type := g.tc.parse_type(node.value) + init_type := raw_init_type + if init_type is types.ArrayFixed { + c_elem, dims := g.fixed_array_decl_parts(init_type) + g.write('(${c_elem}${dims}){0}') + } else { + c_elem := g.sizeof_target(node.value) + g.write('array_new(sizeof(${c_elem}), 0, 0)') + } + } + .map_init { + g.gen_map_init(id, node) + } + .sql_expr { + panic('internal error: SQL expression reached C backend after transform') + } + .cast_expr { + target_type := g.tc.parse_type(node.value) + semantic_target := cgen_unalias_type(target_type) + mut ct := if node.value.starts_with('fn_ptr:') { + g.resolve_fn_ptr_type(node.value) + } else { + g.cast_c_type(target_type) + } + if ct.starts_with('fn_ptr:') { + ct = g.resolve_fn_ptr_type(ct) + } + cast_arg := g.a.child_node(node, 0) + if shared_alias_ptr := g.shared_alias_pointer_type_from_text(node.value) { + g.gen_expr_with_expected_type(g.a.child(node, 0), shared_alias_ptr) + return + } + if cast_arg.kind == .nil_literal && target_type !is types.Pointer { + g.gen_default_value_for_type(target_type) + return + } + if semantic_target is types.Interface && cast_arg.kind == .none_expr + && g.is_ierror_type_name(semantic_target.name) { + g.write(g.ierror_none_literal_string()) + } else if semantic_target is types.Interface { + if !g.gen_interface_value_expr(g.a.child(node, 0), semantic_target) { + g.gen_expr(g.a.child(node, 0)) + } + } else if semantic_target is types.SumType { + g.gen_sum_cast_expr(semantic_target, g.a.child(node, 0)) + } else if semantic_target is types.OptionType || semantic_target is types.ResultType { + g.gen_optional_arg(g.a.child(node, 0), semantic_target) + } else if target_type is types.Pointer + && g.gen_sum_pointer_cast_expr(g.a.child(node, 0), target_type, ct) { + return + } else if target_type is types.Pointer + && g.gen_sum_variant_pointer_cast(g.a.child(node, 0), target_type, ct) { + return + } else if target_type is types.Pointer + && g.gen_pointer_cast_fixed_array_literal(g.a.child(node, 0), target_type, ct) { + return + } else if target_type is types.Pointer + && g.gen_pointer_cast_from_array_ref(g.a.child(node, 0), target_type, ct) { + return + } else if target_type is types.Pointer + && g.gen_pointer_cast_from_map_value_address(g.a.child(node, 0), target_type) { + return + } else if ct == 'map*' { + child_id := g.a.child(node, 0) + child_node := g.a.nodes[int(child_id)] + if child_node.kind == .call && child_node.children_count > 0 { + callee := g.a.child_node(&child_node, 0) + if callee.kind == .ident + && callee.value in ['array_get', 'array__get', 'map__get', 'map__get_check', 'memdup', 'v3_aligned_memdup'] { + g.write('(${ct})') + g.gen_expr(child_id) + return + } + } + if target_type is types.Pointer { + if g.gen_map_pointer_cast_from_value_address(child_id, target_type) { + return + } + } + if child_node.kind == .prefix && child_node.op == .amp { + g.write('(${ct})(') + g.gen_expr(child_id) + g.write(')') + } else { + g.write('&(') + g.gen_expr(child_id) + g.write(')') + } + return + } else if target_type is types.Pointer + && g.gen_cast_from_mut_param_address(g.a.child(node, 0), ct) { + return + } else if target_type is types.Pointer + && g.gen_cast_from_mut_pointer_param_value(g.a.child(node, 0), ct) { + return + } else if fixed := array_fixed_type(target_type) { + literal := g.fixed_array_compound_literal_expr(g.a.child(node, 0), fixed) + if trimmed_space(literal).len > 0 { + g.write(literal) + } else { + g.write('(${ct})(') + g.gen_expr(g.a.child(node, 0)) + g.write(')') + } + } else { + g.write('(${ct})(') + g.gen_expr(g.a.child(node, 0)) + g.write(')') + } + } + .struct_init { + g.gen_struct_init(id) + } + .if_expr { + g.gen_if_expr(node) + } + .array_literal { + if arr := array_like_type(g.usable_expr_type(id)) { + g.gen_array_literal_value(node, arr.elem_type) + return + } + g.write('{') + for i in 0 .. node.children_count { + if i > 0 { + g.write(', ') + } + g.gen_expr(g.a.child(node, i)) + } + g.write('}') + } + .nil_literal { + g.write('NULL') + } + .none_expr { + if g.is_ierror_type_name(g.expected_expr_type.name()) { + g.write(g.ierror_none_literal_string()) + } else { + ct := g.optional_type_name(g.optional_none_type(id)) + g.write('(${ct}){.ok = false}') + } + } + .or_expr { + g.gen_or_expr(node) + } + .block { + if node.children_count > 1 { + // Lowered collection expressions can introduce a lexical defer inside a + // GNU statement expression. Keep it visible to returns/propagations in + // this block, then discard it before generating the enclosing function. + defer_start := g.defers.len + g.write('({') + for bi in 0 .. node.children_count - 1 { + g.gen_node(g.a.child(node, bi)) + } + last_id := g.a.child(node, node.children_count - 1) + last := g.a.nodes[int(last_id)] + if last.kind == .expr_stmt { + g.gen_expr(g.a.child(&last, 0)) + } else if int(last.kind) >= int(flat.NodeKind.int_literal) + && int(last.kind) <= int(flat.NodeKind.in_expr) { + // A bare expression value (lowered const initializers end the + // block with one); gen_node would emit nothing for it. + g.gen_expr(last_id) + } else { + g.gen_node(last_id) + } + g.write(';})') + g.trim_defers(defer_start) + } else if node.children_count > 0 { + last_id := g.a.child(node, 0) + last := g.a.nodes[int(last_id)] + if last.kind == .expr_stmt { + g.gen_expr(g.a.child(&last, 0)) + } else { + g.gen_expr(last_id) + } + } + } + .is_expr { + expr_id := g.a.child(node, 0) + expr_type := g.tc.resolve_type(expr_id) + clean := cgen_unalias_unwrap_all_pointers(expr_type) + expr_node := g.a.nodes[int(expr_id)] + type_depth := cgen_type_pointer_depth(expr_type) + subject_is_pointer := type_depth > 0 + mut extra_deref := if type_depth > 1 { type_depth - 1 } else { 0 } + if expr_node.kind == .ident { + if local_ct := g.local_storage_c_type(expr_node.value) { + local_depth := cgen_c_type_pointer_depth(local_ct) + if local_depth > type_depth { + extra_deref += local_depth - type_depth + } + } + } + if clean is types.SumType { + idx := g.sum_type_index(clean.name, node.value) + g.write('(') + if subject_is_pointer { + g.gen_is_expr_subject(expr_id, extra_deref) + g.write('->typ == ${idx}') + } else { + g.gen_is_expr_subject(expr_id, extra_deref) + g.write('.typ == ${idx}') + } + g.write(')') + } else if clean is types.Interface { + idx := if g.is_ierror_type_name(clean.name) { + g.ierror_type_id_for_pattern(node.value) + } else { + g.iface_type_id_for_pattern(clean.name, node.value) + } + if idx == 0 { + g.write('0') + return + } + g.write('(') + if subject_is_pointer { + g.gen_is_expr_subject(expr_id, extra_deref) + g.write('->_typ == ${idx}') + } else { + g.gen_is_expr_subject(expr_id, extra_deref) + g.write('._typ == ${idx}') + } + g.write(')') + } else if g.is_ierror_type_name(types.Type(clean).name()) { + idx := g.ierror_type_id_for_pattern(node.value) + if idx == 0 { + g.write('0') + return + } + g.write('(') + if subject_is_pointer { + g.gen_is_expr_subject(expr_id, extra_deref) + g.write('->_typ == ${idx}') + } else { + g.gen_is_expr_subject(expr_id, extra_deref) + g.write('._typ == ${idx}') + } + g.write(')') + } else { + g.write('1') + } + } + .as_expr { + expr_id := g.a.child(node, 0) + expr_type0 := g.usable_expr_type(expr_id) + expr_type := if expr_type0 is types.Unknown || expr_type0 is types.Void { + g.tc.resolve_type(expr_id) + } else { + expr_type0 + } + clean := types.unwrap_pointer(expr_type) + if clean is types.SumType { + qv := g.resolve_variant(clean.name, node.value) + field := g.sum_field_name(qv) + if g.variant_references_sum(qv, clean.name) { + g.write('(*') + if expr_type.is_pointer() { + g.gen_expr(expr_id) + g.write('->${field})') + } else { + g.gen_expr(expr_id) + g.write('.${field})') + } + } else { + if expr_type.is_pointer() { + g.gen_expr(expr_id) + g.write('->${field}') + } else { + g.gen_expr(expr_id) + g.write('.${field}') + } + } + } else if clean is types.OptionType { + if clean.base_type is types.Void { + g.gen_expr(expr_id) + } else { + g.gen_expr(expr_id) + if expr_type.is_pointer() { + g.write('->value') + } else { + g.write('.value') + } + } + } else if clean is types.ResultType { + if clean.base_type is types.Void { + g.gen_expr(expr_id) + } else { + g.gen_expr(expr_id) + if expr_type.is_pointer() { + g.write('->value') + } else { + g.write('.value') + } + } + } else if clean is types.Interface || g.is_ierror_type_name(types.Type(clean).name()) { + target := g.tc.parse_type(node.value) + if target is types.Pointer { + g.write('(${g.tc.c_type(target)})') + if expr_type.is_pointer() { + g.write('(') + g.gen_expr(expr_id) + g.write(')') + g.write('->_object') + } else { + g.gen_expr(expr_id) + g.write('._object') + } + } else { + g.write('(*(${g.tc.c_type(target)}*)') + if expr_type.is_pointer() { + g.write('(') + g.gen_expr(expr_id) + g.write(')') + g.write('->_object)') + } else { + g.gen_expr(expr_id) + g.write('._object)') + } + } + } else { + g.gen_expr(expr_id) + } + } + .sizeof_expr { + g.write('sizeof(${g.sizeof_target(node.value)})') + } + .typeof_expr { + g.gen_typeof_name(node) + } + .offsetof_expr { + ct := g.type_name_c_type(node.value) + g.write('offsetof(${ct}, ${g.cname(node.typ)})') + } + .assoc { + g.gen_assoc_expr(node) + } + .empty { + g.write('0') + } + else {} + } +} + +fn (g &FlatGen) fixed_array_index_base_needs_paren(base_id flat.NodeId) bool { + if int(base_id) < 0 || int(base_id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(base_id)] + return node.kind !in [.ident, .selector] +} + +fn (mut g FlatGen) gen_struct_default_global_selector(base flat.Node, field string, op flat.Op) bool { + if g.struct_default_module.len == 0 || base.kind != .ident || base.value.len == 0 { + return false + } + qname := qualify_name_in_module(g.struct_default_module, base.value) + if qname !in g.global_types && base.value !in g.global_types { + return false + } + global_name := if g.struct_default_module in ['', 'main', 'builtin'] { + base.value + } else { + qname + } + g.write(g.cname(global_name)) + if op == .arrow { + g.write('->') + } else { + g.write('.') + } + g.write(g.cname(field)) + return true +} + +fn (mut g FlatGen) gen_pointer_cast_fixed_array_literal(arg_id flat.NodeId, target_type types.Pointer, ct string) bool { + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return false + } + mut literal_id := arg_id + mut arg := g.a.nodes[int(arg_id)] + if arg.kind == .postfix && arg.op == .not && arg.children_count > 0 { + literal_id = g.a.child(&arg, 0) + arg = g.a.nodes[int(literal_id)] + } + if arg.kind != .array_literal { + return false + } + elem_ct := g.value_c_type(target_type.base_type) + g.write('(${ct})((${elem_ct}[]){') + for i in 0 .. arg.children_count { + if i > 0 { + g.write(', ') + } + g.gen_expr_with_expected_type(g.a.child(&arg, i), target_type.base_type) + } + g.write('})') + return true +} + +fn (mut g FlatGen) gen_pointer_cast_from_array_ref(arg_id flat.NodeId, target_type types.Pointer, ct string) bool { + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return false + } + if target_type.base_type is types.Void { + return false + } + base_ct := g.tc.c_type(target_type.base_type) + if base_ct in ['Array', 'array'] { + return false + } + if _ := array_like_type(target_type.base_type) { + return false + } + arg := g.a.nodes[int(arg_id)] + if arg.kind != .prefix || arg.op != .amp || arg.children_count == 0 { + return false + } + child_id := g.a.child(&arg, 0) + child_type := g.usable_expr_type(child_id) + if child_type is types.Pointer { + if _ := array_like_type(child_type.base_type) { + g.write('(${ct})(') + g.gen_expr(child_id) + g.write('->data)') + return true + } + return false + } + if _ := array_like_type(child_type) { + g.write('(${ct})((') + g.gen_expr(child_id) + g.write(').data)') + return true + } + return false +} + +fn (mut g FlatGen) gen_typeof_name(node flat.Node) { + if node.value.len == 0 && node.children_count > 0 { + expr_id := g.a.child(&node, 0) + mut expr_type := cgen_unalias_type(g.usable_expr_type(expr_id)) + mut is_pointer := false + if expr_type is types.Pointer { + is_pointer = true + expr_type = cgen_unalias_type(expr_type.base_type) + } + if expr_type is types.SumType { + sum_name := g.resolve_sum_name(expr_type.name) + variants := g.tc.sum_types[sum_name] or { []string{} } + if variants.len > 0 { + unknown_name := 'unknown ' + typeof_display_type_name(sum_name) + unknown_sid := g.intern_string(unknown_name) + g.write('((string[]){_str_${unknown_sid}') + for variant in variants { + mut display_name := typeof_display_type_name(variant) + if display_name.starts_with('main.') { + display_name = display_name[5..] + } + sid := g.intern_string(display_name) + g.write(', _str_${sid}') + } + g.write('})[') + if is_pointer { + g.write('v3_sum_ptr_type_idx(') + g.gen_expr(expr_id) + g.write(')]') + } else { + g.write('(') + g.gen_expr(expr_id) + g.write(').typ]') + } + return + } + } + } + type_name := g.typeof_type_name(node) + sid := g.intern_string(type_name) + g.write('_str_${sid}') +} + +fn (g &FlatGen) typeof_type_name(node flat.Node) string { + if node.value.len > 0 { + return typeof_display_type_name(node.value) + } + if node.children_count == 0 { + return '' + } + expr_id := g.a.child(&node, 0) + expr_type := g.usable_expr_type(expr_id) + if expr_type !is types.Unknown && expr_type !is types.Void { + return typeof_display_resolved_type_name(expr_type) + } + resolved := g.tc.resolve_type(expr_id) + if resolved !is types.Unknown && resolved !is types.Void { + return typeof_display_resolved_type_name(resolved) + } + return '' +} + +fn typeof_display_resolved_type_name(typ types.Type) string { + if typ is types.ArrayFixed { + len_text := if typ.len_expr.len > 0 { typ.len_expr } else { typ.len.str() } + return '[${len_text}]' + typeof_display_type_name(typ.elem_type.name()) + } + return typeof_display_type_name(typ.name()) +} + +// typeof_display_type_name canonicalizes internal suffix-form fixed-array +// texts (`[]int[3]`) back to V syntax (`[][3]int`) for `typeof(x).name`. +fn typeof_display_type_name(name string) string { + if name.starts_with('[]') { + return '[]' + typeof_display_type_name(name[2..]) + } + if name.starts_with('&') { + return '&' + typeof_display_type_name(name[1..]) + } + if name.starts_with('?') || name.starts_with('!') { + return name[..1] + typeof_display_type_name(name[1..]) + } + if name.starts_with('mut ') { + return 'mut ' + typeof_display_type_name(name[4..]) + } + if name.starts_with('shared ') { + return 'shared ' + typeof_display_type_name(name[7..]) + } + if name.starts_with('chan ') { + return 'chan ' + typeof_display_type_name(name[5..]) + } + if name.starts_with('map[') { + close := typeof_display_type_name_matching_bracket(name, 3) + if close > 3 && close < name.len - 1 { + key := typeof_display_type_name(name[4..close]) + value := typeof_display_type_name(name[close + 1..]) + return 'map[${key}]${value}' + } + } + if name.starts_with('fn(') || name.starts_with('fn (') { + return typeof_display_fn_type_name(name) + } + if name.ends_with(']') && !name.starts_with('[') && !name.starts_with('map[') { + outer_open := name.index_u8(`[`) + if outer_open > 0 + && typeof_display_type_name_matching_bracket(name, outer_open) == name.len - 1 { + args_text := name[outer_open + 1..name.len - 1] + if !typeof_display_fixed_array_len_text(args_text) { + return name[..outer_open] + '[' + typeof_display_type_name_list(args_text) + ']' + } + } + if open_idx := name.last_index('[') { + if open_idx > 0 { + len_text := name[open_idx + 1..name.len - 1] + if typeof_display_fixed_array_len_text(len_text) { + return '[${len_text}]' + typeof_display_type_name(name[..open_idx]) + } + } + } + } + return name +} + +fn typeof_display_fixed_array_len_text(text string) bool { + clean := text.trim_space() + if clean.len == 0 || clean.contains(',') || clean.contains('[') || clean.contains(']') { + return false + } + if clean.starts_with('fn(') || clean.starts_with('fn (') || clean.starts_with('chan ') + || clean.starts_with('shared ') || clean.starts_with('atomic ') || clean.starts_with('mut ') + || clean.starts_with('thread ') { + return false + } + if clean[0] >= `0` && clean[0] <= `9` { + return true + } + if clean[0] == `(` && clean.ends_with(')') { + return typeof_display_fixed_array_len_text(clean[1..clean.len - 1]) + } + if types.is_builtin_type_name(clean) { + return false + } + for i, ch in clean { + if ch in [`+`, `*`, `/`, `%`, `|`, `^`, `<`, `>`] || ((ch == `-` || ch == `&`) && i > 0) { + return true + } + } + last := clean.all_after_last('.') + return last.len > 0 && last[0] >= `a` && last[0] <= `z` +} + +fn typeof_display_fn_type_name(name string) string { + clean := name.trim_space() + open := clean.index_u8(`(`) + close := typeof_display_type_name_matching_paren(clean, open) + if close < 0 { + return name + } + params := typeof_display_fn_param_type_name_list(clean[open + 1..close]) + mut result := 'fn (${params})' + ret := clean[close + 1..].trim_space() + if ret.len == 0 { + return result + } + if ret.starts_with('(') { + ret_close := typeof_display_type_name_matching_paren(ret, 0) + if ret_close == ret.len - 1 { + return result + ' (' + typeof_display_type_name_list(ret[1..ret_close]) + ')' + } + } + return result + ' ' + typeof_display_type_name(ret) +} + +fn typeof_display_fn_param_type_name_list(text string) string { + mut parts := []string{} + mut start := 0 + mut paren_depth := 0 + mut bracket_depth := 0 + for i in 0 .. text.len { + match text[i] { + `(` { + paren_depth++ + } + `)` { + paren_depth-- + } + `[` { + bracket_depth++ + } + `]` { + bracket_depth-- + } + `,` { + if paren_depth == 0 && bracket_depth == 0 { + parts << typeof_display_fn_param_type_name(text[start..i].trim_space()) + start = i + 1 + } + } + else {} + } + } + if start < text.len { + parts << typeof_display_fn_param_type_name(text[start..].trim_space()) + } + return parts.join(', ') +} + +fn typeof_display_fn_param_type_name(param string) string { + mut text := param.trim_space() + mut is_mut := false + if text.starts_with('mut ') { + is_mut = true + text = text[4..].trim_space() + } + space := typeof_display_top_level_space_index(text) + if space > 0 { + head := text[..space].trim_space() + tail := text[space + 1..].trim_space() + if typeof_display_fn_param_head_is_name(head, tail) { + text = tail + } + } + if text.starts_with('mut ') { + is_mut = true + text = text[4..].trim_space() + } + if text.starts_with('&') { + if is_mut { + return 'mut ' + typeof_display_type_name(text[1..]) + } + return typeof_display_type_name(text[1..]) + } + if is_mut { + return 'mut ' + typeof_display_type_name(text) + } + return typeof_display_type_name(text) +} + +fn typeof_display_top_level_space_index(text string) int { + mut paren_depth := 0 + mut bracket_depth := 0 + for i in 0 .. text.len { + match text[i] { + `(` { + paren_depth++ + } + `)` { + paren_depth-- + } + `[` { + bracket_depth++ + } + `]` { + bracket_depth-- + } + ` ` { + if paren_depth == 0 && bracket_depth == 0 { + return i + } + } + else {} + } + } + return -1 +} + +fn typeof_display_fn_param_head_is_name(head string, tail string) bool { + if head.len == 0 || tail.len == 0 { + return false + } + if head.starts_with('fn') || head.starts_with('&') || head.starts_with('[') { + return false + } + if head in ['mut', 'shared', 'atomic', 'chan', 'thread', 'map'] || head.contains('.') { + return false + } + if types.is_builtin_type_name(head) { + return false + } + return (head[0] >= `a` && head[0] <= `z`) || head[0] == `_` +} + +fn typeof_display_type_name_matching_paren(text string, open int) int { + if open < 0 || open >= text.len || text[open] != `(` { + return -1 + } + mut depth := 0 + for i in open .. text.len { + if text[i] == `(` { + depth++ + } else if text[i] == `)` { + depth-- + if depth == 0 { + return i + } + } + } + return -1 +} + +fn typeof_display_type_name_matching_bracket(text string, open int) int { + if open < 0 || open >= text.len || text[open] != `[` { + return -1 + } + mut depth := 0 + for i in open .. text.len { + if text[i] == `[` { + depth++ + } else if text[i] == `]` { + depth-- + if depth == 0 { + return i + } + } + } + return -1 +} + +fn typeof_display_type_name_list(text string) string { + mut parts := []string{} + mut start := 0 + mut paren_depth := 0 + mut bracket_depth := 0 + for i in 0 .. text.len { + match text[i] { + `(` { + paren_depth++ + } + `)` { + paren_depth-- + } + `[` { + bracket_depth++ + } + `]` { + bracket_depth-- + } + `,` { + if paren_depth == 0 && bracket_depth == 0 { + parts << typeof_display_type_name(text[start..i].trim_space()) + start = i + 1 + } + } + else {} + } + } + if start < text.len { + parts << typeof_display_type_name(text[start..].trim_space()) + } + return parts.join(', ') +} + +fn (g &FlatGen) typeof_type_index(node flat.Node) int { + type_name := g.typeof_type_name(node) + return g.type_index_for_type_name(type_name) +} + +fn (g &FlatGen) type_index_for_type_name(type_name string) int { + if type_name.len == 0 { + return 0 + } + mut base_name := type_name.trim_space() + mut indirections := 0 + for base_name.starts_with('&') { + indirections++ + base_name = base_name[1..].trim_space() + } + // Builtin types keep V's stable ast `*_type_idx` values (int==8, string==21, ...), so + // comparisons against `v.ast` constants behave like the reference compiler. + builtin_idx := builtin_ast_type_idx(base_name) + indirection_bits := int(u32(indirections) << 16) + if builtin_idx > 0 { + return builtin_idx | indirection_bits + } + mut candidate_names := []string{cap: 2} + candidate_names << base_name + if !base_name.contains('.') && g.tc.cur_module.len > 0 + && g.tc.cur_module !in ['', 'main', 'builtin'] { + candidate_names << '${g.tc.cur_module}.${base_name}' + } + mut sum_names := []string{} + if g.tc.cur_module.len > 0 { + sum_names << '${g.tc.cur_module}.Primitive' + } + sum_names << 'orm.Primitive' + sum_names << 'Primitive' + for sum_name in sum_names { + if sum_name !in g.tc.sum_types { + continue + } + for candidate in candidate_names { + idx := g.sum_type_index(sum_name, candidate) + if idx != 0 { + return idx | indirection_bits + } + } + } + for candidate in candidate_names { + sum_name := g.find_sum_type_for_variant(candidate) + if sum_name.len > 0 { + idx := g.sum_type_index_resolved(sum_name, candidate) + if idx != 0 { + return idx | indirection_bits + } + } + } + return 0 +} + +fn (g &FlatGen) find_sum_type_for_variant(variant string) string { + mut best := '' + for sum_name, _ in g.tc.sum_types { + if g.sum_type_index_resolved(sum_name, variant) == 0 { + continue + } + if sum_name.contains('.') { + return sum_name + } + if best.len == 0 { + best = sum_name + } + } + return best +} + +// builtin_ast_type_idx maps a builtin type name to V's stable ast `*_type_idx` value +// (vlib/v/ast/types.v), so `typeof[T]().idx` comparisons against `v.ast` constants behave +// like the reference compiler. Returns 0 for non-builtin types. +fn builtin_ast_type_idx(name string) int { + return match name { + 'void' { 1 } + 'voidptr' { 2 } + 'byteptr' { 3 } + 'charptr' { 4 } + 'i8' { 5 } + 'i16' { 6 } + 'i32' { 7 } + 'int' { 8 } + 'i64' { 9 } + 'isize' { 10 } + 'u8', 'byte' { 11 } + 'u16' { 12 } + 'u32' { 13 } + 'u64' { 14 } + 'usize' { 15 } + 'f32' { 16 } + 'f64' { 17 } + 'char' { 18 } + 'bool' { 19 } + 'none' { 20 } + 'string' { 21 } + 'rune' { 22 } + 'float literal' { 27 } + 'int literal' { 28 } + 'thread' { 29 } + 'nil' { 31 } + else { 0 } + } +} + +fn (mut g FlatGen) gen_string_infix_fallback(node flat.Node, lhs_id flat.NodeId, rhs_id flat.NodeId) bool { + if node.op in [.eq, .ne] + && (g.expr_is_non_string_scalar_value(lhs_id) || g.expr_is_non_string_scalar_value(rhs_id)) { + g.write('(') + g.gen_expr(lhs_id) + g.write(if node.op == .eq { ' == ' } else { ' != ' }) + g.gen_expr(rhs_id) + g.write(')') + return true + } + match node.op { + .plus { + g.write('string__plus(') + g.gen_expr_as_string(lhs_id) + g.write(', ') + g.gen_expr_as_string(rhs_id) + g.write(')') + } + .eq { + g.write('string__eq(') + g.gen_expr_as_string(lhs_id) + g.write(', ') + g.gen_expr_as_string(rhs_id) + g.write(')') + } + .ne { + g.write('!string__eq(') + g.gen_expr_as_string(lhs_id) + g.write(', ') + g.gen_expr_as_string(rhs_id) + g.write(')') + } + .lt { + g.write('string__lt(') + g.gen_expr_as_string(lhs_id) + g.write(', ') + g.gen_expr_as_string(rhs_id) + g.write(')') + } + .gt { + g.write('string__lt(') + g.gen_expr_as_string(rhs_id) + g.write(', ') + g.gen_expr_as_string(lhs_id) + g.write(')') + } + .le { + g.write('!string__lt(') + g.gen_expr_as_string(rhs_id) + g.write(', ') + g.gen_expr_as_string(lhs_id) + g.write(')') + } + .ge { + g.write('!string__lt(') + g.gen_expr_as_string(lhs_id) + g.write(', ') + g.gen_expr_as_string(rhs_id) + g.write(')') + } + else { + return false + } + } + + return true +} + +fn (mut g FlatGen) gen_thread_infix_eq(node flat.Node, lhs_id flat.NodeId, rhs_id flat.NodeId, lhs_type types.Type, rhs_type types.Type) bool { + if node.op !in [.eq, .ne] || lhs_type is types.Pointer || rhs_type is types.Pointer + || g.tc.c_type(lhs_type) != '__v_thread' || g.tc.c_type(rhs_type) != '__v_thread' { + return false + } + lhs_name := g.tmp_name() + rhs_name := g.tmp_name() + g.write('({ __v_thread ${lhs_name} = ') + g.gen_expr(lhs_id) + g.write('; __v_thread ${rhs_name} = ') + g.gen_expr(rhs_id) + g.write('; ') + if node.op == .ne { + g.write('!') + } + g.write('__v_thread_equal(${lhs_name}, ${rhs_name}); })') + return true +} + +// gen_map_infix_eq lowers `map == map` / `map != map` to the runtime map +// equality helper. C cannot compare `struct map` values with `==`, so without +// this the generated comparison fails to compile. Pointer operands are left +// alone (like the array-equality path): `&m1 == &m2` must keep comparing the +// pointer addresses, not the pointed-to map contents. +fn (mut g FlatGen) gen_map_infix_eq(node flat.Node, lhs_id flat.NodeId, rhs_id flat.NodeId, lhs_type types.Type, rhs_type types.Type) bool { + if node.op !in [.eq, .ne] { + return false + } + if lhs_type is types.Pointer || rhs_type is types.Pointer { + return false + } + clean_lhs := map_str_clean_type(lhs_type) + if clean_lhs !is types.Map || map_str_clean_type(rhs_type) !is types.Map { + return false + } + // v3_map_map_eq compares value payloads it does not recognize bytewise. That + // is wrong whenever a value's semantic equality differs from its bytes — a + // struct/sum type/fixed array holding strings, arrays or maps. The + // transformer lowers those maps directly to element-wise comparisons; only + // fall back to the raw helper for value types it compares correctly (its + // size dispatch handles primitives, pointers, strings, and dynamic maps and + // arrays of those). Otherwise leave the comparison unlowered rather than + // emit a silently incorrect result. + if !g.map_value_bytewise_eq_safe((clean_lhs as types.Map).value_type) { + return false + } + if node.op == .ne { + g.write('!') + } + g.write('v3_map_map_eq(') + g.gen_expr(lhs_id) + g.write(', ') + g.gen_expr(rhs_id) + g.write(')') + return true +} + +// map_value_bytewise_eq_safe reports whether v3_map_map_eq compares a map value +// of this type correctly. Its size dispatch handles primitive/pointer/string +// values and recurses through dynamic maps and arrays of such values, but +// falls back to a raw memcmp for anything else (structs, sum types, fixed +// arrays, interfaces, options), which breaks semantic equality. +fn (g &FlatGen) map_value_bytewise_eq_safe(value_type types.Type) bool { + clean := default_init_unalias_type(value_type) + if clean is types.Map { + // v3_map_map_eq recurses into map values through itself, so a nested map + // is safe as long as its own value type is. + return g.map_value_bytewise_eq_safe(clean.value_type) + } + if clean is types.Array { + // The runtime helper's Array case only compares string elements + // (array_eq_string) or primitive/pointer elements (array_eq_raw) + // correctly; arrays of maps, structs, or nested arrays fall through to a + // bytewise element compare of their descriptors. Only flat element types + // are safe here — anything else is left to the transform's element-wise + // path. + return g.map_scalar_bytewise_eq_safe(clean.elem_type) + } + return g.map_scalar_bytewise_eq_safe(clean) +} + +// map_scalar_bytewise_eq_safe reports whether a bytewise (memcmp/array_eq_raw) +// comparison of a single value of this type matches its semantic equality. +// True only for types with no indirection to follow: primitives, enums, +// pointers (compared by address), and strings (which v3_map_map_eq / array +// helpers special-case). +fn (g &FlatGen) map_scalar_bytewise_eq_safe(t types.Type) bool { + clean := default_init_unalias_type(t) + return clean is types.Primitive || clean is types.Char || clean is types.Rune + || clean is types.ISize || clean is types.USize || clean is types.Enum + || clean is types.Pointer || clean is types.String || clean is types.Nil +} + +fn (mut g FlatGen) gen_array_infix_eq(node flat.Node, lhs_id flat.NodeId, rhs_id flat.NodeId, lhs_type types.Type, rhs_type types.Type) bool { + if node.op !in [.eq, .ne] { + return false + } + if lhs_type is types.Pointer || rhs_type is types.Pointer { + return false + } + if g.gen_fixed_array_infix_eq(node, lhs_id, rhs_id, lhs_type, rhs_type) { + return true + } + mut lhs_arr := types.Array{ + elem_type: types.Type(types.void_) + } + mut rhs_arr := types.Array{ + elem_type: types.Type(types.void_) + } + mut lhs_is_arr := false + mut rhs_is_arr := false + if arr := array_like_type(types.unwrap_pointer(lhs_type)) { + lhs_arr = arr + lhs_is_arr = true + } + if arr := array_like_type(types.unwrap_pointer(rhs_type)) { + rhs_arr = arr + rhs_is_arr = true + } + if !lhs_is_arr && !rhs_is_arr { + return false + } + if !lhs_is_arr { + lhs_arr = rhs_arr + } + if !rhs_is_arr { + rhs_arr = lhs_arr + } + mut elem_type := if lhs_arr.elem_type.name() != 'unknown' { + lhs_arr.elem_type + } else { + rhs_arr.elem_type + } + // A specialized generic return can retain its unresolved placeholder as the + // default `int` type in this late cgen query. A concrete literal on the other + // side still carries the real element type and is authoritative after the + // checker has accepted the comparison. + if literal_elem := g.array_equality_literal_elem_type(lhs_id) { + elem_type = literal_elem + } + if literal_elem := g.array_equality_literal_elem_type(rhs_id) { + elem_type = literal_elem + } + if node.op == .ne { + g.write('!') + } + clean_elem_type := default_init_unalias_type(elem_type) + if clean_elem_type is types.String { + g.write('array_eq_string(') + } else if clean_elem_type is types.Array { + g.write('array_eq_array(') + } else { + g.write('array_eq_raw(') + } + g.gen_array_value_arg(lhs_id, lhs_type, lhs_arr) + g.write(', ') + g.gen_array_value_arg(rhs_id, rhs_type, rhs_arr) + if clean_elem_type is types.Array { + g.write(', ${array_equality_depth_from_elem_type(elem_type)}') + } else if clean_elem_type !is types.String { + g.write(', sizeof(${g.sizeof_target(g.tc.c_type(elem_type))})') + } + g.write(')') + return true +} + +fn array_equality_depth_from_elem_type(elem_type types.Type) int { + clean := default_init_unalias_type(elem_type) + if clean is types.Array { + return 1 + array_equality_depth_from_elem_type(clean.elem_type) + } + return 1 +} + +fn (g &FlatGen) array_equality_literal_elem_type(id flat.NodeId) ?types.Type { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return none + } + node := g.a.node(id) + if node.kind == .paren && node.children_count == 1 { + return g.array_equality_literal_elem_type(g.a.child(node, 0)) + } + if node.kind != .array_literal || node.children_count == 0 { + return none + } + for i in 0 .. node.children_count { + child_id := g.a.child(node, i) + child := g.a.node(child_id) + if child.kind == .prefix && child.value == '...' { + continue + } + match child.kind { + .string_literal, .string_interp { + return types.Type(types.String{}) + } + .char_literal { + return types.Type(types.Rune{}) + } + .float_literal { + return g.tc.parse_type(if child.typ == 'f32' { 'f32' } else { 'f64' }) + } + .bool_literal { + return g.tc.parse_type('bool') + } + else {} + } + typ := g.usable_expr_type(child_id) + if typ !is types.Unknown && typ !is types.Void { + return typ + } + } + return none +} + +fn (mut g FlatGen) gen_fixed_array_infix_eq(node flat.Node, lhs_id flat.NodeId, rhs_id flat.NodeId, lhs_type types.Type, rhs_type types.Type) bool { + mut lhs_fixed := types.ArrayFixed{ + elem_type: types.Type(types.void_) + } + mut rhs_fixed := types.ArrayFixed{ + elem_type: types.Type(types.void_) + } + mut lhs_is_fixed := false + mut rhs_is_fixed := false + if fixed := array_fixed_type(types.unwrap_pointer(lhs_type)) { + lhs_fixed = fixed + lhs_is_fixed = true + } + if fixed := array_fixed_type(types.unwrap_pointer(rhs_type)) { + rhs_fixed = fixed + rhs_is_fixed = true + } + if !lhs_is_fixed && !rhs_is_fixed { + return false + } + fixed := if lhs_is_fixed { lhs_fixed } else { rhs_fixed } + if !lhs_is_fixed && !g.expr_can_be_fixed_array_literal(lhs_id) { + return false + } + if !rhs_is_fixed && !g.expr_can_be_fixed_array_literal(rhs_id) { + return false + } + if node.op == .ne { + g.write('!') + } + g.write('(memcmp(') + g.gen_fixed_array_eq_arg(lhs_id, fixed) + g.write(', ') + g.gen_fixed_array_eq_arg(rhs_id, fixed) + g.write(', sizeof(${g.value_sizeof_target(types.Type(fixed))})) == 0)') + return true +} + +fn (mut g FlatGen) gen_fixed_array_eq_arg(id flat.NodeId, fixed types.ArrayFixed) { + if g.expr_can_be_fixed_array_literal(id) { + g.write('(${g.fixed_array_c_type(fixed)})') + } + g.gen_expr_with_expected_type(id, types.Type(fixed)) +} + +fn (g &FlatGen) expr_can_be_fixed_array_literal(id flat.NodeId) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(id)] + if node.kind == .array_literal { + return true + } + if node.kind == .postfix && node.op == .not && node.children_count == 1 { + child := g.a.nodes[int(g.a.child(&node, 0))] + return child.kind == .array_literal + } + return false +} + +fn (mut g FlatGen) gen_array_value_arg(id flat.NodeId, typ types.Type, fallback types.Array) { + node := g.a.nodes[int(id)] + semantic_array_in_pointer_storage := typ is types.Array && node.kind == .ident + && g.local_storage_is_pointer(node.value) + if typ is types.Pointer || semantic_array_in_pointer_storage { + g.write('*') + } + if node.kind == .array_literal { + g.gen_array_literal_value(node, fallback.elem_type) + } else { + g.gen_expr(id) + } +} + +fn (g &FlatGen) fixed_array_literal_index_type(base_id flat.NodeId, node flat.Node) ?types.ArrayFixed { + if int(base_id) < 0 || int(base_id) >= g.a.nodes.len { + return none + } + base := g.a.nodes[int(base_id)] + mut literal := base + if base.kind == .postfix && base.op == .not && base.children_count == 1 { + child := g.a.nodes[int(g.a.child(&base, 0))] + if child.kind != .array_literal { + return none + } + literal = child + } else if base.kind != .array_literal { + return none + } + mut elem_type := g.parse_node_type(&node) + if elem_type is types.Void && literal.children_count > 0 { + elem_type = g.usable_expr_type(g.a.child(&literal, 0)) + } + if elem_type is types.Void { + return none + } + return types.ArrayFixed{ + elem_type: elem_type + len: int(literal.children_count) + } +} + +fn char_escape_codepoint(s string) ?int { + if s.starts_with('\\x') && s.len > 2 { + return parse_hex_codepoint(s[2..]) + } + if s.starts_with('\\u{') { + end := s.index('}') or { return none } + return parse_hex_codepoint(s[3..end]) + } + if s.starts_with('\\u') && s.len >= 6 { + return parse_hex_codepoint(s[2..6]) + } + if s.starts_with('\\U') && s.len >= 10 { + return parse_hex_codepoint(s[2..10]) + } + return none +} + +fn c_char_literal_byte_value(s string) ?int { + if s.len == 1 { + return int(s[0]) + } + if s.len < 2 || s[0] != `\\` { + return none + } + if s.len == 2 { + return int(s[1]) + } + if s[1] == `x` { + value := parse_hex_codepoint(s[2..]) or { return none } + if value <= 0xff { + return value + } + return none + } + if s[1] < `0` || s[1] > `7` || s.len > 4 { + return none + } + mut value := 0 + for digit in s[1..].bytes() { + if digit < `0` || digit > `7` { + return none + } + value = value * 8 + int(digit - `0`) + } + if value <= 0xff { + return value + } + return none +} + +fn escape_c_string_literal_quotes(s string) string { + if !s.contains('"') { + return s + } + mut out := strings.new_builder(s.len + 4) + mut preceding_backslashes := 0 + for ch in s.bytes() { + if ch == `"` && preceding_backslashes % 2 == 0 { + out.write_u8(`\\`) + } + out.write_u8(ch) + if ch == `\\` { + preceding_backslashes++ + } else { + preceding_backslashes = 0 + } + } + return out.str() +} + +fn c_segmented_string_literal(s string) string { + max_segment_len := 12_000 + if s.len <= max_segment_len { + return '"${c_escape(s)}"' + } + mut out := strings.new_builder(s.len + (s.len / max_segment_len + 1) * 4) + mut start := 0 + for start < s.len { + end := if start + max_segment_len < s.len { start + max_segment_len } else { s.len } + if start > 0 { + out.write_string(' "') + } else { + out.write_u8(`"`) + } + out.write_string(c_escape(s[start..end])) + out.write_u8(`"`) + start = end + } + return out.str() +} + +fn parse_hex_codepoint(hex string) ?int { + if hex.len == 0 { + return none + } + mut value := 0 + for ch in hex.bytes() { + digit := if ch >= `0` && ch <= `9` { + int(ch - `0`) + } else if ch >= `a` && ch <= `f` { + int(ch - `a`) + 10 + } else if ch >= `A` && ch <= `F` { + int(ch - `A`) + 10 + } else { + return none + } + value = value * 16 + digit + } + return value +} + +fn array_membership_fn_name(elem_type types.Type, fixed bool) string { + prefix := if fixed { 'fixed_array_contains_' } else { 'array_contains_' } + elem_name := elem_type.name() + suffix := match elem_name { + 'string' { 'string' } + 'u8', 'byte' { 'u8' } + else { 'int' } + } + + return prefix + suffix +} + +fn (g &FlatGen) is_module_qualified_enum(base flat.Node) bool { + if base.kind != .selector || base.children_count == 0 { + return false + } + inner_base := g.a.child_node(&base, 0) + if inner_base.kind != .ident || !g.has_import_alias(inner_base.value) { + return false + } + mod := g.import_alias_module(inner_base.value) or { inner_base.value } + short_mod := if mod.contains('.') { mod.all_after_last('.') } else { mod } + qname := '${short_mod}.${base.value}' + return qname in g.tc.enum_names || base.value in g.tc.enum_names +} + +fn (mut g FlatGen) preamble() { + use_system_libc := g.c_directives_use_system_libc() + g.writeln('typedef signed char i8;') + g.writeln('typedef short i16;') + g.writeln('typedef int i32;') + g.writeln('typedef long long i64;') + g.writeln('typedef unsigned char u8;') + g.writeln('typedef unsigned char byte;') + g.writeln('typedef unsigned short u16;') + g.writeln('typedef unsigned int u32;') + g.writeln('typedef unsigned long long u64;') + g.writeln('static inline i64 __v_pow_i64(i64 base, i64 exponent) { if (exponent < 0) { if (base == 0) return -1; if (base != 1 && base != -1) return 0; return (exponent & 1) != 0 ? base : 1; } i64 value = 1; i64 power = base; for (; exponent > 0; exponent >>= 1) { if ((exponent & 1) != 0) value *= power; power *= power; } return value; }') + g.writeln('static inline u64 __v_pow_u64(u64 base, i64 exponent) { if (exponent < 0) { if (base == 0) return (u64)-1; return base == 1 ? 1 : 0; } u64 value = 1; u64 power = base; for (; exponent > 0; exponent >>= 1) { if ((exponent & 1) != 0) value *= power; power *= power; } return value; }') + g.writeln('#ifdef _MSC_VER') + g.writeln('#ifdef _WIN64') + g.writeln('typedef unsigned __int64 size_t;') + g.writeln('typedef __int64 ptrdiff_t;') + g.writeln('typedef unsigned __int64 uintptr_t;') + g.writeln('typedef __int64 intptr_t;') + g.writeln('#else') + g.writeln('typedef unsigned int size_t;') + g.writeln('typedef int ptrdiff_t;') + g.writeln('typedef unsigned int uintptr_t;') + g.writeln('typedef int intptr_t;') + g.writeln('#endif') + g.writeln('#else') + g.writeln('typedef __SIZE_TYPE__ size_t;') + g.writeln('typedef __PTRDIFF_TYPE__ ptrdiff_t;') + g.writeln('typedef __UINTPTR_TYPE__ uintptr_t;') + g.writeln('typedef __INTPTR_TYPE__ intptr_t;') + g.writeln('#endif') + if !use_system_libc { + g.writeln('#if !defined(_TIME_T) && !defined(_TIME_T_DEFINED) && !defined(__time_t_defined) && !defined(_BSD_TIME_T_DEFINED_) && !defined(_TIME_T_DECLARED)') + g.writeln('typedef long long time_t;') + g.writeln('#endif') + } + g.writeln('#ifndef __bool_true_false_are_defined') + g.writeln('#ifdef _MSC_VER') + g.writeln('typedef unsigned char bool;') + g.writeln('#else') + g.writeln('typedef _Bool bool;') + g.writeln('#endif') + g.writeln('#define __bool_true_false_are_defined 1') + g.writeln('#endif') + g.writeln('typedef void* voidptr;') + g.writeln('typedef int int_literal;') + g.writeln('typedef double float_literal;') + g.writeln('struct sync__Channel;') + g.writeln('typedef struct sync__Channel* chan;') + g.writeln('#ifndef true') + g.writeln('#define true 1') + g.writeln('#endif') + g.writeln('#ifndef false') + g.writeln('#define false 0') + g.writeln('#endif') + g.writeln('#define _S(s) ((string){.str=(u8*)("" s), .len=(sizeof(s)-1), .is_lit=1})') + if use_system_libc { + g.writeln('typedef ptrdiff_t isize;') + g.writeln('typedef size_t usize;') + g.writeln('typedef char* charptr;') + g.writeln('typedef unsigned char* byteptr;') + g.writeln('typedef int (*qsort_callback_func)(const void*, const void*);') + g.writeln('#ifndef VCALLCONV') + g.writeln('#define VCALLCONV(x)') + g.writeln('#endif') + g.writeln('#if !defined(VNORETURN)') + g.writeln('#if defined(__TINYC__)') + g.writeln('#define VNORETURN __attribute__((noreturn))') + g.writeln('#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L') + g.writeln('#define VNORETURN _Noreturn') + g.writeln('#elif defined(__GNUC__) && __GNUC__ >= 2') + g.writeln('#define VNORETURN __attribute__((noreturn))') + g.writeln('#endif') + g.writeln('#ifndef VNORETURN') + g.writeln('#define VNORETURN') + g.writeln('#endif') + g.writeln('#endif') + g.write(manual_stdlib_c_headers()) + g.writeln('void abort(void);') + g.system_libc_headers() + g.system_libc_preamble() + } else { + g.headerless_libc_preamble() + } + g.write_arch_macros() + g.writeln('') + if !g.has_builtins { + g.writeln('typedef struct {') + g.writeln('\tchar* str;') + g.writeln('\tint len;') + g.writeln('\tint is_lit;') + g.writeln('} string;') + g.writeln('') + } + g.writeln('#define elem_size element_size') + g.writeln('#define c_name types__c_name') + if g.has_builtins { + // C inserts written for the v1 ABI commonly reference builtin functions with + // their old module prefix. Emit these aliases before any C source include so + // both early type providers and delayed native implementations see them. + g.writeln('#define builtin__string_clone string__clone') + g.writeln('#define builtin__tos2 tos2') + return + } + g.writeln('typedef struct Array { void* data; int len; int cap; int elem_size; } Array;') + g.writeln('') +} + +fn (g &FlatGen) c_directives_use_system_libc() bool { + for directive in g.c_directives { + for line in directive.text.split_into_lines() { + clean := trimmed_space(line) + if c_directive_name(clean) in ['include', 'import'] { + arg := c_directive_arg(clean) + // Closure/thread runtime helpers are implemented against the standalone + // declarations in headerless_libc_preamble(). Do not let unrelated + // builtin headers (for example ) inherit this exemption. + if directive.module in ['builtin', 'builtin.closure', 'closure'] + && arg in ['', '', ''] { + continue + } + // A quoted local header can include system headers itself. Emit the + // system preamble first so its declarations do not conflict with the + // standalone declarations from the headerless preamble. + if arg.len > 0 && arg != '' { + return true + } + } + } + } + return false +} + +fn (mut g FlatGen) system_libc_headers() { + for header in ['assert.h', 'ctype.h', 'errno.h', 'float.h', 'inttypes.h', 'limits.h', 'math.h', + 'setjmp.h', 'signal.h', 'stdatomic.h', 'stdbool.h', 'stddef.h', 'stdint.h', 'time.h', + 'wchar.h'] { + g.writeln('#include <${header}>') + } + g.writeln('#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)') + g.writeln('#include ') + g.writeln('#endif') + g.writeln('#ifdef _WIN32') + g.writeln('#include ') + g.writeln('#include ') + g.writeln('#include ') + g.writeln('#else') + for header in ['dirent.h', 'dlfcn.h', 'fcntl.h', 'netdb.h', 'netinet/in.h', 'pthread.h', + 'arpa/inet.h', 'netinet/tcp.h', 'semaphore.h', 'sys/ioctl.h', 'sys/mman.h', 'sys/resource.h', + 'sys/socket.h', 'sys/stat.h', 'sys/statvfs.h', 'sys/time.h', 'sys/types.h', 'sys/utsname.h', + 'sys/un.h', 'sys/wait.h', 'termios.h', 'unistd.h', 'utime.h'] { + g.writeln('#include <${header}>') + } + g.writeln('#endif') + g.writeln('#ifdef __APPLE__') + g.writeln('#include ') + g.writeln('#include ') + g.writeln('#endif') + g.writeln('#if defined(__linux__) || defined(__ANDROID__)') + g.writeln('#include ') + g.writeln('#endif') + g.writeln('#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)') + g.writeln('#include ') + g.writeln('#endif') + g.writeln('') +} + +fn (mut g FlatGen) system_libc_preamble() { + g.collect_preserved_c_fns(c_headerless_libc_declared_fns) + g.collect_preserved_c_fns([ + 'kevent', + 'kqueue', + 'mach_timebase_info', + 'nanosleep', + 'pthread_condattr_destroy', + 'pthread_condattr_init', + 'pthread_condattr_setpshared', + 'pthread_equal', + 'pthread_self', + ]) + g.collect_preserved_c_structs(c_preserved_system_include_struct_names('')) + g.collect_preserved_c_structs(['__stat64', 'kevent', 'sigaction']) + g.writeln('#ifdef _WIN32') + g.writeln('typedef struct { HANDLE handle; void* context; } __v_thread;') + g.writeln('static bool __v_thread_equal(__v_thread a, __v_thread b) { return a.handle == b.handle; }') + g.writeln('typedef void* (*__v_thread_start_fn)(void*);') + g.writeln('typedef struct { __v_thread_start_fn start; void* arg; void* result; } __v_windows_thread_context;') + g.writeln('static const size_t __v_thread_stack_size = V_THREAD_STACK_SIZE;') + g.writeln('static void* __v_thread_alloc(size_t size) { void* p = malloc(size); if (!p) { fprintf(stderr, "V thread allocation failed\\n"); abort(); } return p; }') + g.writeln('static DWORD WINAPI __v_windows_thread_start(void* raw_context) { __v_windows_thread_context* context = (__v_windows_thread_context*)raw_context; context->result = context->start(context->arg); return 0; }') + g.writeln('static __v_thread __v_thread_spawn(__v_thread_start_fn start, void* arg, void (*cleanup)(void*)) {') + g.writeln('\t__v_thread result;') + g.writeln('\t__v_windows_thread_context* context = (__v_windows_thread_context*)__v_thread_alloc(sizeof(__v_windows_thread_context));') + g.writeln('\tcontext->start = start; context->arg = arg; context->result = NULL;') + g.writeln('\tresult.context = context;') + g.writeln('\tresult.handle = CreateThread(NULL, __v_thread_stack_size, __v_windows_thread_start, context, 0, NULL);') + g.writeln('\tif (!result.handle) { DWORD error = GetLastError(); free(context); if (cleanup) cleanup(arg); fprintf(stderr, "V thread creation failed: %lu\\n", (unsigned long)error); abort(); }') + g.writeln('\treturn result;') + g.writeln('}') + g.writeln('static void* __v_thread_join(__v_thread thread) {') + g.writeln('\tDWORD rc = WaitForSingleObject(thread.handle, INFINITE);') + g.writeln('\tif (rc != WAIT_OBJECT_0) { fprintf(stderr, "V thread join failed: %lu\\n", (unsigned long)rc); abort(); }') + g.writeln('\tvoid* result = ((__v_windows_thread_context*)thread.context)->result;') + g.writeln('\tif (!CloseHandle(thread.handle)) { DWORD error = GetLastError(); free(thread.context); fprintf(stderr, "V thread handle cleanup failed: %lu\\n", (unsigned long)error); abort(); }') + g.writeln('\tfree(thread.context);') + g.writeln('\treturn result;') + g.writeln('}') + g.writeln('#else') + g.writeln('typedef struct { pthread_t handle; } __v_thread;') + g.writeln('static bool __v_thread_equal(__v_thread a, __v_thread b) { return pthread_equal(a.handle, b.handle) != 0; }') + g.writeln('typedef void* (*__v_thread_start_fn)(void*);') + g.writeln('static const size_t __v_thread_stack_size = V_THREAD_STACK_SIZE;') + g.writeln('static void* __v_thread_alloc(size_t size) { void* p = malloc(size); if (!p) { fprintf(stderr, "V thread allocation failed\\n"); abort(); } return p; }') + g.writeln('static __v_thread __v_thread_spawn(__v_thread_start_fn start, void* arg, void (*cleanup)(void*)) {') + g.writeln('\t__v_thread result;') + g.writeln('\tpthread_attr_t attr;') + g.writeln('\tint rc = pthread_attr_init(&attr);') + g.writeln('\tif (rc != 0) { if (cleanup) cleanup(arg); fprintf(stderr, "V thread attribute initialization failed: %d\\n", rc); abort(); }') + g.writeln('\trc = pthread_attr_setstacksize(&attr, __v_thread_stack_size);') + g.writeln('\tif (rc != 0) { pthread_attr_destroy(&attr); if (cleanup) cleanup(arg); fprintf(stderr, "V thread stack size setup failed: %d\\n", rc); abort(); }') + g.writeln('\trc = pthread_create(&result.handle, &attr, (void*)start, arg);') + g.writeln('\tint attr_rc = pthread_attr_destroy(&attr);') + g.writeln('\tif (rc != 0) { if (cleanup) cleanup(arg); fprintf(stderr, "V thread creation failed: %d\\n", rc); abort(); }') + g.writeln('\tif (attr_rc != 0) { fprintf(stderr, "V thread attribute cleanup failed: %d\\n", attr_rc); abort(); }') + g.writeln('\treturn result;') + g.writeln('}') + g.writeln('static void* __v_thread_join(__v_thread thread) { void* result = NULL; int rc = pthread_join(thread.handle, &result); if (rc != 0) { fprintf(stderr, "V thread join failed: %d\\n", rc); abort(); } return result; }') + g.writeln('#endif') +} + +fn (mut g FlatGen) thread_stack_size_definition() { + g.writeln('#ifndef V_THREAD_STACK_SIZE') + g.writeln('#define V_THREAD_STACK_SIZE ${g.thread_stack_size}') + g.writeln('#endif') +} + +fn (mut g FlatGen) c99_feature_test_macros() { + if !g.c99_mode { + return + } + g.writeln('#if defined(__linux__) && !defined(_GNU_SOURCE)') + g.writeln('#define _GNU_SOURCE') + g.writeln('#endif') + g.writeln('#if defined(__linux__) && !defined(_POSIX_C_SOURCE)') + g.writeln('#define _POSIX_C_SOURCE 200809L') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_libc_preamble() { + g.collect_preserved_c_fns(c_headerless_libc_declared_fns) + g.writeln(c_stdint_header_text()) + g.writeln('#ifndef NULL') + g.writeln('#define NULL ((void*)0)') + g.writeln('#endif') + g.writeln('#ifndef offsetof') + g.writeln('#if defined(_MSC_VER) && !defined(__clang__)') + g.writeln('#define offsetof(type, member) ((size_t)&(((type*)0)->member))') + g.writeln('#else') + g.writeln('#define offsetof(type, member) __builtin_offsetof(type, member)') + g.writeln('#endif') + g.writeln('#endif') + g.writeln('#ifndef UINTPTR_MAX') + g.writeln('#if defined(__LP64__) || defined(_WIN64)') + g.writeln('#define UINTPTR_MAX 18446744073709551615ULL') + g.writeln('#else') + g.writeln('#define UINTPTR_MAX 4294967295U') + g.writeln('#endif') + g.writeln('#endif') + g.writeln('#ifndef EOF') + g.writeln('#define EOF (-1)') + g.writeln('#endif') + g.writeln('#ifndef RAND_MAX') + g.writeln('#define RAND_MAX 2147483647') + g.writeln('#endif') + g.writeln('#ifndef FLT_EPSILON') + g.writeln('#define FLT_EPSILON 1.19209290e-7F') + g.writeln('#endif') + g.writeln('#ifndef DBL_EPSILON') + g.writeln('#define DBL_EPSILON 2.2204460492503131e-16') + g.writeln('#endif') + g.writeln('#ifndef FLT_MAX') + g.writeln('#ifdef __FLT_MAX__') + g.writeln('#define FLT_MAX __FLT_MAX__') + g.writeln('#else') + g.writeln('#define FLT_MAX 3.4028234663852886e+38F') + g.writeln('#endif') + g.writeln('#endif') + g.writeln('#ifndef DBL_MAX') + g.writeln('#ifdef __DBL_MAX__') + g.writeln('#define DBL_MAX __DBL_MAX__') + g.writeln('#else') + g.writeln('#define DBL_MAX 1.7976931348623158e+308') + g.writeln('#endif') + g.writeln('#endif') + g.writeln('#ifndef SEEK_SET') + g.writeln('#define SEEK_SET 0') + g.writeln('#endif') + g.writeln('#ifndef SEEK_CUR') + g.writeln('#define SEEK_CUR 1') + g.writeln('#endif') + g.writeln('#ifndef SEEK_END') + g.writeln('#define SEEK_END 2') + g.writeln('#endif') + g.writeln('#ifndef _IOFBF') + g.writeln('#define _IOFBF 0') + g.writeln('#endif') + g.writeln('#ifndef _IOLBF') + g.writeln('#define _IOLBF 1') + g.writeln('#endif') + g.writeln('#ifndef _IONBF') + g.writeln('#define _IONBF 2') + g.writeln('#endif') + g.headerless_windows_sdk_types() + g.writeln('#if !defined(__FILE_defined) && !defined(_FILE_DEFINED) && !defined(_FILEDEFED) && !defined(__DEFINED_FILE) && !defined(_FILE_DECLARED) && !defined(__FILE_DECLARED)') + g.writeln('typedef struct FILE FILE;') + g.writeln('#endif') + g.writeln('typedef struct DIR DIR;') + g.writeln('#if !defined(_SSIZE_T) && !defined(_SSIZE_T_DEFINED) && !defined(__ssize_t_defined) && !defined(_SSIZE_T_DECLARED)') + g.writeln('typedef intptr_t ssize_t;') + g.writeln('#endif') + g.writeln('extern char** environ;') + g.writeln('void* malloc(size_t size);') + g.writeln('void* calloc(size_t count, size_t size);') + g.writeln('void* realloc(void* ptr, size_t size);') + g.writeln('void free(void* ptr);') + g.writeln('int printf(const char* format, ...);') + g.writeln('int fprintf(FILE* stream, const char* format, ...);') + g.writeln('int fseek(FILE* stream, long offset, int whence);') + g.writeln('char* getenv(const char* name);') + g.writeln('int setenv(const char* name, const char* value, int overwrite);') + g.writeln('int unsetenv(const char* name);') + g.writeln('void abort(void);') + for name in c_function_like_macro_decl_names() { + g.writeln('#ifdef ${name}') + g.writeln('#undef ${name}') + g.writeln('#endif') + } + g.writeln('void* memset(void* s, int c, size_t n);') + g.writeln('void* memcpy(void* dest, const void* src, size_t n);') + g.writeln('void* memmove(void* dest, const void* src, size_t n);') + g.writeln('int memcmp(const void* s1, const void* s2, size_t n);') + g.writeln('size_t strlen(const char* s);') + g.writeln('int strcmp(const char* s1, const char* s2);') + g.writeln('int strncmp(const char* s1, const char* s2, size_t n);') + g.writeln('char* strncpy(char* dest, const char* src, size_t n);') + g.writeln('double floor(double x);') + g.writeln('double ceil(double x);') + g.writeln('float floorf(float x);') + g.writeln('float ceilf(float x);') + g.writeln('double sqrt(double x);') + g.writeln('double pow(double x, double y);') + g.writeln('double ldexp(double x, int exp);') + g.writeln('double fmod(double x, double y);') + g.writeln('double cos(double x);') + g.writeln('double acos(double x);') + g.writeln('double fabs(double x);') + g.writeln('#ifndef _WIN32') + g.writeln('int open(const char* path, int flags, ...);') + g.writeln('ssize_t read(int fd, void* buf, size_t count);') + g.writeln('int fork(void);') + g.writeln('int dup2(int oldfd, int newfd);') + g.writeln('int execlp(const char* file, const char* arg, ...);') + g.writeln('int execvp(const char* file, char* const argv[]);') + g.writeln('void _exit(int status);') + g.writeln('#endif') + g.writeln('int access(const char* path, int mode);') + g.writeln('char* realpath(const char* path, char* resolved_path);') + g.writeln('char* strrchr(const char* s, int c);') + g.writeln('char* strstr(const char* haystack, const char* needle);') + g.writeln('int snprintf(char* str, size_t size, const char* format, ...);') + g.writeln('int fcntl(int fd, int cmd, ...);') + g.writeln('int pipe(int* pipefds);') + g.writeln('int close(int fd);') + g.writeln('void* signal(int sig, void* handler);') + g.writeln('int atexit(void (*f)(void));') + g.writeln('#ifdef __APPLE__') + g.writeln('const char* _dyld_get_image_name(unsigned int image_index);') + g.writeln('struct mach_header;') + g.writeln('const struct mach_header* _dyld_get_image_header(unsigned int image_index);') + g.writeln('#endif') + g.writeln('#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__DragonFly__)') + g.writeln('extern FILE* __stdinp;') + g.writeln('extern FILE* __stdoutp;') + g.writeln('extern FILE* __stderrp;') + g.writeln('#define stdin __stdinp') + g.writeln('#define stdout __stdoutp') + g.writeln('#define stderr __stderrp') + g.writeln('int* __error(void);') + g.writeln('#define errno (*__error())') + g.writeln('#elif defined(__OpenBSD__)') + g.writeln('struct __sFstub { long _stub; };') + g.writeln('extern struct __sFstub __stdin[];') + g.writeln('extern struct __sFstub __stdout[];') + g.writeln('extern struct __sFstub __stderr[];') + g.writeln('#define stdin ((FILE*)__stdin)') + g.writeln('#define stdout ((FILE*)__stdout)') + g.writeln('#define stderr ((FILE*)__stderr)') + g.writeln('extern int errno;') + g.writeln('#elif defined(__NetBSD__)') + g.writeln('#if defined(__LP64__) || defined(_LP64)') + g.writeln('struct __netbsd_FILE_stub { unsigned char _opaque[152]; };') + g.writeln('#else') + g.writeln('struct __netbsd_FILE_stub { unsigned char _opaque[88]; };') + g.writeln('#endif') + g.writeln('extern struct __netbsd_FILE_stub __sF[];') + g.writeln('#define stdin ((FILE*)&__sF[0])') + g.writeln('#define stdout ((FILE*)&__sF[1])') + g.writeln('#define stderr ((FILE*)&__sF[2])') + g.writeln('extern int errno;') + g.writeln('#elif defined(__ANDROID__)') + g.writeln('extern FILE* stdin;') + g.writeln('extern FILE* stdout;') + g.writeln('extern FILE* stderr;') + g.writeln('int* __errno(void);') + g.writeln('#define errno (*__errno())') + g.writeln('#elif defined(__linux__)') + g.writeln('extern FILE* stdin;') + g.writeln('extern FILE* stdout;') + g.writeln('extern FILE* stderr;') + g.writeln('int* __errno_location(void);') + g.writeln('#define errno (*__errno_location())') + g.writeln('#elif defined(_WIN32)') + g.writeln('extern FILE* stdin;') + g.writeln('extern FILE* stdout;') + g.writeln('extern FILE* stderr;') + g.writeln('int* _errno(void);') + g.writeln('#define errno (*_errno())') + g.writeln('#else') + g.writeln('extern FILE* stdin;') + g.writeln('extern FILE* stdout;') + g.writeln('extern FILE* stderr;') + g.writeln('extern int errno;') + g.writeln('#endif') + g.writeln('typedef int pid_t;') + g.writeln('#if !defined(_OFF_T) && !defined(_OFF_T_DEFINED) && !defined(__off_t_defined) && !defined(_BSD_OFF_T_DEFINED_) && !defined(_OFF_T_DECLARED)') + g.writeln('typedef long long off_t;') + g.writeln('#endif') + // Fallback pthread declarations for the headerless build. They are only + // emitted when the platform's real guards are absent, so when the + // system header is included its exact types win. The opaque unions are + // deliberately oversized (and over-aligned) to safely back the real objects + // on mainstream targets; a platform whose pthread objects exceed these sizes + // must be built with its real in scope. + g.writeln('#if !defined(_BITS_PTHREADTYPES_COMMON_H) && !defined(_PTHREAD_H) && !defined(_PTHREADTYPES_H_) && !defined(_PTHREADTYPES_H) && !defined(__pthread_t_defined) && !defined(_SYS__PTHREAD_TYPES_H_) && !defined(_PTHREAD_T)') + g.writeln('typedef void* pthread_t;') + g.writeln('typedef union { unsigned char _opaque[64]; long long _align; } pthread_attr_t;') + g.writeln('typedef union { unsigned char _opaque[128]; long long _align; } pthread_mutex_t;') + g.writeln('typedef union { unsigned char _opaque[128]; long long _align; } pthread_cond_t;') + g.writeln('typedef union { unsigned char _opaque[256]; long long _align; } pthread_rwlock_t;') + g.writeln('typedef union { unsigned char _opaque[64]; long long _align; } pthread_rwlockattr_t;') + g.writeln('typedef union { unsigned char _opaque[64]; long long _align; } pthread_condattr_t;') + g.writeln('typedef union { unsigned char _opaque[16]; long long _align; } pthread_once_t;') + g.writeln('typedef unsigned long pthread_key_t;') + g.writeln('#endif') + g.writeln('int pthread_key_create(pthread_key_t* key, void (*dtor)(void*));') + g.writeln('void* pthread_getspecific(pthread_key_t key);') + g.writeln('int pthread_setspecific(pthread_key_t key, const void* const_ptr);') + g.writeln('typedef union { unsigned char _opaque[128]; long long _align; } sem_t;') + g.writeln('#if !defined(__sigset_t_defined) && !defined(_SIGSET_T_DECLARED) && !defined(_SIGSET_T_DEFINED) && !defined(_SIGSET_T)') + g.writeln('typedef union { unsigned char _opaque[128]; long long _align; } sigset_t;') + g.writeln('#endif') + g.writeln('int ptrace(int request, pid_t pid, void* addr, int data);') + g.writeln('int sigaddset(sigset_t* set, int signal_number);') + g.writeln('int sigprocmask(int how, const sigset_t* set, sigset_t* old_set);') + g.headerless_stdarg_decls() + g.writeln('#ifndef PTHREAD_MUTEX_INITIALIZER') + g.writeln('#ifdef __APPLE__') + g.writeln('#define PTHREAD_MUTEX_INITIALIZER { ._opaque = { 0xa7, 0xab, 0xaa, 0x32 } }') + g.writeln('#else') + g.writeln('#define PTHREAD_MUTEX_INITIALIZER { 0 }') + g.writeln('#endif') + g.writeln('#endif') + g.writeln('int pthread_attr_init(pthread_attr_t* attr);') + g.writeln('int pthread_attr_destroy(pthread_attr_t* attr);') + g.writeln('int pthread_attr_setstacksize(pthread_attr_t* attr, size_t stacksize);') + g.writeln('int pthread_attr_setdetachstate(pthread_attr_t* attr, int detachstate);') + g.writeln('#ifndef PTHREAD_CREATE_DETACHED') + g.writeln('#ifdef __APPLE__') + g.writeln('#define PTHREAD_CREATE_DETACHED 2') + g.writeln('#else') + g.writeln('#define PTHREAD_CREATE_DETACHED 1') + g.writeln('#endif') + g.writeln('#endif') + g.writeln('int pthread_equal(pthread_t t1, pthread_t t2);') + g.writeln('int pthread_mutex_init(void* mutex, void* attr);') + g.writeln('int pthread_mutex_lock(void* mutex);') + g.writeln('int pthread_mutex_unlock(void* mutex);') + g.writeln('int pthread_mutex_destroy(void* mutex);') + g.writeln('int pthread_rwlockattr_init(void* attr);') + g.writeln('int pthread_rwlockattr_destroy(void* attr);') + g.writeln('int pthread_rwlock_init(void* rwlock, void* attr);') + g.writeln('int pthread_rwlock_rdlock(void* rwlock);') + g.writeln('int pthread_rwlock_wrlock(void* rwlock);') + g.writeln('int pthread_rwlock_tryrdlock(void* rwlock);') + g.writeln('int pthread_rwlock_trywrlock(void* rwlock);') + g.writeln('int pthread_rwlock_unlock(void* rwlock);') + g.writeln('int pthread_rwlock_destroy(void* rwlock);') + g.writeln('int pthread_create(void* thread, void* attr, void* start_routine, void* arg);') + g.writeln('int pthread_join(void* thread, void** retval);') + g.writeln('int pthread_detach(void* thread);') + g.writeln('int pthread_cond_init(void* cond, void* attr);') + g.writeln('int pthread_cond_destroy(void* cond);') + g.writeln('int pthread_cond_wait(void* cond, void* mutex);') + g.writeln('int pthread_cond_signal(void* cond);') + g.writeln('int pthread_cond_broadcast(void* cond);') + g.writeln('void* malloc(size_t size);') + g.writeln('void* calloc(size_t count, size_t size);') + g.writeln('void* realloc(void* ptr, size_t size);') + g.writeln('void free(void* ptr);') + g.writeln('int printf(const char* format, ...);') + g.writeln('int fprintf(FILE* stream, const char* format, ...);') + g.writeln('int fflush(FILE* stream);') + g.writeln('#ifdef _WIN32') + g.writeln('#ifndef INFINITE') + g.writeln('#define INFINITE 0xFFFFFFFF') + g.writeln('#endif') + g.writeln('HANDLE CreateThread(void* attributes, size_t stack_size, DWORD (WINAPI *start)(void*), void* parameter, DWORD flags, DWORD* thread_id);') + g.writeln('DWORD WaitForSingleObject(HANDLE handle, DWORD milliseconds);') + g.writeln('BOOL CloseHandle(HANDLE handle);') + g.writeln('DWORD GetLastError(void);') + g.writeln('typedef struct { HANDLE handle; void* context; } __v_thread;') + g.writeln('static bool __v_thread_equal(__v_thread a, __v_thread b) { return a.handle == b.handle; }') + g.writeln('typedef void* (*__v_thread_start_fn)(void*);') + g.writeln('typedef struct { __v_thread_start_fn start; void* arg; void* result; } __v_windows_thread_context;') + g.writeln('static const size_t __v_thread_stack_size = V_THREAD_STACK_SIZE;') + g.writeln('static void* __v_thread_alloc(size_t size) { void* p = malloc(size); if (!p) { fprintf(stderr, "V thread allocation failed\\n"); abort(); } return p; }') + g.writeln('static DWORD WINAPI __v_windows_thread_start(void* raw_context) { __v_windows_thread_context* context = (__v_windows_thread_context*)raw_context; context->result = context->start(context->arg); return 0; }') + g.writeln('static __v_thread __v_thread_spawn(__v_thread_start_fn start, void* arg, void (*cleanup)(void*)) {') + g.writeln('\t__v_thread result;') + g.writeln('\t__v_windows_thread_context* context = (__v_windows_thread_context*)__v_thread_alloc(sizeof(__v_windows_thread_context));') + g.writeln('\tcontext->start = start; context->arg = arg; context->result = NULL;') + g.writeln('\tresult.context = context;') + g.writeln('\tresult.handle = CreateThread(NULL, __v_thread_stack_size, __v_windows_thread_start, context, 0, NULL);') + g.writeln('\tif (!result.handle) { DWORD error = GetLastError(); free(context); if (cleanup) cleanup(arg); fprintf(stderr, "V thread creation failed: %lu\\n", (unsigned long)error); abort(); }') + g.writeln('\treturn result;') + g.writeln('}') + g.writeln('static void* __v_thread_join(__v_thread thread) {') + g.writeln('\tDWORD rc = WaitForSingleObject(thread.handle, INFINITE);') + g.writeln('\tif (rc != 0) { fprintf(stderr, "V thread join failed: %lu\\n", (unsigned long)rc); abort(); }') + g.writeln('\tvoid* result = ((__v_windows_thread_context*)thread.context)->result;') + g.writeln('\tif (!CloseHandle(thread.handle)) { DWORD error = GetLastError(); free(thread.context); fprintf(stderr, "V thread handle cleanup failed: %lu\\n", (unsigned long)error); abort(); }') + g.writeln('\tfree(thread.context);') + g.writeln('\treturn result;') + g.writeln('}') + g.writeln('#else') + g.writeln('typedef struct { pthread_t handle; } __v_thread;') + g.writeln('static bool __v_thread_equal(__v_thread a, __v_thread b) { return pthread_equal(a.handle, b.handle) != 0; }') + g.writeln('typedef void* (*__v_thread_start_fn)(void*);') + g.writeln('static const size_t __v_thread_stack_size = V_THREAD_STACK_SIZE;') + g.writeln('static void* __v_thread_alloc(size_t size) { void* p = malloc(size); if (!p) { fprintf(stderr, "V thread allocation failed\\n"); abort(); } return p; }') + g.writeln('static __v_thread __v_thread_spawn(__v_thread_start_fn start, void* arg, void (*cleanup)(void*)) {') + g.writeln('\t__v_thread result;') + g.writeln('\tpthread_attr_t attr;') + g.writeln('\tint rc = pthread_attr_init(&attr);') + g.writeln('\tif (rc != 0) { if (cleanup) cleanup(arg); fprintf(stderr, "V thread attribute initialization failed: %d\\n", rc); abort(); }') + g.writeln('\trc = pthread_attr_setstacksize(&attr, __v_thread_stack_size);') + g.writeln('\tif (rc != 0) { pthread_attr_destroy(&attr); if (cleanup) cleanup(arg); fprintf(stderr, "V thread stack size setup failed: %d\\n", rc); abort(); }') + g.writeln('\trc = pthread_create(&result.handle, &attr, (void*)start, arg);') + g.writeln('\tint attr_rc = pthread_attr_destroy(&attr);') + g.writeln('\tif (rc != 0) { if (cleanup) cleanup(arg); fprintf(stderr, "V thread creation failed: %d\\n", rc); abort(); }') + g.writeln('\tif (attr_rc != 0) { fprintf(stderr, "V thread attribute cleanup failed: %d\\n", attr_rc); abort(); }') + g.writeln('\treturn result;') + g.writeln('}') + g.writeln('static void* __v_thread_join(__v_thread thread) { void* result = NULL; int rc = pthread_join(thread.handle, &result); if (rc != 0) { fprintf(stderr, "V thread join failed: %d\\n", rc); abort(); } return result; }') + g.writeln('#endif') + // Signature shape covers both the BSD (thunk before compar) and GNU + // (compar before arg) qsort_r orders; callers pass fn pointers as void*. + g.writeln('void qsort_r(void* base, size_t nel, size_t width, void* a, void* b);') + g.writeln('#ifdef __linux__') + g.writeln('int pthread_rwlockattr_setkind_np(void* attr, int kind);') + g.writeln('#endif') + g.writeln('typedef struct SRWLOCK { void* Ptr; } SRWLOCK;') + g.writeln('typedef struct CONDITION_VARIABLE { void* Ptr; } CONDITION_VARIABLE;') + g.writeln('typedef void* atomic_uintptr_t;') + g.writeln('#if !defined(__cplusplus) && !defined(_WCHAR_T) && !defined(_WCHAR_T_DEFINED) && !defined(__WCHAR_T) && !defined(__wchar_t_defined) && !defined(_BSD_WCHAR_T_DEFINED_) && !defined(_WCHAR_T_DECLARED)') + g.writeln('#ifdef __WCHAR_TYPE__') + g.writeln('typedef __WCHAR_TYPE__ wchar_t;') + g.writeln('#elif defined(_WIN32)') + g.writeln('typedef unsigned short wchar_t;') + g.writeln('#else') + g.writeln('typedef unsigned int wchar_t;') + g.writeln('#endif') + g.writeln('#endif') + g.writeln('#ifndef FD_SET') + g.writeln('#ifndef FD_SETSIZE') + g.writeln('#define FD_SETSIZE 1024') + g.writeln('#endif') + g.headerless_fd_set_struct() + g.writeln('#endif') + g.headerless_windows_console_structs() + g.headerless_winsize_struct() + if !g.c_directives_provide_posix_socket_structs() { + g.headerless_addrinfo_struct() + g.headerless_sockaddr_structs() + } + g.headerless_epoll_structs() + g.headerless_kevent_struct() + g.headerless_dirent_struct() + g.headerless_statvfs_struct() + g.headerless_timeval_struct() + g.headerless_rusage_struct() + g.headerless_timespec_struct() + g.headerless_darwin_task_info_struct() + g.writeln('#ifdef __APPLE__') + g.writeln('typedef struct mach_timebase_info_data_t { u32 numer; u32 denom; } mach_timebase_info_data_t;') + g.writeln('u64 mach_absolute_time(void);') + g.writeln('int mach_timebase_info(mach_timebase_info_data_t*);') + g.writeln('#endif') + g.headerless_utsname_struct() + g.headerless_stat_struct() + g.writeln('int stat(const char* path, struct stat* buf);') + g.headerless_tm_struct() + g.writeln('struct utimbuf { time_t actime; time_t modtime; };') + g.writeln('time_t mktime(struct tm* timeptr);') + g.writeln('struct tm* localtime(time_t* timer);') + g.writeln('int utime(char* filename, struct utimbuf* times);') + g.writeln('int stat(const char* path, struct stat* buf);') + g.writeln('FILE* fopen(const char* path, const char* mode);') + g.writeln('FILE* freopen(const char* path, const char* mode, FILE* stream);') + g.writeln('int fclose(FILE* stream);') + g.writeln('size_t fread(void* ptr, size_t size, size_t nitems, FILE* stream);') + g.writeln('size_t fwrite(const void* ptr, size_t size, size_t nitems, FILE* stream);') + g.writeln('int fseek(FILE* stream, long offset, int whence);') + g.writeln('long ftell(FILE* stream);') + g.writeln('#if !defined(_WIN32)') + g.writeln('int fseeko(FILE* stream, long long offset, int whence);') + g.writeln('long long ftello(FILE* stream);') + g.writeln('#endif') + g.writeln('int remove(const char* path);') + g.writeln('int rename(const char* from, const char* to);') + g.writeln('#if !defined(_MODE_T) && !defined(__mode_t_defined) && !defined(_MODE_T_DECLARED)') + g.writeln('typedef u32 mode_t;') + g.writeln('#endif') + g.writeln('time_t time(time_t* tloc);') + g.writeln('i32 fileno(FILE* stream);') + g.writeln('i32 ftruncate(i32 fd, u64 length);') + g.writeln('#if !defined(_WIN32)') + g.writeln('i32 mkdir(char* path, u32 mode);') + g.writeln('i32 chmod(char* path, u32 mode);') + g.writeln('i32 symlink(char* target, char* linkpath);') + g.writeln('#endif') + g.headerless_termios_struct() + g.headerless_platform_constants() +} + +fn c_function_like_macro_decl_names() []string { + return [ + 'memset', + 'memcpy', + 'memmove', + 'memcmp', + 'strlen', + 'strcmp', + 'strncmp', + 'strncpy', + ] +} + +const c_headerless_libc_declared_fns = [ + 'malloc', + 'calloc', + 'realloc', + 'free', + 'printf', + 'fprintf', + 'fseek', + 'getenv', + 'setenv', + 'unsetenv', + 'abort', + 'memset', + 'memcpy', + 'memmove', + 'memcmp', + 'strlen', + 'strcmp', + 'strncmp', + 'strncpy', + 'floor', + 'ceil', + 'floorf', + 'ceilf', + 'sqrt', + 'pow', + 'ldexp', + 'fmod', + 'cos', + 'acos', + 'fabs', + 'open', + 'read', + 'fork', + 'dup2', + 'execlp', + 'execvp', + '_exit', + 'access', + 'realpath', + 'strrchr', + 'strstr', + 'snprintf', + 'stat', + 'fcntl', + 'pipe', + 'close', + 'signal', + 'atexit', + '_dyld_get_image_header', + '_dyld_get_image_name', + '__error', + '__errno', + '__errno_location', + '_errno', + 'ptrace', + 'sigaddset', + 'sigprocmask', + 'pthread_attr_init', + 'pthread_attr_destroy', + 'pthread_attr_setstacksize', + 'pthread_mutex_init', + 'pthread_mutex_lock', + 'pthread_mutex_unlock', + 'pthread_mutex_destroy', + 'pthread_create', + 'pthread_join', + 'pthread_detach', + 'pthread_cond_init', + 'pthread_cond_destroy', + 'pthread_cond_wait', + 'pthread_cond_signal', + 'pthread_cond_broadcast', + 'malloc', + 'calloc', + 'realloc', + 'free', + 'clock', + 'fprintf', + 'fflush', + 'qsort_r', +] + +fn (mut g FlatGen) headerless_windows_sdk_types() { + g.writeln('#ifndef WINAPI') + g.writeln('#if defined(_WIN32) && (defined(__i386__) || defined(_M_IX86))') + g.writeln('#define WINAPI __stdcall') + g.writeln('#else') + g.writeln('#define WINAPI') + g.writeln('#endif') + g.writeln('#endif') + g.writeln('#ifdef _WIN32') + g.writeln('#if !defined(_WINDEF_) && !defined(_MINWINDEF_)') + g.writeln('typedef unsigned long DWORD;') + g.writeln('typedef int BOOL;') + g.writeln('typedef void* HANDLE;') + g.writeln('#endif') + g.writeln('#if !defined(_SECURITY_ATTRIBUTES_DEFINED) && !defined(_SECURITY_ATTRIBUTES)') + g.writeln('#define _SECURITY_ATTRIBUTES_DEFINED') + g.writeln('typedef struct SECURITY_ATTRIBUTES { DWORD nLength; void* lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES;') + g.writeln('#endif') + g.writeln('#if !defined(_OVERLAPPED_) && !defined(_OVERLAPPED_DECLARED)') + g.writeln('#define _OVERLAPPED_DECLARED') + g.writeln('typedef struct OVERLAPPED { uintptr_t Internal; uintptr_t InternalHigh; union { struct { DWORD Offset; DWORD OffsetHigh; }; void* Pointer; }; HANDLE hEvent; } OVERLAPPED;') + g.writeln('#endif') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_stdarg_decls() { + g.writeln('#ifndef va_start') + g.writeln('#if defined(_MSC_VER) && !defined(__clang__)') + g.writeln('typedef char* va_list;') + g.writeln('#define __V_VA_ALIGN(type) ((sizeof(type) + sizeof(void*) - 1) & ~(sizeof(void*) - 1))') + g.writeln('#define va_start(ap, last) ((void)((ap) = (va_list)&(last) + __V_VA_ALIGN(last)))') + g.writeln('#define va_arg(ap, type) (*(type*)(((ap) += __V_VA_ALIGN(type)) - __V_VA_ALIGN(type)))') + g.writeln('#define va_end(ap) ((void)((ap) = (va_list)0))') + g.writeln('#define va_copy(dst, src) ((void)((dst) = (src)))') + g.writeln('#else') + g.writeln('typedef __builtin_va_list va_list;') + g.writeln('#define va_start(ap, last) __builtin_va_start(ap, last)') + g.writeln('#define va_arg(ap, type) __builtin_va_arg(ap, type)') + g.writeln('#define va_end(ap) __builtin_va_end(ap)') + g.writeln('#define va_copy(dst, src) __builtin_va_copy(dst, src)') + g.writeln('#endif') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_fd_set_struct() { + g.writeln('#ifdef _WIN32') + g.writeln('typedef uintptr_t SOCKET;') + g.writeln('struct fd_set { unsigned int fd_count; SOCKET fd_array[FD_SETSIZE]; };') + g.writeln('typedef struct fd_set fd_set;') + g.writeln('static inline void v_fd_zero(fd_set* set) { set->fd_count = 0; }') + g.writeln('static inline void v_fd_set(SOCKET fd, fd_set* set) { for (unsigned int i = 0; i < set->fd_count; i++) { if (set->fd_array[i] == fd) { return; } } if (set->fd_count < FD_SETSIZE) { set->fd_array[set->fd_count++] = fd; } }') + g.writeln('static inline int v_fd_isset(SOCKET fd, fd_set* set) { for (unsigned int i = 0; i < set->fd_count; i++) { if (set->fd_array[i] == fd) { return 1; } } return 0; }') + g.writeln('#define FD_ZERO(set) v_fd_zero(set)') + g.writeln('#define FD_SET(fd, set) v_fd_set((SOCKET)(fd), set)') + g.writeln('#define FD_ISSET(fd, set) v_fd_isset((SOCKET)(fd), set)') + g.writeln('#elif defined(__APPLE__)') + g.writeln('#define __V_FD_BITS 32') + g.writeln('struct fd_set { unsigned int fds_bits[FD_SETSIZE / __V_FD_BITS]; };') + g.writeln('typedef struct fd_set fd_set;') + g.writeln('#define FD_ZERO(set) memset((set), 0, sizeof(*(set)))') + g.writeln('#define FD_SET(fd, set) ((set)->fds_bits[(fd) / __V_FD_BITS] |= (1U << ((fd) % __V_FD_BITS)))') + g.writeln('#define FD_ISSET(fd, set) (((set)->fds_bits[(fd) / __V_FD_BITS] & (1U << ((fd) % __V_FD_BITS))) != 0)') + g.writeln('#else') + g.writeln('#define __V_FD_BITS (8 * (int)sizeof(unsigned long))') + g.writeln('struct fd_set { unsigned long fds_bits[FD_SETSIZE / __V_FD_BITS]; };') + g.writeln('typedef struct fd_set fd_set;') + g.writeln('#define FD_ZERO(set) memset((set), 0, sizeof(*(set)))') + g.writeln('#define FD_SET(fd, set) ((set)->fds_bits[(fd) / __V_FD_BITS] |= (1UL << ((fd) % __V_FD_BITS)))') + g.writeln('#define FD_ISSET(fd, set) (((set)->fds_bits[(fd) / __V_FD_BITS] & (1UL << ((fd) % __V_FD_BITS))) != 0)') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_windows_console_structs() { + g.writeln('#if defined(_WIN32)') + g.writeln('typedef struct COORD { i16 X; i16 Y; } COORD;') + g.writeln('typedef struct SMALL_RECT { u16 Left; u16 Top; u16 Right; u16 Bottom; } SMALL_RECT;') + g.writeln('typedef union uChar { u16 UnicodeChar; u8 AsciiChar; } uChar;') + g.writeln('typedef struct KEY_EVENT_RECORD { int bKeyDown; u16 wRepeatCount; u16 wVirtualKeyCode; u16 wVirtualScanCode; uChar uChar; u32 dwControlKeyState; } KEY_EVENT_RECORD;') + g.writeln('typedef struct MOUSE_EVENT_RECORD { COORD dwMousePosition; u32 dwButtonState; u32 dwControlKeyState; u32 dwEventFlags; } MOUSE_EVENT_RECORD;') + g.writeln('typedef struct WINDOW_BUFFER_SIZE_RECORD { COORD dwSize; } WINDOW_BUFFER_SIZE_RECORD;') + g.writeln('typedef struct MENU_EVENT_RECORD { u32 dwCommandId; } MENU_EVENT_RECORD;') + g.writeln('typedef struct FOCUS_EVENT_RECORD { int bSetFocus; } FOCUS_EVENT_RECORD;') + g.writeln('typedef union Event { KEY_EVENT_RECORD KeyEvent; MOUSE_EVENT_RECORD MouseEvent; WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; MENU_EVENT_RECORD MenuEvent; FOCUS_EVENT_RECORD FocusEvent; } Event;') + g.writeln('typedef struct INPUT_RECORD { u16 EventType; Event Event; } INPUT_RECORD;') + g.writeln('typedef struct CONSOLE_SCREEN_BUFFER_INFO { COORD dwSize; COORD dwCursorPosition; u16 wAttributes; SMALL_RECT srWindow; COORD dwMaximumWindowSize; } CONSOLE_SCREEN_BUFFER_INFO;') + g.writeln('typedef struct CHAR_INFO { uChar Char; u16 Attributes; } CHAR_INFO;') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_winsize_struct() { + g.writeln('struct winsize { unsigned short ws_row; unsigned short ws_col; unsigned short ws_xpixel; unsigned short ws_ypixel; };') +} + +fn (g &FlatGen) c_directives_provide_posix_socket_structs() bool { + for directive in g.c_directives { + for line in directive.text.split_into_lines() { + clean := trimmed_space(line) + if c_directive_name(clean) !in ['include', 'import'] { + continue + } + include_arg := c_directive_arg(clean) + if c_is_apple_framework_include(include_arg) + || include_arg in ['', '', '', ''] { + return true + } + } + } + return false +} + +fn (mut g FlatGen) headerless_addrinfo_struct() { + g.writeln('#if defined(_WIN32)') + g.writeln('struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; size_t ai_addrlen; char* ai_canonname; void* ai_addr; struct addrinfo* ai_next; };') + g.writeln('#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)') + g.writeln('struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; unsigned int ai_addrlen; char* ai_canonname; void* ai_addr; struct addrinfo* ai_next; };') + g.writeln('#else') + g.writeln('struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; unsigned int ai_addrlen; void* ai_addr; char* ai_canonname; struct addrinfo* ai_next; };') + g.writeln('#endif') + g.writeln('typedef struct addrinfo addrinfo;') +} + +fn (mut g FlatGen) headerless_sockaddr_structs() { + g.writeln('#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)') + g.writeln('struct sockaddr { u8 sa_len; u8 sa_family; char sa_data[14]; };') + g.writeln('struct sockaddr_in { u8 sin_len; u8 sin_family; u16 sin_port; u32 sin_addr; char sin_zero[8]; };') + g.writeln('struct sockaddr_in6 { u8 sin6_len; u8 sin6_family; u16 sin6_port; u32 sin6_flowinfo; u8 sin6_addr[16]; u32 sin6_scope_id; };') + g.writeln('#if !defined(_SYS_UN_H) && !defined(_SYS_UN_H_)') + g.writeln('struct sockaddr_un { u8 sun_len; u8 sun_family; char sun_path[104]; };') + g.writeln('#endif') + g.writeln('#else') + g.writeln('struct sockaddr { u16 sa_family; char sa_data[14]; };') + g.writeln('struct sockaddr_in { u16 sin_family; u16 sin_port; u32 sin_addr; char sin_zero[8]; };') + g.writeln('struct sockaddr_in6 { u16 sin6_family; u16 sin6_port; u32 sin6_flowinfo; u8 sin6_addr[16]; u32 sin6_scope_id; };') + g.writeln('#if !defined(_SYS_UN_H) && !defined(_SYS_UN_H_)') + g.writeln('struct sockaddr_un { u16 sun_family; char sun_path[108]; };') + g.writeln('#endif') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_epoll_structs() { + g.writeln('#if defined(__linux__) || defined(__ANDROID__)') + g.writeln('typedef union epoll_data { void* ptr; int fd; u32 u32; u64 u64; } epoll_data_t;') + g.writeln('struct epoll_event { u32 events; epoll_data_t data; } __attribute__((packed));') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_kevent_struct() { + g.writeln('#if defined(__NetBSD__)') + g.writeln('struct kevent { uintptr_t ident; u32 filter; u32 flags; u32 fflags; i64 data; void* udata; u64 ext[4]; };') + g.writeln('#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)') + g.writeln('struct kevent { uintptr_t ident; i16 filter; u16 flags; u32 fflags; intptr_t data; void* udata; };') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_dirent_struct() { + g.writeln('#if defined(__APPLE__)') + g.writeln('struct dirent { u64 d_ino; u64 d_seekoff; u16 d_reclen; u16 d_namlen; u8 d_type; char d_name[1024]; };') + g.writeln('#elif defined(__FreeBSD__)') + g.writeln('struct dirent { u64 d_ino; i64 d_seekoff; u16 d_reclen; u8 d_type; u8 __pad0; u16 d_namlen; u16 __pad1; char d_name[256]; };') + g.writeln('#elif defined(__OpenBSD__)') + g.writeln('struct dirent { u64 d_ino; i64 d_seekoff; u16 d_reclen; u8 d_type; u8 d_namlen; u8 __d_padding[4]; char d_name[256]; };') + g.writeln('#elif defined(__NetBSD__)') + g.writeln('struct dirent { u64 d_ino; u16 d_reclen; u16 d_namlen; u8 d_type; char d_name[512]; };') + g.writeln('#elif defined(__DragonFly__)') + g.writeln('struct dirent { u64 d_ino; u16 d_namlen; u8 d_type; u8 __unused1; u32 __unused2; char d_name[256]; };') + g.writeln('#elif defined(__linux__) && (defined(__i386__) || defined(__arm__))') + g.writeln('struct dirent { unsigned long d_ino; long d_off; unsigned short d_reclen; unsigned char d_type; char d_name[256]; };') + g.writeln('#else') + g.writeln('struct dirent { u64 d_ino; i64 d_off; unsigned short d_reclen; unsigned char d_type; char d_name[256]; };') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_statvfs_struct() { + g.writeln('#ifdef __NetBSD__') + g.writeln('struct statvfs { unsigned long f_flag; unsigned long f_bsize; unsigned long f_frsize; unsigned long f_iosize; u64 f_blocks; u64 f_bfree; u64 f_bavail; u64 f_bresvd; u64 f_files; u64 f_ffree; u64 f_favail; u64 f_fresvd; u64 f_syncreads; u64 f_syncwrites; u64 f_asyncreads; u64 f_asyncwrites; struct { i32 __fsid_val[2]; } f_fsidx; unsigned long f_fsid; unsigned long f_namemax; u32 f_owner; u64 f_spare[4]; char f_fstypename[32]; char f_mntonname[1024]; char f_mntfromname[1024]; char f_mntfromlabel[1024]; };') + g.writeln('#else') + g.writeln('struct statvfs { unsigned long f_bsize; unsigned long f_frsize; unsigned long f_blocks; unsigned long f_bfree; unsigned long f_bavail; unsigned long f_files; unsigned long f_ffree; unsigned long f_favail; unsigned long f_fsid; unsigned long f_flag; unsigned long f_namemax; int __f_spare[6]; };') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_timeval_struct() { + g.writeln('#if !defined(__timeval_defined) && !defined(_STRUCT_TIMEVAL) && !defined(_TIMEVAL_DEFINED) && !defined(_TIMEVAL_DECLARED)') + g.writeln('#ifdef _WIN32') + g.writeln('struct timeval { long tv_sec; long tv_usec; };') + g.writeln('#else') + g.writeln('struct timeval { long tv_sec; long tv_usec; };') + g.writeln('#endif') + g.writeln('#endif') + g.writeln('typedef struct timeval timeval;') +} + +fn (mut g FlatGen) headerless_rusage_struct() { + g.writeln('struct rusage { struct timeval ru_utime; struct timeval ru_stime; long ru_maxrss; long ru_ixrss; long ru_idrss; long ru_isrss; long ru_minflt; long ru_majflt; long ru_nswap; long ru_inblock; long ru_oublock; long ru_msgsnd; long ru_msgrcv; long ru_nsignals; long ru_nvcsw; long ru_nivcsw; };') +} + +fn (mut g FlatGen) headerless_timespec_struct() { + if !g.inlined_c_structs['timespec'] { + g.writeln('#if !defined(_STRUCT_TIMESPEC) && !defined(_TIMESPEC_DEFINED) && !defined(_TIMESPEC_DECLARED) && !defined(__timespec_defined)') + g.writeln('#ifdef _WIN32') + g.writeln('struct timespec { i64 tv_sec; long tv_nsec; };') + g.writeln('#else') + g.writeln('struct timespec { long tv_sec; long tv_nsec; };') + g.writeln('#endif') + g.writeln('#endif') + } + g.writeln('typedef struct timespec timespec;') + g.writeln('#if !defined(_CLOCK_T) && !defined(__clock_t_defined) && !defined(_CLOCK_T_DECLARED) && !defined(_CLOCK_T_DEFINED)') + g.writeln('typedef long clock_t;') + g.writeln('#endif') + g.writeln('#ifndef CLOCKS_PER_SEC') + g.writeln('#define CLOCKS_PER_SEC 1000000') + g.writeln('#endif') + g.writeln('clock_t clock(void);') +} + +fn (mut g FlatGen) headerless_darwin_task_info_struct() { + g.writeln('#if defined(__APPLE__) && !defined(_MACH_TASK_INFO_H_)') + g.writeln('typedef unsigned int task_t;') + g.writeln('#pragma pack(push, 4)') + g.writeln('struct task_basic_info { i32 suspend_count; u64 virtual_size; u64 resident_size; struct { i32 seconds; i32 microseconds; } user_time; struct { i32 seconds; i32 microseconds; } system_time; i32 policy; };') + g.writeln('#pragma pack(pop)') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_tm_struct() { + if !g.inlined_c_structs['tm'] { + g.writeln('#if !defined(_STRUCT_TM) && !defined(_TM_DEFINED) && !defined(_TM_DECLARED) && !defined(__tm_defined)') + g.writeln('#ifdef _WIN32') + g.writeln('struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; };') + g.writeln('#else') + g.writeln('struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; long tm_gmtoff; const char* tm_zone; };') + g.writeln('#endif') + g.writeln('#endif') + } + g.writeln('typedef struct tm tm;') +} + +fn (mut g FlatGen) headerless_termios_struct() { + g.writeln('#if defined(__APPLE__)') + g.writeln('struct termios { size_t c_iflag; size_t c_oflag; size_t c_cflag; size_t c_lflag; u8 c_cc[20]; size_t c_ispeed; size_t c_ospeed; };') + g.writeln('#elif defined(__linux__) || defined(__ANDROID__)') + g.writeln('struct termios { int c_iflag; int c_oflag; int c_cflag; int c_lflag; u8 c_line; u8 c_cc[32]; int c_ispeed; int c_ospeed; };') + g.writeln('#elif defined(__sun)') + g.writeln('struct termios { int c_iflag; int c_oflag; int c_cflag; int c_lflag; u8 c_cc[20]; };') + g.writeln('#elif defined(__QNX__) || defined(__QNXNTO__)') + g.writeln('struct termios { int c_iflag; int c_oflag; int c_cflag; int c_lflag; u8 c_cc[20]; u32 reserved[3]; int c_ispeed; int c_ospeed; };') + g.writeln('#else') + g.writeln('struct termios { int c_iflag; int c_oflag; int c_cflag; int c_lflag; u8 c_cc[20]; int c_ispeed; int c_ospeed; };') + g.writeln('#endif') + g.writeln('typedef struct termios termios;') +} + +fn (mut g FlatGen) headerless_utsname_struct() { + g.writeln('#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)') + g.writeln('struct utsname { char sysname[256]; char nodename[256]; char release[256]; char version[256]; char machine[256]; };') + g.writeln('#else') + g.writeln('struct utsname { char sysname[65]; char nodename[65]; char release[65]; char version[65]; char machine[65]; char domainname[65]; };') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_stat_struct() { + g.writeln('#ifdef __APPLE__') + g.writeln('struct stat { int st_dev; unsigned short st_mode; unsigned short st_nlink; u64 st_ino; u32 st_uid; u32 st_gid; int st_rdev; i64 st_atime; i64 st_atimensec; i64 st_mtime; i64 st_mtimensec; i64 st_ctime; i64 st_ctimensec; i64 st_birthtime; i64 st_birthtimensec; i64 st_size; i64 st_blocks; int st_blksize; u32 st_flags; u32 st_gen; int st_lspare; i64 st_qspare[2]; };') + g.writeln('#elif defined(__linux__)') + g.headerless_linux_stat_struct() + g.writeln('#elif defined(_WIN32)') + g.writeln('struct stat { u64 st_dev; u64 st_ino; u32 st_mode; u64 st_nlink; u32 st_uid; u32 st_gid; u64 st_rdev; u64 st_size; int st_atime; int st_mtime; int st_ctime; };') + g.writeln('#elif defined(__FreeBSD__)') + g.headerless_freebsd_stat_struct() + g.writeln('#elif defined(__OpenBSD__)') + g.headerless_openbsd_stat_struct() + g.writeln('#elif defined(__NetBSD__)') + g.headerless_netbsd_stat_struct() + g.writeln('#elif defined(__DragonFly__)') + g.headerless_dragonfly_stat_struct() + g.writeln('#elif defined(__sun)') + g.headerless_solaris_stat_struct() + g.writeln('#elif defined(__QNX__) || defined(__QNXNTO__)') + g.headerless_qnx_stat_struct() + g.writeln('#else') + g.writeln('#error unsupported headerless Unix struct stat layout for this platform') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_freebsd_stat_struct() { + g.writeln('#if defined(__i386__)') + g.writeln('struct stat { u64 st_dev; u64 st_ino; u64 st_nlink; u16 st_mode; i16 st_bsdflags; u32 st_uid; u32 st_gid; i32 st_padding1; u64 st_rdev; i32 st_atim_ext; i64 st_atime; long st_atimensec; i32 st_mtim_ext; i64 st_mtime; long st_mtimensec; i32 st_ctim_ext; i64 st_ctime; long st_ctimensec; i32 st_btim_ext; i64 st_birthtime; long st_birthtimensec; i64 st_size; i64 st_blocks; i32 st_blksize; u32 st_flags; u64 st_gen; u64 st_filerev; u64 st_spare[9]; };') + g.writeln('#else') + g.writeln('struct stat { u64 st_dev; u64 st_ino; u64 st_nlink; u16 st_mode; i16 st_bsdflags; u32 st_uid; u32 st_gid; i32 st_padding1; u64 st_rdev; i64 st_atime; long st_atimensec; i64 st_mtime; long st_mtimensec; i64 st_ctime; long st_ctimensec; i64 st_birthtime; long st_birthtimensec; i64 st_size; i64 st_blocks; i32 st_blksize; u32 st_flags; u64 st_gen; u64 st_filerev; u64 st_spare[9]; };') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_openbsd_stat_struct() { + g.writeln('struct stat { u32 st_mode; i32 st_dev; u64 st_ino; u32 st_nlink; u32 st_uid; u32 st_gid; i32 st_rdev; i64 st_atime; long st_atimensec; i64 st_mtime; long st_mtimensec; i64 st_ctime; long st_ctimensec; i64 __st_birthtime; long __st_birthtimensec; i64 st_size; i64 st_blocks; i32 st_blksize; u32 st_flags; u32 st_gen; };') +} + +fn (mut g FlatGen) headerless_netbsd_stat_struct() { + g.writeln('struct stat { u64 st_dev; u32 st_mode; u64 st_ino; u32 st_nlink; u32 st_uid; u32 st_gid; u64 st_rdev; i64 st_atime; long st_atimensec; i64 st_mtime; long st_mtimensec; i64 st_ctime; long st_ctimensec; i64 st_birthtime; long st_birthtimensec; i64 st_size; i64 st_blocks; i32 st_blksize; u32 st_flags; u32 st_gen; u32 st_spare[2]; };') +} + +fn (mut g FlatGen) headerless_dragonfly_stat_struct() { + g.writeln('struct stat { u64 st_ino; u32 st_nlink; u32 st_dev; u16 st_mode; u16 st_padding1; u32 st_uid; u32 st_gid; u32 st_rdev; i64 st_atime; long st_atimensec; i64 st_mtime; long st_mtimensec; i64 st_ctime; long st_ctimensec; i64 st_size; i64 st_blocks; u32 __old_st_blksize; u32 st_flags; u32 st_gen; i32 st_lspare; i64 st_blksize; i64 st_qspare2; };') +} + +fn (mut g FlatGen) headerless_solaris_stat_struct() { + g.writeln('#if defined(_LP64)') + g.writeln('struct stat { u64 st_dev; u64 st_ino; u32 st_mode; u32 st_nlink; u32 st_uid; u32 st_gid; u64 st_rdev; i64 st_size; long st_atime; long st_atimensec; long st_mtime; long st_mtimensec; long st_ctime; long st_ctimensec; long st_blksize; i64 st_blocks; char st_fstype[16]; };') + g.writeln('#else') + g.writeln('struct stat { unsigned long st_dev; long st_pad1[3]; unsigned long st_ino; u32 st_mode; u32 st_nlink; u32 st_uid; u32 st_gid; unsigned long st_rdev; long st_pad2[2]; long st_size; long st_pad3; long st_atime; long st_atimensec; long st_mtime; long st_mtimensec; long st_ctime; long st_ctimensec; long st_blksize; long st_blocks; char st_fstype[16]; long st_pad4[8]; };') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_qnx_stat_struct() { + g.writeln('#if _FILE_OFFSET_BITS - 0 == 64') + g.writeln('struct stat { u64 st_ino; i64 st_size; u64 st_dev; u64 st_rdev; u32 st_uid; u32 st_gid; long st_mtime; long st_atime; long st_ctime; u32 st_mode; u32 st_nlink; long st_blocksize; i32 st_nblocks; long st_blksize; i64 st_blocks; };') + g.writeln('#elif defined(__BIGENDIAN__)') + g.writeln('struct stat { unsigned long st_ino_hi; unsigned long st_ino; long st_size_hi; long st_size; unsigned long st_dev; unsigned long st_rdev; u32 st_uid; u32 st_gid; long st_mtime; long st_atime; long st_ctime; u32 st_mode; u32 st_nlink; long st_blocksize; i32 st_nblocks; long st_blksize; long st_blocks_hi; long st_blocks; };') + g.writeln('#else') + g.writeln('struct stat { unsigned long st_ino; unsigned long st_ino_hi; long st_size; long st_size_hi; unsigned long st_dev; unsigned long st_rdev; u32 st_uid; u32 st_gid; long st_mtime; long st_atime; long st_ctime; u32 st_mode; u32 st_nlink; long st_blocksize; i32 st_nblocks; long st_blksize; long st_blocks; long st_blocks_hi; };') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_linux_stat_struct() { + g.writeln('#if defined(__x86_64__) && !defined(__ILP32__)') + g.writeln('struct stat { u64 st_dev; u64 st_ino; u64 st_nlink; u32 st_mode; u32 st_uid; u32 st_gid; int __pad0; u64 st_rdev; i64 st_size; i64 st_blksize; i64 st_blocks; i64 st_atime; i64 st_atimensec; i64 st_mtime; i64 st_mtimensec; i64 st_ctime; i64 st_ctimensec; i64 __glibc_reserved[3]; };') + g.writeln('#elif defined(__aarch64__) || (defined(__riscv) && __riscv_xlen == 64) || defined(__loongarch_lp64)') + g.writeln('struct stat { u64 st_dev; u64 st_ino; u32 st_mode; u32 st_nlink; u32 st_uid; u32 st_gid; u64 st_rdev; unsigned long __pad1; i64 st_size; int st_blksize; int __pad2; i64 st_blocks; i64 st_atime; i64 st_atimensec; i64 st_mtime; i64 st_mtimensec; i64 st_ctime; i64 st_ctimensec; unsigned int __glibc_reserved[2]; };') + g.writeln('#elif defined(__i386__) || defined(__arm__)') + g.writeln('struct stat { u64 st_dev; unsigned short __pad1; unsigned long st_ino; u32 st_mode; unsigned long st_nlink; u32 st_uid; u32 st_gid; u64 st_rdev; unsigned short __pad2; long st_size; long st_blksize; long st_blocks; long st_atime; unsigned long st_atimensec; long st_mtime; unsigned long st_mtimensec; long st_ctime; unsigned long st_ctimensec; unsigned long __glibc_reserved4; unsigned long __glibc_reserved5; };') + g.writeln('#else') + g.writeln('#error unsupported Linux struct stat layout for this architecture') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_platform_constants() { + g.writeln('#define STDIN_FILENO 0') + g.writeln('#define STDOUT_FILENO 1') + g.writeln('#define STDERR_FILENO 2') + g.writeln('#define F_OK 0') + g.writeln('#define WEXITSTATUS(status) (((status) >> 8) & 0xff)') + g.writeln('#define WTERMSIG(status) ((status) & 0x7f)') + g.writeln('#define WIFEXITED(status) (WTERMSIG(status) == 0)') + g.writeln('#define WIFSIGNALED(status) ((((status) & 0x7f) + 1) >= 2)') + g.writeln('#define WNOHANG 1') + g.writeln('#define LOCK_SH 1') + g.writeln('#define LOCK_EX 2') + g.writeln('#define LOCK_NB 4') + g.writeln('#define LOCK_UN 8') + g.writeln('#define ENOENT 2') + g.writeln('#define RUSAGE_SELF 0') + g.writeln('#ifdef __APPLE__') + g.headerless_darwin_constants() + g.writeln('#elif defined(_WIN32)') + g.headerless_windows_constants() + g.writeln('#elif defined(__FreeBSD__)') + g.headerless_bsd_constants('0x00100000', '12', '13', '4', '47', '28', '0x00020000', '0x0800', + true) + g.writeln('#elif defined(__OpenBSD__)') + g.headerless_bsd_constants('0x10000', '8', '9', '3', '28', '24', '0x0400', '', false) + g.writeln('#elif defined(__NetBSD__)') + g.headerless_bsd_constants('0x00400000', '8', '9', '3', '28', '24', '0x0400', '0x0800', false) + g.writeln('#elif defined(__DragonFly__)') + g.headerless_bsd_constants('0x00020000', '8', '9', '4', '47', '28', '0x0400', '0x0800', false) + g.writeln('#elif defined(__sun)') + g.headerless_solaris_constants() + g.writeln('#elif defined(__QNX__) || defined(__QNXNTO__)') + g.headerless_qnx_constants() + g.writeln('#elif defined(__linux__) || defined(__ANDROID__)') + g.headerless_linux_constants() + g.writeln('#else') + g.writeln('#error unsupported headerless C platform constants') + g.writeln('#endif') + g.headerless_signal_constants() + g.headerless_ptrace_constants() + g.headerless_sysctl_constants() + g.writeln('#ifndef MSG_NOSIGNAL') + g.writeln('#define MSG_NOSIGNAL 0') + g.writeln('#endif') + g.writeln('#ifndef SO_NOSIGPIPE') + g.writeln('#define SO_NOSIGPIPE 0') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_signal_constants() { + g.writeln('#define SIGINT 2') + g.writeln('#define SIGKILL 9') + g.writeln('#define SIGTERM 15') + g.writeln('#define SIGPIPE 13') + g.writeln('#define SIG_IGN ((void*)1)') + g.writeln('#if defined(__linux__) || defined(__ANDROID__)') + g.writeln('#define SIGSTOP 19') + g.writeln('#define SIGCONT 18') + g.writeln('#define SIG_BLOCK 0') + g.writeln('#else') + g.writeln('#define SIGSTOP 17') + g.writeln('#define SIGCONT 19') + g.writeln('#define SIG_BLOCK 1') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_ptrace_constants() { + g.writeln('#if defined(__linux__) || defined(__ANDROID__)') + g.writeln('#define PTRACE_ATTACH 16') + g.writeln('#define PTRACE_DETACH 17') + g.writeln('#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)') + g.writeln('#define PT_TRACE_ME 0') + g.writeln('#define PT_ATTACH 10') + g.writeln('#define PT_DETACH 11') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_sysctl_constants() { + g.writeln('#if defined(__FreeBSD__)') + g.writeln('#define CTL_KERN 1') + g.writeln('#define CTL_VM 2') + g.writeln('#define KERN_PROC 14') + g.writeln('#define KERN_PROC_PID 1') + g.writeln('#define KERN_PROC_ARGS 7') + g.writeln('#define KERN_PROC_PATHNAME 12') + g.writeln('#define KERN_PROC_INC_THREAD 0x10') + g.writeln('#elif defined(__OpenBSD__)') + g.writeln('#define CTL_KERN 1') + g.writeln('#define CTL_VM 2') + g.writeln('#define KERN_PROC 66') + g.writeln('#define KERN_PROC_PID 1') + g.writeln('#define KERN_PROC_ARGS 55') + g.writeln('#define KERN_PROC_ARGV 1') + g.writeln('#define VM_UVMEXP 4') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_mmap_constants(map_anonymous string) { + g.writeln('#define PROT_READ 0x1') + g.writeln('#define PROT_WRITE 0x2') + g.writeln('#define PROT_EXEC 0x4') + g.writeln('#define MAP_PRIVATE 0x0002') + g.writeln('#define MAP_ANONYMOUS ${map_anonymous}') + g.writeln('#define MAP_ANON MAP_ANONYMOUS') + g.writeln('#define MAP_FAILED ((void*)-1)') +} + +fn (mut g FlatGen) headerless_linux_syscall_constants() { + g.writeln('#ifndef SYS_getrandom') + g.writeln('#if defined(__x86_64__)') + g.writeln('#define SYS_getrandom 318') + g.writeln('#elif defined(__i386__)') + g.writeln('#define SYS_getrandom 355') + g.writeln('#elif defined(__arm__)') + g.writeln('#define SYS_getrandom 384') + g.writeln('#elif defined(__aarch64__) || (defined(__riscv) && __riscv_xlen == 64) || defined(__loongarch_lp64)') + g.writeln('#define SYS_getrandom 278') + g.writeln('#else') + g.writeln('#define SYS_getrandom 278') + g.writeln('#endif') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_linux_sysconf_constants() { + g.writeln('#if defined(__ANDROID__)') + g.writeln('#define _SC_PAGESIZE 0x0027') + g.writeln('#define _SC_NPROCESSORS_ONLN 0x0061') + g.writeln('#define _SC_PHYS_PAGES 0x0062') + g.writeln('#define _SC_AVPHYS_PAGES 0x0063') + g.writeln('#else') + g.writeln('#define _SC_PAGESIZE 30') + g.writeln('#define _SC_NPROCESSORS_ONLN 84') + g.writeln('#define _SC_PHYS_PAGES 85') + g.writeln('#define _SC_AVPHYS_PAGES 86') + g.writeln('#endif') +} + +fn (mut g FlatGen) headerless_kqueue_common_constants() { + g.writeln('#define EVFILT_READ (-1)') + g.writeln('#define EVFILT_WRITE (-2)') + g.writeln('#define EV_ADD 0x0001') + g.writeln('#define EV_DELETE 0x0002') + g.writeln('#define EV_ENABLE 0x0004') + g.writeln('#define EV_DISABLE 0x0008') + g.writeln('#define EV_ONESHOT 0x0010') + g.writeln('#define EV_CLEAR 0x0020') + g.writeln('#define EV_RECEIPT 0x0040') + g.writeln('#define EV_DISPATCH 0x0080') + g.writeln('#define EV_EOF 0x8000') + g.writeln('#define EV_ERROR 0x4000') + g.writeln('#define EV_SET(kevp, a, b, c, d, e, f) do { struct kevent* __kevp = (struct kevent*)(kevp); __kevp->ident = (uintptr_t)(a); __kevp->filter = (b); __kevp->flags = (c); __kevp->fflags = (d); __kevp->data = (intptr_t)(e); __kevp->udata = (void*)(f); } while (0)') +} + +fn (mut g FlatGen) headerless_darwin_kqueue_constants() { + g.headerless_kqueue_common_constants() + g.writeln('#define EVFILT_AIO (-3)') + g.writeln('#define EVFILT_VNODE (-4)') + g.writeln('#define EVFILT_PROC (-5)') + g.writeln('#define EVFILT_SIGNAL (-6)') + g.writeln('#define EVFILT_TIMER (-7)') + g.writeln('#define EVFILT_MACHPORT (-8)') + g.writeln('#define EVFILT_FS (-9)') + g.writeln('#define EVFILT_USER (-10)') + g.writeln('#define EVFILT_VM (-12)') + g.writeln('#define EVFILT_EXCEPT (-15)') + g.writeln('#define EVFILT_SYSCOUNT 18') + g.writeln('#define EV_UDATA_SPECIFIC 0x0100') + g.writeln('#define EV_DISPATCH2 (EV_DISPATCH | EV_UDATA_SPECIFIC)') + g.writeln('#define EV_VANISHED 0x0200') + g.writeln('#define EV_SYSFLAGS 0xF000') + g.writeln('#define EV_FLAG0 0x1000') + g.writeln('#define EV_FLAG1 0x2000') +} + +fn (mut g FlatGen) headerless_darwin_constants() { + g.writeln('#define O_RDONLY 0x0000') + g.writeln('#define O_WRONLY 0x0001') + g.writeln('#define O_RDWR 0x0002') + g.writeln('#define O_NONBLOCK 0x0004') + g.writeln('#define O_APPEND 0x0008') + g.writeln('#define O_SYNC 0x0080') + g.writeln('#define O_CREAT 0x0200') + g.writeln('#define O_TRUNC 0x0400') + g.writeln('#define O_EXCL 0x0800') + g.writeln('#define O_NOCTTY 0x20000') + g.writeln('#define O_CLOEXEC 0x01000000') + g.writeln('#define F_GETFD 1') + g.writeln('#define F_SETFD 2') + g.writeln('#define F_GETFL 3') + g.writeln('#define F_SETFL 4') + g.writeln('#define F_SETLK 8') + g.writeln('#define F_SETLKW 9') + g.writeln('#define FD_CLOEXEC 1') + g.writeln('#define F_RDLCK 1') + g.writeln('#define F_UNLCK 2') + g.writeln('#define F_WRLCK 3') + g.writeln('#define EACCES 13') + g.writeln('#define EFAULT 14') + g.writeln('#define EINTR 4') + g.writeln('#define EINVAL 22') + g.writeln('#define EAGAIN 35') + g.writeln('#define EWOULDBLOCK 35') + g.writeln('#define EINPROGRESS 36') + g.writeln('#define EBUSY 16') + g.writeln('#define EDEADLK 11') + g.writeln('#define ETIMEDOUT 60') + g.writeln('#define EPROTONOSUPPORT 43') + g.writeln('#define EAFNOSUPPORT 47') + g.writeln('#define EADDRNOTAVAIL 49') + g.writeln('#define EAI_SYSTEM 11') + g.writeln('#define CLOCK_REALTIME 0') + g.writeln('#define CLOCK_MONOTONIC 6') + g.writeln('#define _SC_PAGESIZE 29') + g.writeln('#define _SC_NPROCESSORS_ONLN 58') + g.writeln('#define _SC_PHYS_PAGES 200') + g.writeln('#ifndef KERN_SUCCESS') + g.writeln('#define KERN_SUCCESS 0') + g.writeln('#endif') + g.writeln('#ifndef MACH_TASK_BASIC_INFO_COUNT') + g.writeln('#define MACH_TASK_BASIC_INFO_COUNT 12') + g.writeln('#endif') + g.writeln('#ifndef TASK_BASIC_INFO') + g.writeln('#if defined(__arm__) || defined(__arm64__)') + g.writeln('#define TASK_BASIC_INFO 18') + g.writeln('#elif defined(__LP64__)') + g.writeln('#define TASK_BASIC_INFO 5') + g.writeln('#else') + g.writeln('#define TASK_BASIC_INFO 4') + g.writeln('#endif') + g.writeln('#endif') + g.headerless_mmap_constants('0x1000') + g.headerless_darwin_kqueue_constants() + g.writeln('#define FIONREAD 0x4004667f') + g.writeln('#define TIOCGWINSZ 0x40087468') + g.writeln('#define TCSANOW 0') + g.writeln('#define TCSADRAIN 1') + g.writeln('#define TCSAFLUSH 2') + g.writeln('#define IGNBRK 1') + g.writeln('#define BRKINT 2') + g.writeln('#define PARMRK 8') + g.writeln('#define INPCK 16') + g.writeln('#define ISTRIP 32') + g.writeln('#define ICRNL 256') + g.writeln('#define IXON 512') + g.writeln('#define OPOST 1') + g.writeln('#define CS8 768') + g.writeln('#define ISIG 128') + g.writeln('#define ICANON 256') + g.writeln('#define ECHO 8') + g.writeln('#define IEXTEN 1024') + g.writeln('#define TOSTOP 4194304') + g.writeln('#define VMIN 16') + g.writeln('#define VTIME 17') + g.writeln('#define PTHREAD_PROCESS_PRIVATE 2') + g.writeln('#define PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 0') + g.writeln('#define S_IFMT 0170000') + g.writeln('#define S_IFIFO 0010000') + g.writeln('#define S_IFCHR 0020000') + g.writeln('#define S_IFDIR 0040000') + g.writeln('#define S_IFBLK 0060000') + g.writeln('#define S_IFREG 0100000') + g.writeln('#define S_IFLNK 0120000') + g.writeln('#define S_IFSOCK 0140000') + g.writeln('#define S_IRUSR 0000400') + g.writeln('#define S_IWUSR 0000200') + g.writeln('#define S_IXUSR 0000100') + g.writeln('#define S_IRGRP 0000040') + g.writeln('#define S_IWGRP 0000020') + g.writeln('#define S_IXGRP 0000010') + g.writeln('#define S_IROTH 0000004') + g.writeln('#define S_IWOTH 0000002') + g.writeln('#define S_IXOTH 0000001') + g.writeln('#define S_IRWXU 0000700') + g.writeln('#define S_IRWXG 0000070') + g.writeln('#define S_IRWXO 0000007') + g.writeln('#define S_ISUID 0004000') + g.writeln('#define S_ISGID 0002000') + g.writeln('#define S_ISVTX 0001000') + g.writeln('#ifndef EEXIST') + g.writeln('#define EEXIST 17') + g.writeln('#endif') + g.writeln('#ifndef S_ISDIR') + g.writeln('#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)') + g.writeln('#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)') + g.writeln('#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)') + g.writeln('#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)') + g.writeln('#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)') + g.writeln('#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)') + g.writeln('#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)') + g.writeln('#endif') + g.headerless_darwin_net_constants() + g.writeln('#define SIG_ERR ((void (*)(int))-1)') + g.writeln('struct flock { off_t l_start; off_t l_len; pid_t l_pid; short l_type; short l_whence; };') +} + +fn (mut g FlatGen) headerless_bsd_constants(o_cloexec string, f_setlk string, f_setlkw string, clock_monotonic string, sc_pagesize string, af_inet6 string, msg_nosignal string, so_nosigpipe string, flock_sysid bool) { + g.writeln('#define O_RDONLY 0x0000') + g.writeln('#define O_WRONLY 0x0001') + g.writeln('#define O_RDWR 0x0002') + g.writeln('#define O_NONBLOCK 0x0004') + g.writeln('#define O_APPEND 0x0008') + g.writeln('#define O_SYNC 0x0080') + g.writeln('#define O_CREAT 0x0200') + g.writeln('#define O_TRUNC 0x0400') + g.writeln('#define O_EXCL 0x0800') + g.writeln('#define O_NOCTTY 0x8000') + g.writeln('#define O_CLOEXEC ${o_cloexec}') + g.writeln('#define F_GETFD 1') + g.writeln('#define F_SETFD 2') + g.writeln('#define F_GETFL 3') + g.writeln('#define F_SETFL 4') + g.writeln('#define F_SETLK ${f_setlk}') + g.writeln('#define F_SETLKW ${f_setlkw}') + g.writeln('#define FD_CLOEXEC 1') + g.writeln('#define F_RDLCK 1') + g.writeln('#define F_UNLCK 2') + g.writeln('#define F_WRLCK 3') + g.writeln('#define EACCES 13') + g.writeln('#define EFAULT 14') + g.writeln('#define EINTR 4') + g.writeln('#define EINVAL 22') + g.writeln('#define EAGAIN 35') + g.writeln('#define EWOULDBLOCK 35') + g.writeln('#define EINPROGRESS 36') + g.writeln('#define EBUSY 16') + g.writeln('#define EDEADLK 11') + g.writeln('#define ETIMEDOUT 60') + g.writeln('#define EPROTONOSUPPORT 43') + g.writeln('#define EAFNOSUPPORT 47') + g.writeln('#define EADDRNOTAVAIL 49') + g.writeln('#define EAI_SYSTEM 11') + g.writeln('#define CLOCK_REALTIME 0') + g.writeln('#define CLOCK_MONOTONIC ${clock_monotonic}') + g.writeln('#define _SC_PAGESIZE ${sc_pagesize}') + g.headerless_mmap_constants('0x1000') + g.headerless_kqueue_common_constants() + g.writeln('#define FIONREAD 0x4004667f') + g.writeln('#define TIOCGWINSZ 0x40087468') + g.writeln('#define TCSANOW 0') + g.writeln('#define TCSADRAIN 1') + g.writeln('#define TCSAFLUSH 2') + g.writeln('#define IGNBRK 1') + g.writeln('#define BRKINT 2') + g.writeln('#define PARMRK 8') + g.writeln('#define INPCK 16') + g.writeln('#define ISTRIP 32') + g.writeln('#define ICRNL 256') + g.writeln('#define IXON 512') + g.writeln('#define OPOST 1') + g.writeln('#define CS8 768') + g.writeln('#define ISIG 128') + g.writeln('#define ICANON 256') + g.writeln('#define ECHO 8') + g.writeln('#define IEXTEN 1024') + g.writeln('#define TOSTOP 4194304') + g.writeln('#define VMIN 16') + g.writeln('#define VTIME 17') + g.writeln('#define PTHREAD_PROCESS_PRIVATE 0') + g.writeln('#define PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 0') + g.writeln('#define S_IFMT 0170000') + g.writeln('#define S_IFIFO 0010000') + g.writeln('#define S_IFCHR 0020000') + g.writeln('#define S_IFDIR 0040000') + g.writeln('#define S_IFBLK 0060000') + g.writeln('#define S_IFREG 0100000') + g.writeln('#define S_IFLNK 0120000') + g.writeln('#define S_IFSOCK 0140000') + g.writeln('#define S_IRUSR 0000400') + g.writeln('#define S_IWUSR 0000200') + g.writeln('#define S_IXUSR 0000100') + g.writeln('#define S_IRGRP 0000040') + g.writeln('#define S_IWGRP 0000020') + g.writeln('#define S_IXGRP 0000010') + g.writeln('#define S_IROTH 0000004') + g.writeln('#define S_IWOTH 0000002') + g.writeln('#define S_IXOTH 0000001') + g.writeln('#define S_IRWXU 0000700') + g.writeln('#define S_IRWXG 0000070') + g.writeln('#define S_IRWXO 0000007') + g.writeln('#define S_ISUID 0004000') + g.writeln('#define S_ISGID 0002000') + g.writeln('#define S_ISVTX 0001000') + g.writeln('#ifndef EEXIST') + g.writeln('#define EEXIST 17') + g.writeln('#endif') + g.writeln('#ifndef S_ISDIR') + g.writeln('#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)') + g.writeln('#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)') + g.writeln('#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)') + g.writeln('#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)') + g.writeln('#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)') + g.writeln('#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)') + g.writeln('#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)') + g.writeln('#endif') + g.headerless_bsd_net_constants(af_inet6, msg_nosignal, so_nosigpipe) + g.writeln('#define SIG_ERR ((void (*)(int))-1)') + if flock_sysid { + g.writeln('struct flock { off_t l_start; off_t l_len; pid_t l_pid; short l_type; short l_whence; int l_sysid; };') + } else { + g.writeln('struct flock { off_t l_start; off_t l_len; pid_t l_pid; short l_type; short l_whence; };') + } +} + +fn (mut g FlatGen) headerless_solaris_constants() { + g.writeln('#define O_RDONLY 0') + g.writeln('#define O_WRONLY 1') + g.writeln('#define O_RDWR 2') + g.writeln('#define O_APPEND 0x08') + g.writeln('#define O_SYNC 0x10') + g.writeln('#define O_NONBLOCK 0x80') + g.writeln('#define O_CREAT 0x100') + g.writeln('#define O_TRUNC 0x200') + g.writeln('#define O_EXCL 0x400') + g.writeln('#define O_NOCTTY 0x800') + g.writeln('#define O_CLOEXEC 0x800000') + g.writeln('#define F_GETFD 1') + g.writeln('#define F_SETFD 2') + g.writeln('#define F_GETFL 3') + g.writeln('#define F_SETFL 4') + g.writeln('#define F_SETLK 6') + g.writeln('#define F_SETLKW 7') + g.writeln('#define FD_CLOEXEC 1') + g.writeln('#define F_RDLCK 1') + g.writeln('#define F_WRLCK 2') + g.writeln('#define F_UNLCK 3') + g.writeln('#define EINTR 4') + g.writeln('#define EINVAL 22') + g.writeln('#define EAGAIN 11') + g.writeln('#define EWOULDBLOCK EAGAIN') + g.writeln('#define EINPROGRESS 150') + g.writeln('#define EBUSY 16') + g.writeln('#define EDEADLK 45') + g.writeln('#define ETIMEDOUT 145') + g.writeln('#define EPROTONOSUPPORT 120') + g.writeln('#define EAFNOSUPPORT 124') + g.writeln('#define EADDRNOTAVAIL 126') + g.writeln('#define EAI_SYSTEM 11') + g.writeln('#define CLOCK_REALTIME 0') + g.writeln('#define CLOCK_MONOTONIC 4') + g.writeln('#define _SC_PAGESIZE 11') + g.headerless_mmap_constants('0x100') + g.writeln('#define FIONREAD 0x4004667f') + g.writeln("#define TIOCGWINSZ (('T' << 8) | 104)") + g.writeln("#define TCSANOW (('T' << 8) | 14)") + g.writeln("#define TCSADRAIN (('T' << 8) | 15)") + g.writeln("#define TCSAFLUSH (('T' << 8) | 16)") + g.writeln('#define IGNBRK 0000001') + g.writeln('#define BRKINT 0000002') + g.writeln('#define PARMRK 0000010') + g.writeln('#define INPCK 0000020') + g.writeln('#define ISTRIP 0000040') + g.writeln('#define ICRNL 0000400') + g.writeln('#define IXON 0002000') + g.writeln('#define OPOST 0000001') + g.writeln('#define CS8 0000060') + g.writeln('#define ISIG 0000001') + g.writeln('#define ICANON 0000002') + g.writeln('#define ECHO 0000010') + g.writeln('#define IEXTEN 0100000') + g.writeln('#define TOSTOP 0000400') + g.writeln('#define VMIN 4') + g.writeln('#define VTIME 5') + g.writeln('#define PTHREAD_PROCESS_PRIVATE 0') + g.writeln('#define PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 0') + g.writeln('#define S_IFMT 0170000') + g.writeln('#define S_IFIFO 0010000') + g.writeln('#define S_IFCHR 0020000') + g.writeln('#define S_IFDIR 0040000') + g.writeln('#define S_IFBLK 0060000') + g.writeln('#define S_IFREG 0100000') + g.writeln('#define S_IFLNK 0120000') + g.writeln('#define S_IFSOCK 0140000') + g.writeln('#define S_IRUSR 0000400') + g.writeln('#define S_IWUSR 0000200') + g.writeln('#define S_IXUSR 0000100') + g.writeln('#define S_IRGRP 0000040') + g.writeln('#define S_IWGRP 0000020') + g.writeln('#define S_IXGRP 0000010') + g.writeln('#define S_IROTH 0000004') + g.writeln('#define S_IWOTH 0000002') + g.writeln('#define S_IXOTH 0000001') + g.writeln('#define S_IRWXU 0000700') + g.writeln('#define S_IRWXG 0000070') + g.writeln('#define S_IRWXO 0000007') + g.writeln('#define S_ISUID 0004000') + g.writeln('#define S_ISGID 0002000') + g.writeln('#define S_ISVTX 0001000') + g.writeln('#ifndef EEXIST') + g.writeln('#define EEXIST 17') + g.writeln('#endif') + g.writeln('#ifndef S_ISDIR') + g.writeln('#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)') + g.writeln('#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)') + g.writeln('#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)') + g.writeln('#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)') + g.writeln('#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)') + g.writeln('#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)') + g.writeln('#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)') + g.writeln('#endif') + g.headerless_solaris_net_constants() + g.writeln('#define SIG_ERR ((void (*)(int))-1)') + g.writeln('struct flock { short l_type; short l_whence; off_t l_start; off_t l_len; int l_sysid; pid_t l_pid; long l_pad[4]; };') +} + +fn (mut g FlatGen) headerless_qnx_constants() { + g.writeln('#define O_RDONLY 000000') + g.writeln('#define O_WRONLY 000001') + g.writeln('#define O_RDWR 000002') + g.writeln('#define O_APPEND 000010') + g.writeln('#define O_SYNC 000040') + g.writeln('#define O_NONBLOCK 000200') + g.writeln('#define O_CREAT 000400') + g.writeln('#define O_TRUNC 001000') + g.writeln('#define O_EXCL 002000') + g.writeln('#define O_NOCTTY 004000') + g.writeln('#define O_CLOEXEC 020000') + g.writeln('#define F_GETFD 1') + g.writeln('#define F_SETFD 2') + g.writeln('#define F_GETFL 3') + g.writeln('#define F_SETFL 4') + g.writeln('#define F_SETLK 6') + g.writeln('#define F_SETLKW 7') + g.writeln('#define FD_CLOEXEC 1') + g.writeln('#define F_RDLCK 1') + g.writeln('#define F_WRLCK 2') + g.writeln('#define F_UNLCK 3') + g.writeln('#define EINTR 4') + g.writeln('#define EINVAL 22') + g.writeln('#define EAGAIN 11') + g.writeln('#define EWOULDBLOCK EAGAIN') + g.writeln('#define EINPROGRESS 236') + g.writeln('#define EBUSY 16') + g.writeln('#define EDEADLK 45') + g.writeln('#define ETIMEDOUT 260') + g.writeln('#define EPROTONOSUPPORT 243') + g.writeln('#define EAFNOSUPPORT 247') + g.writeln('#define EADDRNOTAVAIL 249') + g.writeln('#define EAI_SYSTEM 11') + g.writeln('#define CLOCK_REALTIME 0') + g.writeln('#define CLOCK_MONOTONIC 2') + g.writeln('#define _SC_PAGESIZE 11') + g.headerless_mmap_constants('0x00080000') + g.writeln('#define FIONREAD 0x4004667f') + g.writeln('#define TIOCGWINSZ 0x40087468') + g.writeln('#define TCSANOW 0x0001') + g.writeln('#define TCSADRAIN 0x0002') + g.writeln('#define TCSAFLUSH 0x0004') + g.writeln('#define IGNBRK 0x00000001') + g.writeln('#define BRKINT 0x00000002') + g.writeln('#define PARMRK 0x00000008') + g.writeln('#define INPCK 0x00000010') + g.writeln('#define ISTRIP 0x00000020') + g.writeln('#define ICRNL 0x00000100') + g.writeln('#define IXON 0x00000400') + g.writeln('#define OPOST 0x00000001') + g.writeln('#define CS8 0x30') + g.writeln('#define ISIG 0x00000001') + g.writeln('#define ICANON 0x00000002') + g.writeln('#define ECHO 0x00000008') + g.writeln('#define IEXTEN 0x00008000') + g.writeln('#define TOSTOP 0x00000100') + g.writeln('#define VMIN 16') + g.writeln('#define VTIME 17') + g.writeln('#define PTHREAD_PROCESS_PRIVATE 0') + g.writeln('#define PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 0') + g.writeln('#define S_IFMT 0170000') + g.writeln('#define S_IFIFO 0010000') + g.writeln('#define S_IFCHR 0020000') + g.writeln('#define S_IFDIR 0040000') + g.writeln('#define S_IFBLK 0060000') + g.writeln('#define S_IFREG 0100000') + g.writeln('#define S_IFLNK 0120000') + g.writeln('#define S_IFSOCK 0140000') + g.writeln('#define S_IRUSR 0000400') + g.writeln('#define S_IWUSR 0000200') + g.writeln('#define S_IXUSR 0000100') + g.writeln('#define S_IRGRP 0000040') + g.writeln('#define S_IWGRP 0000020') + g.writeln('#define S_IXGRP 0000010') + g.writeln('#define S_IROTH 0000004') + g.writeln('#define S_IWOTH 0000002') + g.writeln('#define S_IXOTH 0000001') + g.writeln('#define S_IRWXU 0000700') + g.writeln('#define S_IRWXG 0000070') + g.writeln('#define S_IRWXO 0000007') + g.writeln('#define S_ISUID 0004000') + g.writeln('#define S_ISGID 0002000') + g.writeln('#define S_ISVTX 0001000') + g.writeln('#ifndef EEXIST') + g.writeln('#define EEXIST 17') + g.writeln('#endif') + g.writeln('#ifndef S_ISDIR') + g.writeln('#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)') + g.writeln('#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)') + g.writeln('#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)') + g.writeln('#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)') + g.writeln('#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)') + g.writeln('#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)') + g.writeln('#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)') + g.writeln('#endif') + g.headerless_qnx_net_constants() + g.writeln('#define SIG_ERR ((void (*)(int))-1)') + g.writeln('struct flock { short l_type; short l_whence; int l_zero1; off_t l_start; off_t l_len; pid_t l_pid; unsigned int l_sysid; };') +} + +fn (mut g FlatGen) headerless_windows_constants() { + g.writeln('#define O_RDONLY 0x0000') + g.writeln('#define O_WRONLY 0x0001') + g.writeln('#define O_RDWR 0x0002') + g.writeln('#define O_APPEND 0x0008') + g.writeln('#define O_CREAT 0x0100') + g.writeln('#define O_TRUNC 0x0200') + g.writeln('#define O_EXCL 0x0400') + g.writeln('#define O_BINARY 0x8000') + g.writeln('#define _O_BINARY 0x8000') + g.writeln('#define F_GETFD 1') + g.writeln('#define F_SETFD 2') + g.writeln('#define F_GETFL 3') + g.writeln('#define F_SETFL 4') + g.writeln('#define FD_CLOEXEC 1') + g.writeln('#define EINTR 4') + g.writeln('#define EINVAL 22') + g.writeln('#define EAGAIN 11') + g.writeln('#define EWOULDBLOCK 11') + g.writeln('#define EINPROGRESS 10036') + g.writeln('#define EBUSY 16') + g.writeln('#define EDEADLK 36') + g.writeln('#define ETIMEDOUT 10060') + g.writeln('#define EPROTONOSUPPORT 10043') + g.writeln('#define EAFNOSUPPORT 10047') + g.writeln('#define EADDRNOTAVAIL 10049') + g.writeln('#define EAI_SYSTEM 11') + g.writeln('#define CLOCK_REALTIME 0') + g.writeln('#define CLOCK_MONOTONIC 1') + g.writeln('#define _SC_PAGESIZE 30') + g.writeln('#define FIONREAD 0x4004667f') + g.writeln('#define FIONBIO 0x8004667eU') + g.writeln('#define TCSANOW 0') + g.writeln('#define TCSADRAIN 1') + g.writeln('#define TCSAFLUSH 2') + g.writeln('#define PTHREAD_PROCESS_PRIVATE 0') + g.writeln('#define PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 0') + g.writeln('#define S_IFMT 0170000') + g.writeln('#define S_IFIFO 0010000') + g.writeln('#define S_IFCHR 0020000') + g.writeln('#define S_IFDIR 0040000') + g.writeln('#define S_IFBLK 0060000') + g.writeln('#define S_IFREG 0100000') + g.writeln('#define S_IFLNK 0120000') + g.writeln('#define S_IFSOCK 0140000') + g.writeln('#define S_IREAD 0000400') + g.writeln('#define S_IWRITE 0000200') + g.writeln('#define S_IEXEC 0000100') + g.writeln('#define S_IRUSR 0000400') + g.writeln('#define S_IWUSR 0000200') + g.writeln('#define S_IXUSR 0000100') + g.headerless_windows_net_constants() + g.writeln('#define SIG_ERR ((void (*)(int))-1)') + g.writeln('#define GENERIC_READ 0x80000000U') + g.writeln('#define GENERIC_WRITE 0x40000000U') + g.writeln('#define FILE_SHARE_READ 0x00000001U') + g.writeln('#define FILE_SHARE_WRITE 0x00000002U') + g.writeln('#define FILE_SHARE_DELETE 0x00000004U') + g.writeln('#define OPEN_EXISTING 3') + g.writeln('#define OPEN_ALWAYS 4') + g.writeln('#define FILE_ATTRIBUTE_NORMAL 0x00000080U') + g.writeln('#define FILE_ATTRIBUTE_DIRECTORY 0x00000010U') + g.writeln('#define INVALID_FILE_ATTRIBUTES 0xffffffffU') + g.writeln('#define LOCKFILE_FAIL_IMMEDIATELY 0x00000001U') + g.writeln('#define LOCKFILE_EXCLUSIVE_LOCK 0x00000002U') + g.writeln('#define MAXDWORD 0xffffffffU') + g.writeln('#define MEM_COMMIT 0x00001000U') + g.writeln('#define MEM_RESERVE 0x00002000U') + g.writeln('#define PAGE_READWRITE 0x04U') + g.writeln('#define PAGE_EXECUTE_READ 0x20U') + g.writeln('#define TLS_OUT_OF_INDEXES 0xffffffffU') + g.writeln('#define TRUE 1') + g.writeln('#define FALSE 0') + g.writeln('#define INFINITE 0xffffffffU') + g.writeln('#define INVALID_HANDLE_VALUE ((void*)-1)') + g.writeln('#define STD_INPUT_HANDLE 0xfffffff6U') + g.writeln('#define STD_OUTPUT_HANDLE 0xfffffff5U') + g.writeln('#define STD_ERROR_HANDLE 0xfffffff4U') + g.writeln('#define ENABLE_PROCESSED_INPUT 0x0001') + g.writeln('#define ENABLE_LINE_INPUT 0x0002') + g.writeln('#define ENABLE_ECHO_INPUT 0x0004') + g.writeln('#define ENABLE_WINDOW_INPUT 0x0008') + g.writeln('#define ENABLE_MOUSE_INPUT 0x0010') + g.writeln('#define ENABLE_EXTENDED_FLAGS 0x0080') + g.writeln('#define STARTF_USESTDHANDLES 0x00000100U') + g.writeln('#define CREATE_NEW_PROCESS_GROUP 0x00000200U') + g.writeln('#define CREATE_UNICODE_ENVIRONMENT 0x00000400U') + g.writeln('#define NORMAL_PRIORITY_CLASS 0x00000020U') + g.writeln('#define CREATE_NO_WINDOW 0x08000000U') + g.writeln('#define HANDLE_FLAG_INHERIT 0x00000001U') + g.writeln('#define CTRL_BREAK_EVENT 1') + g.writeln('#define STILL_ACTIVE 259') + g.writeln('#define KEY_EVENT 0x0001') + g.writeln('#define MOUSE_EVENT 0x0002') + g.writeln('#define WINDOW_BUFFER_SIZE_EVENT 0x0004') + g.writeln('#define MENU_EVENT 0x0008') + g.writeln('#define FOCUS_EVENT 0x0010') + g.writeln('#define MOUSE_MOVED 0x0001') + g.writeln('#define DOUBLE_CLICK 0x0002') + g.writeln('#define MOUSE_WHEELED 0x0004') + g.writeln('#define VK_BACK 0x08') + g.writeln('#define VK_RETURN 0x0d') + g.writeln('#define VK_PRIOR 0x21') + g.writeln('#define VK_NEXT 0x22') + g.writeln('#define VK_END 0x23') + g.writeln('#define VK_HOME 0x24') + g.writeln('#define VK_LEFT 0x25') + g.writeln('#define VK_UP 0x26') + g.writeln('#define VK_RIGHT 0x27') + g.writeln('#define VK_DOWN 0x28') + g.writeln('#define VK_INSERT 0x2d') + g.writeln('#define VK_DELETE 0x2e') + g.writeln('#define ERROR_ACCESS_DENIED 5') + g.writeln('#define ERROR_CLASS_ALREADY_EXISTS 1410') + g.writeln('#define HWND_MESSAGE ((void*)-3)') + g.writeln('#define MB_ERR_INVALID_CHARS 0x00000008U') + g.writeln('#define GMEM_MOVEABLE 0x0002U') + g.writeln('#define CF_UNICODETEXT 13') +} + +fn (mut g FlatGen) headerless_linux_constants() { + g.writeln('#define O_RDONLY 0') + g.writeln('#define O_WRONLY 1') + g.writeln('#define O_RDWR 2') + g.writeln('#define O_CREAT 0100') + g.writeln('#define O_EXCL 0200') + g.writeln('#define O_NOCTTY 0400') + g.writeln('#define O_TRUNC 01000') + g.writeln('#define O_APPEND 02000') + g.writeln('#define O_NONBLOCK 04000') + g.writeln('#define O_SYNC 04010000') + g.writeln('#define O_CLOEXEC 02000000') + g.writeln('#define TFD_NONBLOCK O_NONBLOCK') + g.writeln('#define TFD_CLOEXEC O_CLOEXEC') + g.writeln('#define F_GETFD 1') + g.writeln('#define F_SETFD 2') + g.writeln('#define F_GETFL 3') + g.writeln('#define F_SETFL 4') + g.writeln('#define F_SETLK 6') + g.writeln('#define F_SETLKW 7') + g.writeln('#define FD_CLOEXEC 1') + g.writeln('#define F_RDLCK 0') + g.writeln('#define F_WRLCK 1') + g.writeln('#define F_UNLCK 2') + g.writeln('#define EINTR 4') + g.writeln('#define EIO 5') + g.writeln('#define EBADF 9') + g.writeln('#define EINVAL 22') + g.writeln('#define EAGAIN 11') + g.writeln('#define EWOULDBLOCK 11') + g.writeln('#define ENOMEM 12') + g.writeln('#define EFAULT 14') + g.writeln('#define EINPROGRESS 115') + g.writeln('#define ESPIPE 29') + g.writeln('#define EBUSY 16') + g.writeln('#define EDEADLK 35') + g.writeln('#define ETIMEDOUT 110') + g.writeln('#define EOVERFLOW 75') + g.writeln('#define EPROTONOSUPPORT 93') + g.writeln('#define EAFNOSUPPORT 97') + g.writeln('#define EADDRNOTAVAIL 99') + g.writeln('#define EAI_SYSTEM -11') + g.writeln('#define CLOCK_REALTIME 0') + g.writeln('#define CLOCK_MONOTONIC 1') + g.headerless_linux_sysconf_constants() + g.headerless_mmap_constants('0x20') + g.headerless_linux_syscall_constants() + g.writeln('#define FIONREAD 0x541b') + g.writeln('#define TIOCGWINSZ 0x5413') + g.writeln('#define EPOLLIN 0x001') + g.writeln('#define EPOLLPRI 0x002') + g.writeln('#define EPOLLOUT 0x004') + g.writeln('#define EPOLLERR 0x008') + g.writeln('#define EPOLLHUP 0x010') + g.writeln('#define EPOLLRDHUP 0x2000') + g.writeln('#define EPOLLEXCLUSIVE (1U << 28)') + g.writeln('#define EPOLLWAKEUP (1U << 29)') + g.writeln('#define EPOLLONESHOT (1U << 30)') + g.writeln('#define EPOLLET (1U << 31)') + g.writeln('#define EPOLL_CTL_ADD 1') + g.writeln('#define EPOLL_CTL_DEL 2') + g.writeln('#define EPOLL_CTL_MOD 3') + g.writeln('#define TCSANOW 0') + g.writeln('#define TCSADRAIN 1') + g.writeln('#define TCSAFLUSH 2') + g.writeln('#define IGNBRK 0000001') + g.writeln('#define BRKINT 0000002') + g.writeln('#define PARMRK 0000010') + g.writeln('#define INPCK 0000020') + g.writeln('#define ISTRIP 0000040') + g.writeln('#define ICRNL 0000400') + g.writeln('#define IXON 0002000') + g.writeln('#define OPOST 0000001') + g.writeln('#define CS8 0000060') + g.writeln('#define ISIG 0000001') + g.writeln('#define ICANON 0000002') + g.writeln('#define ECHO 0000010') + g.writeln('#define IEXTEN 0100000') + g.writeln('#define TOSTOP 0000400') + g.writeln('#define VMIN 6') + g.writeln('#define VTIME 5') + g.writeln('#define PTHREAD_PROCESS_PRIVATE 0') + g.writeln('#define PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 2') + g.writeln('#define S_IFMT 00170000') + g.writeln('#define S_IFIFO 0010000') + g.writeln('#define S_IFCHR 0020000') + g.writeln('#define S_IFDIR 0040000') + g.writeln('#define S_IFBLK 0060000') + g.writeln('#define S_IFREG 0100000') + g.writeln('#define S_IFLNK 0120000') + g.writeln('#define S_IFSOCK 0140000') + g.writeln('#define S_IRUSR 0000400') + g.writeln('#define S_IWUSR 0000200') + g.writeln('#define S_IXUSR 0000100') + g.writeln('#define S_IRGRP 0000040') + g.writeln('#define S_IWGRP 0000020') + g.writeln('#define S_IXGRP 0000010') + g.writeln('#define S_IROTH 0000004') + g.writeln('#define S_IWOTH 0000002') + g.writeln('#define S_IXOTH 0000001') + g.writeln('#define S_IRWXU 0000700') + g.writeln('#define S_IRWXG 0000070') + g.writeln('#define S_IRWXO 0000007') + g.writeln('#define S_ISUID 0004000') + g.writeln('#define S_ISGID 0002000') + g.writeln('#define S_ISVTX 0001000') + g.writeln('#ifndef EEXIST') + g.writeln('#define EEXIST 17') + g.writeln('#endif') + g.writeln('#ifndef S_ISDIR') + g.writeln('#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)') + g.writeln('#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)') + g.writeln('#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)') + g.writeln('#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)') + g.writeln('#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)') + g.writeln('#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)') + g.writeln('#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)') + g.writeln('#endif') + g.headerless_linux_net_constants() + g.writeln('#define SIG_ERR ((void (*)(int))-1)') + g.writeln('struct flock { short l_type; short l_whence; off_t l_start; off_t l_len; pid_t l_pid; };') +} + +fn (mut g FlatGen) headerless_darwin_net_constants() { + g.writeln('#define AF_UNSPEC 0') + g.writeln('#define AF_UNIX 1') + g.writeln('#define AF_INET 2') + g.writeln('#define AF_INET6 30') + g.writeln('#define SOCK_STREAM 1') + g.writeln('#define SOCK_DGRAM 2') + g.writeln('#define SOCK_RAW 3') + g.writeln('#define SOCK_SEQPACKET 5') + g.writeln('#define IPPROTO_IP 0') + g.writeln('#define IPPROTO_ICMP 1') + g.writeln('#define IPPROTO_TCP 6') + g.writeln('#define IPPROTO_UDP 17') + g.writeln('#define IPPROTO_IPV6 41') + g.writeln('#define IPPROTO_ICMPV6 58') + g.writeln('#define IPPROTO_RAW 255') + g.writeln('#define SOL_SOCKET 0xffff') + g.writeln('#define SO_DEBUG 0x0001') + g.writeln('#define SO_REUSEADDR 0x0004') + g.writeln('#define SO_KEEPALIVE 0x0008') + g.writeln('#define SO_DONTROUTE 0x0010') + g.writeln('#define SO_BROADCAST 0x0020') + g.writeln('#define SO_LINGER 0x0080') + g.writeln('#define SO_OOBINLINE 0x0100') + g.writeln('#define SO_SNDBUF 0x1001') + g.writeln('#define SO_RCVBUF 0x1002') + g.writeln('#define SO_SNDLOWAT 0x1003') + g.writeln('#define SO_RCVLOWAT 0x1004') + g.writeln('#define SO_SNDTIMEO 0x1005') + g.writeln('#define SO_RCVTIMEO 0x1006') + g.writeln('#define SO_ERROR 0x1007') + g.writeln('#define SO_TYPE 0x1008') + g.writeln('#define SO_NOSIGPIPE 0x1022') + g.writeln('#define TCP_NODELAY 1') + g.writeln('#define IP_HDRINCL 2') + g.writeln('#define IP_MULTICAST_IF 9') + g.writeln('#define IP_MULTICAST_TTL 10') + g.writeln('#define IP_MULTICAST_LOOP 11') + g.writeln('#define IP_ADD_MEMBERSHIP 12') + g.writeln('#define IP_DROP_MEMBERSHIP 13') + g.writeln('#define IPV6_MULTICAST_IF 9') + g.writeln('#define IPV6_MULTICAST_HOPS 10') + g.writeln('#define IPV6_MULTICAST_LOOP 11') + g.writeln('#define IPV6_ADD_MEMBERSHIP 12') + g.writeln('#define IPV6_JOIN_GROUP 12') + g.writeln('#define IPV6_DROP_MEMBERSHIP 13') + g.writeln('#define IPV6_LEAVE_GROUP 13') + g.writeln('#define IPV6_V6ONLY 27') + g.writeln('#define AI_PASSIVE 0x00000001') + g.writeln('#define MSG_DONTWAIT 0x80') +} + +fn (mut g FlatGen) headerless_bsd_net_constants(af_inet6 string, msg_nosignal string, so_nosigpipe string) { + g.writeln('#define AF_UNSPEC 0') + g.writeln('#define AF_UNIX 1') + g.writeln('#define AF_INET 2') + g.writeln('#define AF_INET6 ${af_inet6}') + g.writeln('#define SOCK_STREAM 1') + g.writeln('#define SOCK_DGRAM 2') + g.writeln('#define SOCK_RAW 3') + g.writeln('#define SOCK_SEQPACKET 5') + g.writeln('#if defined(__FreeBSD__)') + g.writeln('#define SOCK_NONBLOCK 0x20000000') + g.writeln('#endif') + g.writeln('#define IPPROTO_IP 0') + g.writeln('#define IPPROTO_ICMP 1') + g.writeln('#define IPPROTO_TCP 6') + g.writeln('#define IPPROTO_UDP 17') + g.writeln('#define IPPROTO_IPV6 41') + g.writeln('#define IPPROTO_ICMPV6 58') + g.writeln('#define IPPROTO_RAW 255') + g.writeln('#define SOL_SOCKET 0xffff') + g.writeln('#define SO_DEBUG 0x0001') + g.writeln('#define SO_REUSEADDR 0x0004') + g.writeln('#define SO_KEEPALIVE 0x0008') + g.writeln('#define SO_DONTROUTE 0x0010') + g.writeln('#define SO_BROADCAST 0x0020') + g.writeln('#define SO_LINGER 0x0080') + g.writeln('#define SO_OOBINLINE 0x0100') + g.writeln('#define SO_REUSEPORT 0x0200') + if so_nosigpipe.len > 0 { + g.writeln('#define SO_NOSIGPIPE ${so_nosigpipe}') + } + g.writeln('#define SO_SNDBUF 0x1001') + g.writeln('#define SO_RCVBUF 0x1002') + g.writeln('#define SO_SNDLOWAT 0x1003') + g.writeln('#define SO_RCVLOWAT 0x1004') + g.writeln('#define SO_SNDTIMEO 0x1005') + g.writeln('#define SO_RCVTIMEO 0x1006') + g.writeln('#define SO_ERROR 0x1007') + g.writeln('#define SO_TYPE 0x1008') + g.writeln('#define TCP_NODELAY 1') + g.writeln('#define IP_HDRINCL 2') + g.writeln('#define IP_MULTICAST_IF 9') + g.writeln('#define IP_MULTICAST_TTL 10') + g.writeln('#define IP_MULTICAST_LOOP 11') + g.writeln('#define IP_ADD_MEMBERSHIP 12') + g.writeln('#define IP_DROP_MEMBERSHIP 13') + g.writeln('#define IPV6_MULTICAST_IF 9') + g.writeln('#define IPV6_MULTICAST_HOPS 10') + g.writeln('#define IPV6_MULTICAST_LOOP 11') + g.writeln('#define IPV6_ADD_MEMBERSHIP 12') + g.writeln('#define IPV6_JOIN_GROUP 12') + g.writeln('#define IPV6_DROP_MEMBERSHIP 13') + g.writeln('#define IPV6_LEAVE_GROUP 13') + g.writeln('#define IPV6_V6ONLY 27') + g.writeln('#define AI_PASSIVE 0x00000001') + g.writeln('#define MSG_DONTWAIT 0x80') + g.writeln('#define MSG_NOSIGNAL ${msg_nosignal}') +} + +fn (mut g FlatGen) headerless_solaris_net_constants() { + g.writeln('#define AF_UNSPEC 0') + g.writeln('#define AF_UNIX 1') + g.writeln('#define AF_INET 2') + g.writeln('#define AF_INET6 26') + g.writeln('#define SOCK_STREAM 2') + g.writeln('#define SOCK_DGRAM 1') + g.writeln('#define SOCK_RAW 4') + g.writeln('#define SOCK_SEQPACKET 6') + g.writeln('#define IPPROTO_IP 0') + g.writeln('#define IPPROTO_ICMP 1') + g.writeln('#define IPPROTO_TCP 6') + g.writeln('#define IPPROTO_UDP 17') + g.writeln('#define IPPROTO_IPV6 41') + g.writeln('#define IPPROTO_ICMPV6 58') + g.writeln('#define IPPROTO_RAW 255') + g.writeln('#define SOL_SOCKET 0xffff') + g.writeln('#define SO_DEBUG 0x0001') + g.writeln('#define SO_REUSEADDR 0x0004') + g.writeln('#define SO_KEEPALIVE 0x0008') + g.writeln('#define SO_DONTROUTE 0x0010') + g.writeln('#define SO_BROADCAST 0x0020') + g.writeln('#define SO_LINGER 0x0080') + g.writeln('#define SO_OOBINLINE 0x0100') + g.writeln('#define SO_SNDBUF 0x1001') + g.writeln('#define SO_RCVBUF 0x1002') + g.writeln('#define SO_SNDLOWAT 0x1003') + g.writeln('#define SO_RCVLOWAT 0x1004') + g.writeln('#define SO_SNDTIMEO 0x1005') + g.writeln('#define SO_RCVTIMEO 0x1006') + g.writeln('#define SO_ERROR 0x1007') + g.writeln('#define SO_TYPE 0x1008') + g.writeln('#define TCP_NODELAY 1') + g.writeln('#define IP_HDRINCL 2') + g.writeln('#define IP_MULTICAST_IF 0x10') + g.writeln('#define IP_MULTICAST_TTL 0x11') + g.writeln('#define IP_MULTICAST_LOOP 0x12') + g.writeln('#define IP_ADD_MEMBERSHIP 0x13') + g.writeln('#define IP_DROP_MEMBERSHIP 0x14') + g.writeln('#define IPV6_MULTICAST_IF 0x6') + g.writeln('#define IPV6_MULTICAST_HOPS 0x7') + g.writeln('#define IPV6_MULTICAST_LOOP 0x8') + g.writeln('#define IPV6_ADD_MEMBERSHIP 0x9') + g.writeln('#define IPV6_JOIN_GROUP 0x9') + g.writeln('#define IPV6_DROP_MEMBERSHIP 0xa') + g.writeln('#define IPV6_LEAVE_GROUP 0xa') + g.writeln('#define IPV6_V6ONLY 0x27') + g.writeln('#define AI_PASSIVE 0x0008') + g.writeln('#define MSG_DONTWAIT 0x80') + g.writeln('#define MSG_NOSIGNAL 0x200') +} + +fn (mut g FlatGen) headerless_qnx_net_constants() { + g.writeln('#define AF_UNSPEC 0') + g.writeln('#define AF_UNIX 1') + g.writeln('#define AF_INET 2') + g.writeln('#define AF_INET6 24') + g.writeln('#define SOCK_STREAM 1') + g.writeln('#define SOCK_DGRAM 2') + g.writeln('#define SOCK_RAW 3') + g.writeln('#define SOCK_SEQPACKET 5') + g.writeln('#define IPPROTO_IP 0') + g.writeln('#define IPPROTO_ICMP 1') + g.writeln('#define IPPROTO_TCP 6') + g.writeln('#define IPPROTO_UDP 17') + g.writeln('#define IPPROTO_IPV6 41') + g.writeln('#define IPPROTO_ICMPV6 58') + g.writeln('#define IPPROTO_RAW 255') + g.writeln('#define SOL_SOCKET 0xffff') + g.writeln('#define SO_DEBUG 0x0001') + g.writeln('#define SO_REUSEADDR 0x0004') + g.writeln('#define SO_KEEPALIVE 0x0008') + g.writeln('#define SO_DONTROUTE 0x0010') + g.writeln('#define SO_BROADCAST 0x0020') + g.writeln('#define SO_LINGER 0x0080') + g.writeln('#define SO_OOBINLINE 0x0100') + g.writeln('#define SO_SNDBUF 0x1001') + g.writeln('#define SO_RCVBUF 0x1002') + g.writeln('#define SO_SNDLOWAT 0x1003') + g.writeln('#define SO_RCVLOWAT 0x1004') + g.writeln('#define SO_SNDTIMEO 0x1005') + g.writeln('#define SO_RCVTIMEO 0x1006') + g.writeln('#define SO_ERROR 0x1007') + g.writeln('#define SO_TYPE 0x1008') + g.writeln('#define TCP_NODELAY 1') + g.writeln('#define IP_HDRINCL 2') + g.writeln('#define IP_MULTICAST_IF 9') + g.writeln('#define IP_MULTICAST_TTL 10') + g.writeln('#define IP_MULTICAST_LOOP 11') + g.writeln('#define IP_ADD_MEMBERSHIP 12') + g.writeln('#define IP_DROP_MEMBERSHIP 13') + g.writeln('#define IPV6_MULTICAST_IF 9') + g.writeln('#define IPV6_MULTICAST_HOPS 10') + g.writeln('#define IPV6_MULTICAST_LOOP 11') + g.writeln('#define IPV6_ADD_MEMBERSHIP 12') + g.writeln('#define IPV6_JOIN_GROUP 12') + g.writeln('#define IPV6_DROP_MEMBERSHIP 13') + g.writeln('#define IPV6_LEAVE_GROUP 13') + g.writeln('#define IPV6_V6ONLY 27') + g.writeln('#define AI_PASSIVE 0x00000001') + g.writeln('#define MSG_DONTWAIT 0x80') + g.writeln('#define MSG_NOSIGNAL 0x0800') +} + +fn (mut g FlatGen) headerless_linux_net_constants() { + g.writeln('#define AF_UNSPEC 0') + g.writeln('#define AF_UNIX 1') + g.writeln('#define AF_INET 2') + g.writeln('#define AF_INET6 10') + g.writeln('#define SOCK_STREAM 1') + g.writeln('#define SOCK_DGRAM 2') + g.writeln('#define SOCK_RAW 3') + g.writeln('#define SOCK_SEQPACKET 5') + g.writeln('#define SOCK_NONBLOCK 04000') + g.writeln('#define IPPROTO_IP 0') + g.writeln('#define IPPROTO_ICMP 1') + g.writeln('#define IPPROTO_TCP 6') + g.writeln('#define IPPROTO_UDP 17') + g.writeln('#define IPPROTO_IPV6 41') + g.writeln('#define IPPROTO_ICMPV6 58') + g.writeln('#define IPPROTO_RAW 255') + g.writeln('#define SOL_SOCKET 1') + g.writeln('#define SO_DEBUG 1') + g.writeln('#define SO_REUSEADDR 2') + g.writeln('#define SO_TYPE 3') + g.writeln('#define SO_ERROR 4') + g.writeln('#define SO_DONTROUTE 5') + g.writeln('#define SO_BROADCAST 6') + g.writeln('#define SO_SNDBUF 7') + g.writeln('#define SO_RCVBUF 8') + g.writeln('#define SO_KEEPALIVE 9') + g.writeln('#define SO_OOBINLINE 10') + g.writeln('#define SO_LINGER 13') + g.writeln('#define SO_REUSEPORT 15') + g.writeln('#define SO_RCVLOWAT 18') + g.writeln('#define SO_SNDLOWAT 19') + g.writeln('#define SO_RCVTIMEO 20') + g.writeln('#define SO_SNDTIMEO 21') + g.writeln('#define TCP_NODELAY 1') + g.writeln('#define TCP_DEFER_ACCEPT 9') + g.writeln('#define TCP_QUICKACK 12') + g.writeln('#define TCP_FASTOPEN 23') + g.writeln('#define IP_HDRINCL 3') + g.writeln('#define IP_MULTICAST_IF 32') + g.writeln('#define IP_MULTICAST_TTL 33') + g.writeln('#define IP_MULTICAST_LOOP 34') + g.writeln('#define IP_ADD_MEMBERSHIP 35') + g.writeln('#define IP_DROP_MEMBERSHIP 36') + g.writeln('#define IPV6_MULTICAST_IF 17') + g.writeln('#define IPV6_MULTICAST_HOPS 18') + g.writeln('#define IPV6_MULTICAST_LOOP 19') + g.writeln('#define IPV6_ADD_MEMBERSHIP 20') + g.writeln('#define IPV6_JOIN_GROUP 20') + g.writeln('#define IPV6_DROP_MEMBERSHIP 21') + g.writeln('#define IPV6_LEAVE_GROUP 21') + g.writeln('#define IPV6_V6ONLY 26') + g.writeln('#define AI_PASSIVE 0x0001') + g.writeln('#define SOMAXCONN 4096') + g.writeln('#define MSG_DONTWAIT 0x40') + g.writeln('#define MSG_NOSIGNAL 0x4000') +} + +fn (mut g FlatGen) headerless_windows_net_constants() { + g.writeln('#define AF_UNSPEC 0') + g.writeln('#define AF_UNIX 1') + g.writeln('#define AF_INET 2') + g.writeln('#define AF_INET6 23') + g.writeln('#define SOCK_STREAM 1') + g.writeln('#define SOCK_DGRAM 2') + g.writeln('#define SOCK_RAW 3') + g.writeln('#define SOCK_SEQPACKET 5') + g.writeln('#define SOCKET_ERROR (-1)') + g.writeln('#define WSAEWOULDBLOCK 10035') + g.writeln('#define IPPROTO_IP 0') + g.writeln('#define IPPROTO_ICMP 1') + g.writeln('#define IPPROTO_TCP 6') + g.writeln('#define IPPROTO_UDP 17') + g.writeln('#define IPPROTO_IPV6 41') + g.writeln('#define IPPROTO_ICMPV6 58') + g.writeln('#define IPPROTO_RAW 255') + g.writeln('#define SOL_SOCKET 0xffff') + g.writeln('#define SO_DEBUG 0x0001') + g.writeln('#define SO_REUSEADDR 0x0004') + g.writeln('#define SO_KEEPALIVE 0x0008') + g.writeln('#define SO_DONTROUTE 0x0010') + g.writeln('#define SO_BROADCAST 0x0020') + g.writeln('#define SO_LINGER 0x0080') + g.writeln('#define SO_OOBINLINE 0x0100') + g.writeln('#define SO_SNDBUF 0x1001') + g.writeln('#define SO_RCVBUF 0x1002') + g.writeln('#define SO_SNDLOWAT 0x1003') + g.writeln('#define SO_RCVLOWAT 0x1004') + g.writeln('#define SO_SNDTIMEO 0x1005') + g.writeln('#define SO_RCVTIMEO 0x1006') + g.writeln('#define SO_ERROR 0x1007') + g.writeln('#define SO_TYPE 0x1008') + g.writeln('#define TCP_NODELAY 1') + g.writeln('#define IP_HDRINCL 2') + g.writeln('#define IP_MULTICAST_IF 9') + g.writeln('#define IP_MULTICAST_TTL 10') + g.writeln('#define IP_MULTICAST_LOOP 11') + g.writeln('#define IP_ADD_MEMBERSHIP 12') + g.writeln('#define IP_DROP_MEMBERSHIP 13') + g.writeln('#define IPV6_MULTICAST_IF 9') + g.writeln('#define IPV6_MULTICAST_HOPS 10') + g.writeln('#define IPV6_MULTICAST_LOOP 11') + g.writeln('#define IPV6_ADD_MEMBERSHIP 12') + g.writeln('#define IPV6_JOIN_GROUP 12') + g.writeln('#define IPV6_DROP_MEMBERSHIP 13') + g.writeln('#define IPV6_LEAVE_GROUP 13') + g.writeln('#define IPV6_V6ONLY 27') + g.writeln('#define AI_PASSIVE 0x00000001') + g.writeln('#define MSG_DONTWAIT 0') + g.writeln('#define MSG_NOSIGNAL 0') +} + +fn (mut g FlatGen) write_arch_macros() { + g.writeln('#ifndef __V_architecture') + g.writeln('#define __V_architecture 0') + g.writeln('#endif') + g.writeln('#if defined(__x86_64__) || defined(_M_AMD64)') + g.writeln('#define __V_amd64 1') + g.writeln('#undef __V_architecture') + g.writeln('#define __V_architecture 1') + g.writeln('#endif') + g.writeln('#if defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64)') + g.writeln('#define __V_arm64 1') + g.writeln('#undef __V_architecture') + g.writeln('#define __V_architecture 2') + g.writeln('#endif') + g.writeln('#if defined(__arm__) || defined(_M_ARM)') + g.writeln('#define __V_arm32 1') + g.writeln('#undef __V_architecture') + g.writeln('#define __V_architecture 3') + g.writeln('#endif') + g.writeln('#if defined(__riscv) && __riscv_xlen == 64') + g.writeln('#define __V_rv64 1') + g.writeln('#undef __V_architecture') + g.writeln('#define __V_architecture 4') + g.writeln('#endif') + g.writeln('#if defined(__riscv) && __riscv_xlen == 32') + g.writeln('#define __V_rv32 1') + g.writeln('#undef __V_architecture') + g.writeln('#define __V_architecture 5') + g.writeln('#endif') + g.writeln('#if defined(__i386__) || defined(_M_IX86)') + g.writeln('#define __V_x86 1') + g.writeln('#undef __V_architecture') + g.writeln('#define __V_architecture 6') + g.writeln('#endif') + g.writeln('#if (defined(__powerpc__) || defined(__powerpc) || defined(__POWERPC__) || defined(__ppc__) || defined(__ppc) || defined(__PPC__)) && !defined(__powerpc64__) && !defined(__ppc64__) && !defined(__PPC64__)') + g.writeln('#define __V_ppc 1') + g.writeln('#undef __V_architecture') + g.writeln('#define __V_architecture 12') + g.writeln('#endif') +} + +fn (mut g FlatGen) libc_compat_decls() { + if g.libc_compat_fns['gettid'] { + g.writeln('#ifdef __linux__') + g.writeln('#ifndef SYS_gettid') + g.writeln('#if defined(__x86_64__)') + g.writeln('#define SYS_gettid 186') + g.writeln('#elif defined(__aarch64__)') + g.writeln('#define SYS_gettid 178') + g.writeln('#elif defined(__i386__)') + g.writeln('#define SYS_gettid 224') + g.writeln('#elif defined(__arm__)') + g.writeln('#define SYS_gettid 224') + g.writeln('#elif defined(__riscv) && __riscv_xlen == 64') + g.writeln('#define SYS_gettid 178') + g.writeln('#elif defined(__loongarch_lp64)') + g.writeln('#define SYS_gettid 178') + g.writeln('#else') + g.writeln('#error unsupported Linux gettid syscall number for this architecture') + g.writeln('#endif') + g.writeln('#endif') + if !g.libc_compat_fns[c_libc_compat_syscall_decl_key] { + g.writeln('long syscall(long number, ...);') + } + g.writeln('static inline u32 v3_gettid(void) {') + g.writeln('\treturn (u32)syscall(SYS_gettid);') + g.writeln('}') + g.writeln('#endif') + g.writeln('') + } +} + +fn (mut g FlatGen) prealloc_atomic_compat_decls() { + g.writeln('static inline int v_prealloc_atomic_add_i32(int *ptr, int delta) { return __atomic_add_fetch(ptr, delta, 5); }') + g.writeln('static inline int v_prealloc_atomic_load_i32(int *ptr) { return __atomic_add_fetch(ptr, 0, 5); }') + g.writeln('static inline long long v_prealloc_atomic_add_i64(long long *ptr, long long delta) { return __atomic_add_fetch(ptr, delta, 5); }') + g.writeln('static inline long long v_prealloc_atomic_load_i64(long long *ptr) { return __atomic_add_fetch(ptr, 0, 5); }') + g.writeln('#ifdef __TINYC__') + g.writeln('static inline int v_prealloc_atomic_store_i32(int *ptr, int val) { return (int)__atomic_exchange_4((u32*)ptr, (u32)val, 5); }') + g.writeln('static inline int v_prealloc_atomic_cas_i32(int *ptr, int expected, int desired) { u32 e = (u32)expected; return __atomic_compare_exchange_4((u32*)ptr, &e, (u32)desired, 5, 5); }') + g.writeln('#else') + g.writeln('static inline int v_prealloc_atomic_store_i32(int *ptr, int val) { return __atomic_exchange_n(ptr, val, 5); }') + g.writeln('static inline int v_prealloc_atomic_cas_i32(int *ptr, int expected, int desired) { return __atomic_compare_exchange_n(ptr, &expected, desired, 0, 5, 5); }') + g.writeln('#endif') +} + +fn (mut g FlatGen) atomic_builtin_compat_decls() { + if g.target.os == 'windows' + && (g.ccompiler == 'tinyc' || g.ccompiler.to_lower().contains('tcc')) { + header := + os.join_path(g.compiler_vroot, 'thirdparty', 'stdatomic', 'win', 'atomic.h').replace('\\', '/') + g.writeln('#include "${header}"') + return + } + // Atomic helpers. We use compiler __atomic_* builtins (memory order 5 == __ATOMIC_SEQ_CST). + // clang/gcc inline the generic _n / RMW builtins. tcc only implements the inline + // __atomic_{add,sub,fetch}_* RMW builtins; for load/store/exchange/cas it has no generic + // _n form, so we route those to the sized __atomic_*_N libcalls (resolved from libc). + g.writeln('static inline byte atomic_fetch_add_byte(void* ptr, byte delta) { return __atomic_fetch_add((byte*)ptr, delta, 5); }') + g.writeln('static inline u16 atomic_fetch_add_u16(void* ptr, u16 delta) { return __atomic_fetch_add((u16*)ptr, delta, 5); }') + g.writeln('static inline u32 atomic_fetch_add_u32(void* ptr, u32 delta) { return __atomic_fetch_add((u32*)ptr, delta, 5); }') + g.writeln('static inline u64 atomic_fetch_add_u64(void* ptr, u64 delta) { return __atomic_fetch_add((u64*)ptr, delta, 5); }') + g.writeln('static inline void* atomic_fetch_add_ptr(void* ptr, void* delta) { return (void*)(uintptr_t)__atomic_fetch_add((uintptr_t*)ptr, (uintptr_t)delta, 5); }') + g.writeln('static inline byte atomic_fetch_sub_byte(void* ptr, byte delta) { return __atomic_fetch_sub((byte*)ptr, delta, 5); }') + g.writeln('static inline u16 atomic_fetch_sub_u16(void* ptr, u16 delta) { return __atomic_fetch_sub((u16*)ptr, delta, 5); }') + g.writeln('static inline u32 atomic_fetch_sub_u32(void* ptr, u32 delta) { return __atomic_fetch_sub((u32*)ptr, delta, 5); }') + g.writeln('static inline u64 atomic_fetch_sub_u64(void* ptr, u64 delta) { return __atomic_fetch_sub((u64*)ptr, delta, 5); }') + g.writeln('static inline void* atomic_fetch_sub_ptr(void* ptr, void* delta) { return (void*)(uintptr_t)__atomic_fetch_sub((uintptr_t*)ptr, (uintptr_t)delta, 5); }') + g.writeln('static inline byte atomic_load_byte(void* ptr) { return __atomic_fetch_add((byte*)ptr, 0, 5); }') + g.writeln('static inline u16 atomic_load_u16(void* ptr) { return __atomic_fetch_add((u16*)ptr, 0, 5); }') + g.writeln('static inline u32 atomic_load_u32(void* ptr) { return __atomic_fetch_add((u32*)ptr, 0, 5); }') + g.writeln('static inline u64 atomic_load_u64(void* ptr) { return __atomic_fetch_add((u64*)ptr, 0, 5); }') + g.writeln('#ifdef __TINYC__') + // Without a declaration TCC applies the C89 implicit `int` return type to the + // 64-bit libcall, truncating exchanged pointers and large u64 values. + g.writeln('extern u64 __atomic_exchange_8(u64* ptr, u64 val, int order);') + g.writeln('static inline void* atomic_load_ptr(void* ptr) { return (void*)(uintptr_t)__atomic_fetch_add((uintptr_t*)ptr, (uintptr_t)0, 5); }') + g.writeln('static inline byte atomic_exchange_byte(void* ptr, byte val) { return __atomic_exchange_1((byte*)ptr, val, 5); }') + g.writeln('static inline u16 atomic_exchange_u16(void* ptr, u16 val) { return __atomic_exchange_2((u16*)ptr, val, 5); }') + g.writeln('static inline u32 atomic_exchange_u32(void* ptr, u32 val) { return __atomic_exchange_4((u32*)ptr, val, 5); }') + g.writeln('static inline u64 atomic_exchange_u64(void* ptr, u64 val) { return __atomic_exchange_8((u64*)ptr, val, 5); }') + g.writeln('static inline void atomic_store_byte(void* ptr, byte val) { __atomic_store_1((byte*)ptr, val, 5); }') + g.writeln('static inline void atomic_store_u16(void* ptr, u16 val) { __atomic_store_2((u16*)ptr, val, 5); }') + g.writeln('static inline void atomic_store_u32(void* ptr, u32 val) { __atomic_store_4((u32*)ptr, val, 5); }') + g.writeln('static inline void atomic_store_u64(void* ptr, u64 val) { __atomic_store_8((u64*)ptr, val, 5); }') + g.writeln('#if UINTPTR_MAX == 0xFFFFFFFF') + g.writeln('static inline void* atomic_exchange_ptr(void* ptr, void* val) { return (void*)(size_t)__atomic_exchange_4((u32*)ptr, (u32)(size_t)val, 5); }') + g.writeln('static inline void atomic_store_ptr(void* ptr, void* val) { __atomic_store_4((u32*)ptr, (u32)(size_t)val, 5); }') + g.writeln('#else') + g.writeln('static inline void* atomic_exchange_ptr(void* ptr, void* val) { return (void*)(size_t)__atomic_exchange_8((u64*)ptr, (u64)(size_t)val, 5); }') + g.writeln('static inline void atomic_store_ptr(void* ptr, void* val) { __atomic_store_8((u64*)ptr, (u64)(size_t)val, 5); }') + g.writeln('#endif') + g.writeln('static inline bool atomic_compare_exchange_strong_byte(void* ptr, byte* expected, byte desired) { return __atomic_compare_exchange_1((byte*)ptr, expected, desired, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_strong_u16(void* ptr, u16* expected, u16 desired) { return __atomic_compare_exchange_2((u16*)ptr, expected, desired, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_strong_u32(void* ptr, u32* expected, u32 desired) { return __atomic_compare_exchange_4((u32*)ptr, expected, desired, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_strong_u64(void* ptr, u64* expected, u64 desired) { return __atomic_compare_exchange_8((u64*)ptr, expected, desired, 5, 5); }') + g.writeln('#if UINTPTR_MAX == 0xFFFFFFFF') + g.writeln('static inline bool atomic_compare_exchange_strong_ptr(void* ptr, void* expected, ptrdiff_t desired) { return __atomic_compare_exchange_4((u32*)ptr, (u32*)expected, (u32)desired, 5, 5); }') + g.writeln('#else') + g.writeln('static inline bool atomic_compare_exchange_strong_ptr(void* ptr, void* expected, ptrdiff_t desired) { return __atomic_compare_exchange_8((u64*)ptr, (u64*)expected, (u64)desired, 5, 5); }') + g.writeln('#endif') + g.writeln('static inline bool atomic_compare_exchange_weak_byte(void* ptr, byte* expected, byte desired) { return __atomic_compare_exchange_1((byte*)ptr, expected, desired, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_weak_u16(void* ptr, u16* expected, u16 desired) { return __atomic_compare_exchange_2((u16*)ptr, expected, desired, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_weak_u32(void* ptr, u32* expected, u32 desired) { return __atomic_compare_exchange_4((u32*)ptr, expected, desired, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_weak_u64(void* ptr, u64* expected, u64 desired) { return __atomic_compare_exchange_8((u64*)ptr, expected, desired, 5, 5); }') + g.writeln('#else') + g.writeln('static inline void* atomic_load_ptr(void* ptr) { return __atomic_load_n((void**)ptr, 5); }') + g.writeln('static inline byte atomic_exchange_byte(void* ptr, byte val) { return __atomic_exchange_n((byte*)ptr, val, 5); }') + g.writeln('static inline u16 atomic_exchange_u16(void* ptr, u16 val) { return __atomic_exchange_n((u16*)ptr, val, 5); }') + g.writeln('static inline u32 atomic_exchange_u32(void* ptr, u32 val) { return __atomic_exchange_n((u32*)ptr, val, 5); }') + g.writeln('static inline u64 atomic_exchange_u64(void* ptr, u64 val) { return __atomic_exchange_n((u64*)ptr, val, 5); }') + g.writeln('static inline void* atomic_exchange_ptr(void* ptr, void* val) { return __atomic_exchange_n((void**)ptr, val, 5); }') + g.writeln('static inline void atomic_store_byte(void* ptr, byte val) { __atomic_store_n((byte*)ptr, val, 5); }') + g.writeln('static inline void atomic_store_u16(void* ptr, u16 val) { __atomic_store_n((u16*)ptr, val, 5); }') + g.writeln('static inline void atomic_store_u32(void* ptr, u32 val) { __atomic_store_n((u32*)ptr, val, 5); }') + g.writeln('static inline void atomic_store_u64(void* ptr, u64 val) { __atomic_store_n((u64*)ptr, val, 5); }') + g.writeln('static inline void atomic_store_ptr(void* ptr, void* val) { __atomic_store_n((void**)ptr, val, 5); }') + g.writeln('static inline bool atomic_compare_exchange_strong_byte(void* ptr, byte* expected, byte desired) { return __atomic_compare_exchange_n((byte*)ptr, expected, desired, 0, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_strong_u16(void* ptr, u16* expected, u16 desired) { return __atomic_compare_exchange_n((u16*)ptr, expected, desired, 0, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_strong_u32(void* ptr, u32* expected, u32 desired) { return __atomic_compare_exchange_n((u32*)ptr, expected, desired, 0, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_strong_u64(void* ptr, u64* expected, u64 desired) { return __atomic_compare_exchange_n((u64*)ptr, expected, desired, 0, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_strong_ptr(void* ptr, void* expected, ptrdiff_t desired) { return __atomic_compare_exchange_n((void**)ptr, (void**)expected, (void*)desired, 0, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_weak_byte(void* ptr, byte* expected, byte desired) { return __atomic_compare_exchange_n((byte*)ptr, expected, desired, 1, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_weak_u16(void* ptr, u16* expected, u16 desired) { return __atomic_compare_exchange_n((u16*)ptr, expected, desired, 1, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_weak_u32(void* ptr, u32* expected, u32 desired) { return __atomic_compare_exchange_n((u32*)ptr, expected, desired, 1, 5, 5); }') + g.writeln('static inline bool atomic_compare_exchange_weak_u64(void* ptr, u64* expected, u64 desired) { return __atomic_compare_exchange_n((u64*)ptr, expected, desired, 1, 5, 5); }') + g.writeln('#endif') + g.writeln('static inline bool atomic_compare_exchange_weak_ptr(void* ptr, void* expected, ptrdiff_t desired) { return atomic_compare_exchange_strong_ptr(ptr, expected, desired); }') + // tcc's arm64 backend rejects inline asm ("ARM asm not implemented"), even the + // empty compiler-barrier form. cpu_relax is only a spin-loop hint; the real memory + // ordering in those loops comes from the atomic ops, so a no-op is safe under tcc. + g.writeln('#ifdef __TINYC__') + g.writeln('static inline void cpu_relax(void) { }') + g.writeln('#else') + g.writeln('static inline void cpu_relax(void) { __asm__ __volatile__("" ::: "memory"); }') + g.writeln('#endif') +} + +fn (mut g FlatGen) builtin_abi_decls() { + if !g.has_builtins { + return + } + g.libc_compat_decls() + g.writeln('#ifndef __linux__') + g.writeln('#define pthread_rwlockattr_setkind_np(attr, kind) 0') + g.writeln('#endif') + g.filelock_compat_decls() + g.writeln('#define array_new(elem_size, len, cap) __new_array((len), (cap), (elem_size))') + g.writeln('#define array_push array__push') + g.writeln('void array__push_many(array* a, void* val, int size);') + g.writeln('#define array_push_many_ptr(a, val, size) array__push_many((a), (void*)(val), (size))') + g.writeln('#define array_get array__get') + g.writeln('#define array_set(a, i, ...) array__set(&(a), (i), __VA_ARGS__)') + g.writeln('array array__clone(array* a);') + g.writeln('#define array_slice array__slice') + g.writeln('#define array_delete array__delete') + g.writeln('#define array_ensure_cap array__ensure_cap') + g.writeln('#define map__get_or_set map__get_and_set') + g.writeln('#ifndef V_COMMIT_HASH') + g.writeln('#define V_COMMIT_HASH ""') + g.writeln('#endif') + g.writeln('#ifndef memory_order_relaxed') + g.writeln('#define memory_order_relaxed 0') + g.writeln('#define memory_order_acquire 2') + g.writeln('#define memory_order_release 3') + g.writeln('#define memory_order_acq_rel 4') + g.writeln('#define memory_order_seq_cst 5') + g.writeln('#endif') + // tcc has no `__atomic_thread_fence` builtin. On the architectures where + // thirdparty/stdatomic/nix/atomic.S provides `_V_atomic_thread_fence`, route to + // that shim; on x86_64 Unix the assembly file provides `__atomic_thread_fence` + // directly, so keep the normal name there. clang/gcc keep the builtin. + g.writeln('#if defined(__TINYC__) && (defined(__i386__) || defined(__arm__) || defined(__aarch64__) || defined(__riscv) || (defined(__x86_64__) && defined(_WIN32)))') + g.writeln('extern void _V_atomic_thread_fence(int order);') + g.writeln('#define atomic_thread_fence(order) _V_atomic_thread_fence(order)') + g.writeln('#define __atomic_thread_fence(order) _V_atomic_thread_fence(order)') + g.writeln('#else') + g.writeln('#if defined(__TINYC__) && defined(__x86_64__) && !defined(_WIN32)') + g.writeln('extern void __atomic_thread_fence(int order);') + g.writeln('#endif') + g.writeln('#define atomic_thread_fence(order) __atomic_thread_fence(order)') + g.writeln('#endif') + // Weak fallbacks for the heap-tracking hooks. A program that provides real + // implementations (e.g. a `vheap_alloc`/`vheap_free` from a linked C file, as + // some projects do) overrides these without a redefinition/static-vs-non-static + // clash against that file's own non-static prototype. + if g.object_file_mode { + g.writeln('static void vheap_alloc(void* p, u64 n) { (void)p; (void)n; }') + g.writeln('static void vheap_free(void* p) { (void)p; }') + } else { + g.writeln('__attribute__((weak)) void vheap_alloc(void* p, u64 n) { (void)p; (void)n; }') + g.writeln('__attribute__((weak)) void vheap_free(void* p) { (void)p; }') + } + g.writeln('static inline int v3_sum_ptr_type_idx(const void* p) { return p == NULL ? 0 : *(const int*)p; }') + g.prealloc_atomic_compat_decls() + g.atomic_builtin_compat_decls() + g.writeln('static inline double math__abs(double a) { return a < 0 ? -a : a; }') + g.writeln('static inline double math__min(double a, double b) { return a < b ? a : b; }') + g.writeln('static const u64 _wyp[4] = {0x2d358dccaa6c78a5ull, 0x8bb84b93962eacc9ull, 0x4b33a62ed433d4a3ull, 0x4d5a2da51de1aa47ull};') + g.writeln('static inline u64 _wymix(u64 a, u64 b) { u64 ha = a >> 32, hb = b >> 32, la = (u32)a, lb = (u32)b, hi, lo; u64 rh = ha * hb, rm0 = ha * lb, rm1 = hb * la, rl = la * lb, t = rl + (rm0 << 32), c = t < rl; lo = t + (rm1 << 32); c += lo < t; hi = rh + (rm0 >> 32) + (rm1 >> 32) + c; return lo ^ hi; }') + g.writeln('static inline u64 wyhash64(u64 a, u64 b) { a ^= _wyp[0]; b ^= _wyp[1]; a *= 0xa0761d6478bd642full; b *= 0xe7037ed1a0b428dbull; return (a ^ (a >> 32)) ^ (b ^ (b >> 32)); }') + g.writeln('static inline u64 wyhash(const void* key, size_t len, u64 seed, const u64* secret) { const unsigned char* p = (const unsigned char*)key; u64 h = seed ^ secret[0] ^ (u64)len; for (size_t i = 0; i < len; i++) h = wyhash64(h ^ (u64)p[i], secret[(i + 1) & 3]); return h; }') + g.writeln('#define v_signal_with_handler_cast(sig, handler) signal((sig), ((void (*)(int))(handler)))') + g.writeln('string string__clone(string a);') + g.writeln('void string__free(string* s);') + g.writeln('string string__plus(string s, string a);') + g.writeln('string int__str(int n);') + g.writeln('string i64__str(i64 n);') + g.writeln('string u64__str(u64 nn);') + g.writeln('string f64__str(double x);') + g.writeln('string rune__str(u32 c);') + g.writeln('u8* malloc_noscan(ptrdiff_t n);') + g.writeln('void* memdup(void* src, ptrdiff_t sz);') + g.writeln('static inline Array* v3_heap_array(Array value) { return (Array*)memdup(&value, sizeof(Array)); }') + for sort_spec in ['int|int', 'i8|signed char', 'i16|short', 'i64|long long', 'u8|unsigned char', + 'u16|unsigned short', 'u32|unsigned', 'u64|unsigned long long', 'isize|ptrdiff_t', + 'usize|size_t', 'f32|float', 'f64|double', 'rune|unsigned', 'char|char'] { + sort_type := sort_spec.all_before('|') + c_type := sort_spec.all_after('|') + g.writeln('static int v3_array_sort_${sort_type}_cmp(const void* a, const void* b) { ${c_type} av = *(const ${c_type}*)a; ${c_type} bv = *(const ${c_type}*)b; return (av > bv) - (av < bv); }') + g.writeln('static inline void v3_array_sort_${sort_type}(Array* a) { if (a != NULL && a->len > 1) qsort(a->data, (size_t)a->len, sizeof(${c_type}), v3_array_sort_${sort_type}_cmp); }') + } + g.writeln('#ifdef _WIN32') + g.writeln('void* _aligned_malloc(size_t size, size_t alignment);') + g.writeln('void _aligned_free(void* memblock);') + g.writeln('#else') + g.writeln('int posix_memalign(void** memptr, size_t alignment, size_t size);') + g.writeln('#endif') + g.writeln('static inline void* v3_aligned_memdup(void* src, ptrdiff_t sz, size_t alignment) { void* p = NULL; if (alignment < sizeof(void*)) alignment = sizeof(void*);') + g.writeln('#ifdef _WIN32') + g.writeln('p = _aligned_malloc((size_t)sz, alignment);') + g.writeln('#else') + g.writeln('if (posix_memalign(&p, alignment, (size_t)sz) != 0) p = NULL;') + g.writeln('#endif') + g.writeln('if (p != NULL) memcpy(p, src, (size_t)sz); return p; }') + g.writeln('static inline void v3_aligned_free(void* p) {') + g.writeln('#ifdef _WIN32') + g.writeln('_aligned_free(p);') + g.writeln('#else') + g.writeln('free(p);') + g.writeln('#endif') + g.writeln('}') + g.writeln('static inline string v3_c_lit(const char* s, int len) { return (string){.str = (u8*)s, .len = len, .is_lit = 1}; }') + g.writeln('static inline int v3_utf8_next_cp(const u8* s, int len, int* i) { u8 c = s[*i]; if (c < 0x80) { (*i)++; return c; } int n = (c & 0xE0) == 0xC0 ? 2 : ((c & 0xF0) == 0xE0 ? 3 : ((c & 0xF8) == 0xF0 ? 4 : 1)); if (*i + n > len) { (*i)++; return c; } int cp = c & (n == 2 ? 0x1F : (n == 3 ? 0x0F : (n == 4 ? 0x07 : 0x7F))); for (int j = 1; j < n; ++j) cp = (cp << 6) | (s[*i + j] & 0x3F); *i += n; return cp; }') + g.writeln('static inline int v3_codepoint_is_combining(int cp) { return (cp >= 0x0300 && cp <= 0x036F) || (cp >= 0x1AB0 && cp <= 0x1AFF) || (cp >= 0x1DC0 && cp <= 0x1DFF) || (cp >= 0x20D0 && cp <= 0x20FF) || (cp >= 0xFE00 && cp <= 0xFE0F) || (cp >= 0xFE20 && cp <= 0xFE2F) || (cp >= 0x1F3FB && cp <= 0x1F3FF) || cp == 0x0E31 || (cp >= 0x0E34 && cp <= 0x0E3A) || (cp >= 0x0E47 && cp <= 0x0E4E); }') + g.writeln('static inline int v3_codepoint_is_wide(int cp) { return (cp >= 0x1100 && cp <= 0x115F) || (cp >= 0x2329 && cp <= 0x232A) || (cp >= 0x2E80 && cp <= 0xA4CF) || (cp >= 0xAC00 && cp <= 0xD7A3) || (cp >= 0xF900 && cp <= 0xFAFF) || (cp >= 0xFE10 && cp <= 0xFE19) || (cp >= 0xFE30 && cp <= 0xFE6F) || (cp >= 0xFF00 && cp <= 0xFF60) || (cp >= 0xFFE0 && cp <= 0xFFE6) || (cp >= 0x1F000 && cp <= 0x1FAFF); }') + g.writeln('static inline int v3_string_display_width(string s) { int width = 0; int join = 0; for (int i = 0; i < s.len;) { int cp = v3_utf8_next_cp(s.str, s.len, &i); if (cp == 0x200D) { join = 1; continue; } if (v3_codepoint_is_combining(cp)) continue; if (join) { join = 0; continue; } width += v3_codepoint_is_wide(cp) ? 2 : 1; } return width; }') + g.writeln("static inline string v3_string_pad(string s, int width, int left) { if (width < 0) { left = 1; width = -width; } int visible = v3_string_display_width(s); if (visible >= width) return s; int pad = width - visible; int out_len = s.len + pad; u8* out = malloc_noscan((ptrdiff_t)out_len + 1); if (left) { memcpy(out, s.str, (size_t)s.len); memset(out + s.len, ' ', (size_t)pad); } else { memset(out, ' ', (size_t)pad); memcpy(out + pad, s.str, (size_t)s.len); } out[out_len] = 0; return (string){.str = out, .len = out_len, .is_lit = 0}; }") + g.writeln("static inline string v3_string_upper_ascii(string s) { u8* out = malloc_noscan((ptrdiff_t)s.len + 1); for (int i = 0; i < s.len; ++i) { u8 c = s.str[i]; out[i] = c >= 'a' && c <= 'z' ? (u8)(c - ('a' - 'A')) : c; } out[s.len] = 0; return (string){.str = out, .len = s.len, .is_lit = 0}; }") + g.writeln('static inline string v3_char_string(int c) { return rune__str((u32)c); }') + g.writeln("static inline string v3_indent_multiline(string s) { int lines = 0; for (int i = 0; i < s.len; ++i) if (s.str[i] == '\\n') ++lines; if (lines == 0) return s; int out_len = s.len + lines * 4; u8* out = malloc_noscan((ptrdiff_t)out_len + 1); int p = 0; for (int i = 0; i < s.len; ++i) { u8 c = s.str[i]; out[p++] = c; if (c == '\\n') { memset(out + p, ' ', 4); p += 4; } } out[p] = 0; return (string){.str = out, .len = out_len, .is_lit = 0}; }") + if 'sync.Channel' in g.tc.structs { + g.writeln('static inline string v3_chan_str(chan ch, string elem) { if (ch == NULL) return string__plus(string__plus(v3_c_lit("chan ", 5), elem), v3_c_lit("(nil)", 5)); string out = string__plus(string__plus(v3_c_lit("chan ", 5), elem), v3_c_lit("{\\n cap: ", 11)); out = string__plus(out, int__str(ch->cap)); out = string__plus(out, ch->closed != 0 ? v3_c_lit(", closed: true\\n}", 16) : v3_c_lit(", closed: false\\n}", 17)); return out; }') + } + g.writeln('static inline double v3_f64_fixed_value(double x, int precision) { if (precision == 0) return x < 0.0 ? ceil(x - 0.5) : floor(x + 0.5); if (precision == 6) { double scale = 1000000.0; double ax = fabs(x) * scale; double base = floor(ax); double frac = ax - base; if (frac == 0.5) { double rounded = floor(ax + 0.5) / scale; return x < 0.0 ? -rounded : rounded; } } return x; }') + g.writeln('static inline string v3_f64_fixed(double x, int precision) { if (precision >= 16) { char base[128]; int b = snprintf(base, sizeof(base), "%.16g", x); if (b >= 0 && b < (int)sizeof(base)) { int dot = -1; int has_exp = 0; for (int i = 0; i < b; ++i) { if (base[i] == \'.\') dot = i; if (base[i] == \'e\' || base[i] == \'E\') has_exp = 1; } if (!has_exp) { int frac = dot >= 0 ? b - dot - 1 : 0; if (frac <= precision) { int n = b + (dot < 0 ? 1 : 0) + (precision - frac); u8* out = malloc_noscan(n + 1); memcpy(out, base, b); int pos = b; if (dot < 0) out[pos++] = \'.\'; while (frac++ < precision) out[pos++] = \'0\'; out[pos] = 0; return (string){.str = out, .len = n, .is_lit = 0}; } } } } double y = v3_f64_fixed_value(x, precision); char tmp[128]; int n = snprintf(tmp, sizeof(tmp), "%.*f", precision, y); if (n < 0) return v3_c_lit("", 0); if (n < (int)sizeof(tmp)) { u8* out = malloc_noscan(n + 1); memcpy(out, tmp, n + 1); return (string){.str = out, .len = n, .is_lit = 0}; } u8* out = malloc_noscan(n + 1); snprintf((char*)out, (size_t)n + 1, "%.*f", precision, y); return (string){.str = out, .len = n, .is_lit = 0}; }') + g.writeln('static inline string v3_f64_exp(double x, int precision, int upper) { char tmp[128]; int n = upper ? snprintf(tmp, sizeof(tmp), "%.*E", precision, x) : snprintf(tmp, sizeof(tmp), "%.*e", precision, x); if (n < 0) return v3_c_lit("", 0); if (n < (int)sizeof(tmp)) { u8* out = malloc_noscan(n + 1); memcpy(out, tmp, n + 1); return (string){.str = out, .len = n, .is_lit = 0}; } u8* out = malloc_noscan(n + 1); if (upper) snprintf((char*)out, (size_t)n + 1, "%.*E", precision, x); else snprintf((char*)out, (size_t)n + 1, "%.*e", precision, x); return (string){.str = out, .len = n, .is_lit = 0}; }') + g.writeln('static inline string v3_f64_general(double x, int precision, int upper) { char tmp[128]; int n = upper ? snprintf(tmp, sizeof(tmp), "%.*G", precision, x) : snprintf(tmp, sizeof(tmp), "%.*g", precision, x); if (n < 0) return v3_c_lit("", 0); if (n < (int)sizeof(tmp)) { u8* out = malloc_noscan(n + 1); memcpy(out, tmp, n + 1); return (string){.str = out, .len = n, .is_lit = 0}; } u8* out = malloc_noscan(n + 1); if (upper) snprintf((char*)out, (size_t)n + 1, "%.*G", precision, x); else snprintf((char*)out, (size_t)n + 1, "%.*g", precision, x); return (string){.str = out, .len = n, .is_lit = 0}; }') + g.writeln("static inline string v3_string_zpad(string s, int width) { if (s.len >= width) return s; int sign = s.len > 0 && s.str[0] == '-'; int pad = width - s.len; u8* out = malloc_noscan((ptrdiff_t)width + 1); int pos = 0; if (sign) out[pos++] = '-'; memset(out + pos, '0', (size_t)pad); pos += pad; memcpy(out + pos, s.str + sign, (size_t)(s.len - sign)); out[width] = 0; return (string){.str = out, .len = width, .is_lit = 0}; }") + g.writeln('static inline string v3_int_zpad(int n, int width) { return v3_string_zpad(int__str(n), width); }') + g.writeln('static inline string v3_i64_zpad(i64 n, int width) { return v3_string_zpad(i64__str(n), width); }') + g.writeln('static inline string v3_u64_zpad(u64 n, int width) { return v3_string_zpad(u64__str(n), width); }') + g.writeln("static inline string v3_string_rpad_zero(string s, int width) { if (s.len >= width) return s; u8* out = malloc_noscan((ptrdiff_t)width + 1); memcpy(out, s.str, (size_t)s.len); memset(out + s.len, '0', (size_t)(width - s.len)); out[width] = 0; return (string){.str = out, .len = width, .is_lit = 0}; }") + // Length-aware JSON string escaper: honors string.len so embedded NUL bytes are + // escaped rather than truncating like a C NUL-terminated string. ASCII + // codes are used to avoid escaping quirks; 92=\ 34=" 98=b 102=f 110=n 114=r 116=t 117=u 48=0. + g.writeln('static inline string v3_json_encode_string(string s) { const char* hex = "0123456789abcdef"; u8* out = malloc_noscan((ptrdiff_t)s.len * 6 + 8); int p = 0; out[p++] = 34; for (int i = 0; i < s.len; i++) { u8 c = s.str[i]; if (c == 34) { out[p++]=92; out[p++]=34; } else if (c == 92) { out[p++]=92; out[p++]=92; } else if (c == 8) { out[p++]=92; out[p++]=98; } else if (c == 12) { out[p++]=92; out[p++]=102; } else if (c == 10) { out[p++]=92; out[p++]=110; } else if (c == 13) { out[p++]=92; out[p++]=114; } else if (c == 9) { out[p++]=92; out[p++]=116; } else if (c < 32) { out[p++]=92; out[p++]=117; out[p++]=48; out[p++]=48; out[p++]=hex[(c>>4)&15]; out[p++]=hex[c&15]; } else { out[p++]=c; } } out[p++] = 34; out[p] = 0; return (string){.str = out, .len = p, .is_lit = 0}; }') + if g.has_cjson() { + g.json_number_token_helpers() + } + g.writeln('static inline i64 v3_map_signed(void* p, int bytes) { if (bytes == 1) return *(signed char*)p; if (bytes == 2) return *(short*)p; if (bytes == 8) return *(long long*)p; return *(int*)p; }') + g.writeln('static inline u64 v3_map_unsigned(void* p, int bytes) { if (bytes == 1) return *(unsigned char*)p; if (bytes == 2) return *(unsigned short*)p; if (bytes == 8) return *(unsigned long long*)p; return *(unsigned int*)p; }') + g.writeln('static inline string v3_f32_array_str(float* vals, int n) { string out = v3_c_lit("[", 1); for (int i = 0; i < n; ++i) { if (i > 0) out = string__plus(out, v3_c_lit(", ", 2)); out = string__plus(out, f64__str((double)vals[i])); } return string__plus(out, v3_c_lit("]", 1)); }') + g.writeln('static inline string v3_f64_array_str(double* vals, int n) { string out = v3_c_lit("[", 1); for (int i = 0; i < n; ++i) { if (i > 0) out = string__plus(out, v3_c_lit(", ", 2)); out = string__plus(out, f64__str(vals[i])); } return string__plus(out, v3_c_lit("]", 1)); }') + g.writeln('static inline string v3_map_str_piece(void* p, int kind, int bytes, int fixed_len) {') + g.writeln('\tif (kind == 1) { return string__plus(string__plus(v3_c_lit("\'", 1), *(string*)p), v3_c_lit("\'", 1)); }') + g.writeln('\tif (kind == 2) { return v3_i64_zpad(v3_map_signed(p, bytes), 0); }') + g.writeln('\tif (kind == 3) { return u64__str(v3_map_unsigned(p, bytes)); }') + g.writeln('\tif (kind == 4) { u32 r = bytes == 1 ? (u32)(*(u8*)p) : *(u32*)p; return string__plus(string__plus(v3_c_lit("`", 1), rune__str(r)), v3_c_lit("`", 1)); }') + g.writeln('\tif (kind == 5) { if (bytes == (int)sizeof(float)) return f64__str((double)*(float*)p); return f64__str(*(double*)p); }') + g.writeln('\tif (kind == 6) { if (fixed_len == 0 && bytes == (int)sizeof(Array)) { Array a = *(Array*)p; if (a.element_size == (int)sizeof(float)) return v3_f32_array_str((float*)a.data, a.len); if (a.element_size == (int)sizeof(double)) return v3_f64_array_str((double*)a.data, a.len); } if (fixed_len > 0 && bytes == fixed_len * (int)sizeof(float)) return v3_f32_array_str((float*)p, fixed_len); int n = fixed_len > 0 ? fixed_len : bytes / (int)sizeof(double); return v3_f64_array_str((double*)p, n); }') + g.writeln('\tif (kind == 8) { return f64__str((double)*(float*)p); }') + g.writeln('\tif (kind == 9) { int n = fixed_len > 0 ? fixed_len : bytes / (int)sizeof(float); return v3_f32_array_str((float*)p, n); }') + g.writeln('\tif (kind == 7) { return *(bool*)p ? v3_c_lit("true", 4) : v3_c_lit("false", 5); }') + g.writeln('\treturn v3_c_lit("", 11);') + g.writeln('}') + g.writeln('static inline string v3_map_str(map m, int key_kind, int val_kind, int val_fixed_len) {') + g.writeln('\tstring out = v3_c_lit("{", 1); bool first = true;') + g.writeln('\tfor (int i = 0; i < m.key_values.len; ++i) {') + g.writeln('\t\tif (m.key_values.deletes != 0 && m.key_values.all_deleted != 0 && m.key_values.all_deleted[i] != 0) continue;') + g.writeln('\t\tif (!first) out = string__plus(out, v3_c_lit(", ", 2));') + g.writeln('\t\tvoid* key = (void*)(m.key_values.keys + i * m.key_values.key_bytes);') + g.writeln('\t\tvoid* val = (void*)(m.key_values.values + i * m.key_values.value_bytes);') + g.writeln('\t\tout = string__plus(out, v3_map_str_piece(key, key_kind, m.key_values.key_bytes, 0));') + g.writeln('\t\tout = string__plus(out, v3_c_lit(": ", 2));') + g.writeln('\t\tout = string__plus(out, v3_map_str_piece(val, val_kind, m.value_bytes, val_fixed_len));') + g.writeln('\t\tfirst = false;') + g.writeln('\t}') + g.writeln('\treturn string__plus(out, v3_c_lit("}", 1));') + g.writeln('}') + g.writeln('static inline int array_index_int(Array a, int val) { for (int i = 0; i < a.len; i++) if (((int*)a.data)[i] == val) return i; return -1; }') + g.writeln('static inline int array_last_index_int(Array a, int val) { for (int i = a.len - 1; i >= 0; i--) if (((int*)a.data)[i] == val) return i; return -1; }') + g.writeln('static inline bool array_contains_int(Array a, int val) { return array_index_int(a, val) >= 0; }') + g.writeln('static inline int array_index_u8(Array a, u8 val) { for (int i = 0; i < a.len; i++) if (((u8*)a.data)[i] == val) return i; return -1; }') + g.writeln('static inline int array_last_index_u8(Array a, u8 val) { for (int i = a.len - 1; i >= 0; i--) if (((u8*)a.data)[i] == val) return i; return -1; }') + g.writeln('static inline bool array_contains_u8(Array a, u8 val) { return array_index_u8(a, val) >= 0; }') + g.writeln('static inline int array_index_string(Array a, string val) { string* data = (string*)a.data; for (int i = 0; i < a.len; i++) if (data[i].len == val.len && memcmp(data[i].str, val.str, val.len) == 0) return i; return -1; }') + g.writeln('static inline int array_last_index_string(Array a, string val) { string* data = (string*)a.data; for (int i = a.len - 1; i >= 0; i--) if (data[i].len == val.len && memcmp(data[i].str, val.str, val.len) == 0) return i; return -1; }') + g.writeln('static inline bool array_contains_string(Array a, string val) { return array_index_string(a, val) >= 0; }') + g.writeln('static inline int array_last_index_raw(Array a, const void* val) { for (int i = a.len - 1; i >= 0; i--) if (memcmp((u8*)a.data + (size_t)i * (size_t)a.element_size, val, (size_t)a.element_size) == 0) return i; return -1; }') + g.writeln('static inline bool array_eq_raw(Array a, Array b, int elem_size) { return a.len == b.len && (a.len == 0 || memcmp(a.data, b.data, (size_t)a.len * elem_size) == 0); }') + g.writeln('static inline bool array_eq_string(Array a, Array b) { if (a.len != b.len) return false; string* ad = (string*)a.data; string* bd = (string*)b.data; for (int i = 0; i < a.len; i++) if (ad[i].len != bd[i].len || memcmp(ad[i].str, bd[i].str, ad[i].len) != 0) return false; return true; }') + g.writeln('static inline bool array_eq_array(Array a, Array b, int depth) { if (a.len != b.len || a.element_size != b.element_size) return false; if (depth <= 1 || a.element_size != sizeof(Array)) { if (a.element_size == sizeof(string)) return array_eq_string(a, b); return array_eq_raw(a, b, a.element_size); } Array* ad = (Array*)a.data; Array* bd = (Array*)b.data; for (int i = 0; i < a.len; i++) { if (!array_eq_array(ad[i], bd[i], depth - 1)) return false; } return true; }') + g.writeln('void* map__get(map* m, void* key, void* zero);') + g.writeln('bool map__exists(map* m, void* key);') + g.writeln('static inline bool v3_map_map_eq(map a, map b);') + g.writeln('static inline bool v3_map_value_eq(void* a, void* b, int value_bytes) { if (value_bytes == sizeof(string)) { string sa = *(string*)a; string sb = *(string*)b; return sa.len == sb.len && (sa.len == 0 || memcmp(sa.str, sb.str, sa.len) == 0); } if (value_bytes == sizeof(map)) { return v3_map_map_eq(*(map*)a, *(map*)b); } if (value_bytes == sizeof(string) + sizeof(map)) { string sa = *(string*)a; string sb = *(string*)b; if (!(sa.len == sb.len && (sa.len == 0 || memcmp(sa.str, sb.str, sa.len) == 0))) return false; map ma = *(map*)((u8*)a + sizeof(string)); map mb = *(map*)((u8*)b + sizeof(string)); return v3_map_map_eq(ma, mb); } if (value_bytes == sizeof(Array)) { Array aa = *(Array*)a; Array bb = *(Array*)b; if (aa.element_size != bb.element_size) return false; if (aa.element_size == sizeof(string)) return array_eq_string(aa, bb); if (aa.element_size == sizeof(Array)) return array_eq_array(aa, bb, 8); return array_eq_raw(aa, bb, aa.element_size); } return memcmp(a, b, value_bytes) == 0; }') + g.writeln('static inline bool v3_map_map_eq(map a, map b) { if (a.len != b.len) return false; for (int i = 0; i < a.key_values.len; ++i) { if (a.key_values.deletes != 0 && a.key_values.all_deleted != 0 && a.key_values.all_deleted[i] != 0) continue; void* ak = (void*)(a.key_values.keys + i * a.key_values.key_bytes); if (!map__exists(&b, ak)) return false; void* av = (void*)(a.key_values.values + i * a.key_values.value_bytes); void* bv = map__get(&b, ak, av); if (!v3_map_value_eq(av, bv, a.value_bytes)) return false; } return true; }') + g.writeln('static inline bool fixed_array_contains_string(const string* a, int len, string val) { for (int i = 0; i < len; i++) if (a[i].len == val.len && memcmp(a[i].str, val.str, val.len) == 0) return true; return false; }') + g.writeln('static inline bool fixed_array_contains_u8(const u8* a, int len, u8 val) { for (int i = 0; i < len; i++) if (a[i] == val) return true; return false; }') + g.writeln('static inline bool fixed_array_contains_int(const int* a, int len, int val) { for (int i = 0; i < len; i++) if (a[i] == val) return true; return false; }') + g.writeln('static inline string Array_str(Array a) { if (a.element_size == 1) { u8* buf = (u8*)malloc((size_t)a.len + 1); if (a.len > 0) memcpy(buf, a.data, (size_t)a.len); buf[a.len] = 0; return (string){buf, a.len, 0}; } return (string){(u8*)"[]", 2, 1}; }') + g.writeln('#ifndef max_int') + g.writeln('#define max_int max_i32') + g.writeln('#endif') + g.writeln('#ifndef min_int') + g.writeln('#define min_int min_i32') + g.writeln('#endif') + g.writeln('') +} + +fn (g &FlatGen) has_cjson() bool { + for flag in g.c_flags { + if flag.contains('cJSON') { + return true + } + } + return false +} + +fn (mut g FlatGen) json_number_token_helpers() { + g.writeln('static inline const u8* v3_json_skip_space(const u8* p, const u8* end) { while (p < end && (*p == 32 || *p == 9 || *p == 10 || *p == 13)) p++; return p; }') + g.writeln('static inline const u8* v3_json_skip_string(const u8* p, const u8* end) { if (p >= end || *p != 34) return p; p++; while (p < end) { if (*p == 92) { p++; if (p < end) p++; continue; } if (*p == 34) return p + 1; p++; } return p; }') + g.writeln('static const u8* v3_json_preserve_number_tokens_inner(const u8* p, const u8* end, cJSON* item);') + g.writeln('static const u8* v3_json_preserve_number_tokens_inner(const u8* p, const u8* end, cJSON* item) {') + g.writeln('\tp = v3_json_skip_space(p, end); if (p >= end) return p;') + g.writeln('\tif (*p == 123) { cJSON* child = item != NULL ? item->child : NULL; p++; p = v3_json_skip_space(p, end); while (p < end && *p != 125) { p = v3_json_skip_string(p, end); p = v3_json_skip_space(p, end); if (p < end && *p == 58) p++; p = v3_json_preserve_number_tokens_inner(p, end, child); if (child != NULL) child = child->next; p = v3_json_skip_space(p, end); if (p < end && *p == 44) { p++; p = v3_json_skip_space(p, end); } else { break; } } return p < end && *p == 125 ? p + 1 : p; }') + g.writeln('\tif (*p == 91) { cJSON* child = item != NULL ? item->child : NULL; p++; p = v3_json_skip_space(p, end); while (p < end && *p != 93) { p = v3_json_preserve_number_tokens_inner(p, end, child); if (child != NULL) child = child->next; p = v3_json_skip_space(p, end); if (p < end && *p == 44) { p++; p = v3_json_skip_space(p, end); } else { break; } } return p < end && *p == 93 ? p + 1 : p; }') + g.writeln('\tif (*p == 34) return v3_json_skip_string(p, end);') + g.writeln('\tconst u8* start = p; while (p < end && *p != 44 && *p != 93 && *p != 125 && *p != 32 && *p != 9 && *p != 10 && *p != 13) p++; if (item != NULL && cJSON_IsNumber(item) && item->valuestring == NULL) { size_t len = (size_t)(p - start); char* raw = (char*)cJSON_malloc(len + 1); if (raw != NULL) { memcpy(raw, start, len); raw[len] = 0; item->valuestring = raw; } } return p;') + g.writeln('}') + g.writeln('static inline void v3_json_preserve_number_tokens(const u8* json, int len, cJSON* root) { if (json != NULL && len > 0 && root != NULL) v3_json_preserve_number_tokens_inner(json, json + len, root); }') +} + +fn (mut g FlatGen) filelock_compat_decls() { + if !g.libc_compat_fns['filelock'] && !g.used_fn_contains('C.v_filelock_lock') + && !g.used_fn_contains('C.v_filelock_unlock') + && !g.used_fn_contains_in_module('FileLock.lock_handle', 'filelock') + && !g.used_fn_contains_in_module('FileLock.lock_fd', 'filelock') + && !g.used_fn_contains_in_module('FileLock.close_lock', 'filelock') { + return + } + g.writeln('#ifndef V_OS_FILELOCK_HELPERS_H') + g.writeln('#ifdef _WIN32') + g.writeln('BOOL LockFileEx(HANDLE handle, DWORD flags, DWORD reserved, DWORD low, DWORD high, OVERLAPPED* overlap);') + g.writeln('BOOL UnlockFileEx(HANDLE handle, DWORD reserved, DWORD low, DWORD high, OVERLAPPED* overlap);') + g.writeln('static inline int v_filelock_lock(HANDLE handle, int exclusive, int immediate, u64 start, u64 len) { OVERLAPPED overlap; memset(&overlap, 0, sizeof(overlap)); overlap.Offset = (DWORD)(start & 0xffffffffULL); overlap.OffsetHigh = (DWORD)(start >> 32); DWORD flags = immediate ? LOCKFILE_FAIL_IMMEDIATELY : 0; if (exclusive) { flags |= LOCKFILE_EXCLUSIVE_LOCK; } DWORD low = len == 0 ? MAXDWORD : (DWORD)(len & 0xffffffffULL); DWORD high = len == 0 ? MAXDWORD : (DWORD)(len >> 32); return LockFileEx(handle, flags, 0, low, high, &overlap) ? 0 : -1; }') + g.writeln('static inline int v_filelock_unlock(HANDLE handle, u64 start, u64 len) { OVERLAPPED overlap; memset(&overlap, 0, sizeof(overlap)); overlap.Offset = (DWORD)(start & 0xffffffffULL); overlap.OffsetHigh = (DWORD)(start >> 32); DWORD low = len == 0 ? MAXDWORD : (DWORD)(len & 0xffffffffULL); DWORD high = len == 0 ? MAXDWORD : (DWORD)(len >> 32); return UnlockFileEx(handle, 0, low, high, &overlap) ? 0 : -1; }') + g.writeln('#else') + g.writeln('static inline int v_filelock_lock(int fd, int exclusive, int immediate, u64 start, u64 len) { struct flock fl; memset(&fl, 0, sizeof(fl)); fl.l_type = exclusive ? F_WRLCK : F_RDLCK; fl.l_whence = SEEK_SET; fl.l_start = (off_t)start; fl.l_len = len == 0 ? 0 : (off_t)len; return fcntl(fd, immediate ? F_SETLK : F_SETLKW, &fl); }') + g.writeln('static inline int v_filelock_unlock(int fd, u64 start, u64 len) { struct flock fl; memset(&fl, 0, sizeof(fl)); fl.l_type = F_UNLCK; fl.l_whence = SEEK_SET; fl.l_start = (off_t)start; fl.l_len = len == 0 ? 0 : (off_t)len; return fcntl(fd, F_SETLK, &fl); }') + g.writeln('#endif') + g.writeln('#endif') +} + +fn (mut g FlatGen) collect_fixed_array_typedefs_needed() map[string]FixedArrayTypedefInfo { + if g.fixed_array_typedefs_ready { + return g.fixed_array_typedefs_needed + } + mut needed := map[string]FixedArrayTypedefInfo{} + mut type_seen := &FixedArrayTypeSeen{} + mut text_seen := &FixedArrayTextSeen{} + old_module := g.tc.cur_module + old_file := g.tc.cur_file + for name, ret_type in g.tc.fn_ret_types { + g.tc.cur_module = module_from_qualified_name(name) + if fixed_array_type_first_seen(ret_type, g.tc.cur_module, mut type_seen) { + g.collect_fixed_array_typedef(ret_type, g.tc.cur_module, mut needed) + } + } + for name, param_types in g.tc.fn_param_types { + g.tc.cur_module = module_from_qualified_name(name) + for param_type in param_types { + if fixed_array_type_first_seen(param_type, g.tc.cur_module, mut type_seen) { + g.collect_fixed_array_typedef(param_type, g.tc.cur_module, mut needed) + } + } + } + for name, fields in g.tc.structs { + g.tc.cur_module = g.fixed_array_typedef_type_module(name, old_module) + for field in fields { + if fixed_array_type_first_seen(field.typ, g.tc.cur_module, mut type_seen) { + g.collect_fixed_array_typedef(field.typ, g.tc.cur_module, mut needed) + } + } + } + for name, fields in g.tc.interface_fields { + g.tc.cur_module = module_from_qualified_name(name) + for field in fields { + if fixed_array_type_first_seen(field.typ, g.tc.cur_module, mut type_seen) { + g.collect_fixed_array_typedef(field.typ, g.tc.cur_module, mut needed) + } + } + } + for name, typ in g.global_types { + g.tc.cur_module = g.global_modules[name] or { old_module } + if fixed_array_type_first_seen(typ, g.tc.cur_module, mut type_seen) { + g.collect_fixed_array_typedef(typ, g.tc.cur_module, mut needed) + } + } + for _, typ in g.tc.c_globals { + g.tc.cur_module = old_module + if fixed_array_type_first_seen(typ, g.tc.cur_module, mut type_seen) { + g.collect_fixed_array_typedef(typ, g.tc.cur_module, mut needed) + } + } + for name, typ in g.tc.const_types { + g.tc.cur_module = g.const_modules[name] or { old_module } + if fixed_array_type_first_seen(typ, g.tc.cur_module, mut type_seen) { + g.collect_fixed_array_typedef(typ, g.tc.cur_module, mut needed) + } + } + mut cur_file := old_file + mut cur_module := old_module + for node in g.a.nodes { + kind_id := node_kind_id(node) + if kind_id == 77 { + cur_file = node.value + cur_module = g.tc.file_modules[cur_file] or { old_module } + g.tc.cur_file = cur_file + g.tc.cur_module = cur_module + continue + } + if kind_id == 73 { + cur_module = node.value + g.tc.cur_file = cur_file + g.tc.cur_module = cur_module + continue + } + g.tc.cur_file = cur_file + g.tc.cur_module = cur_module + // Struct-init node types use scratch text while fields are transformed; the + // declared struct metadata above is the authoritative fixed-array source. + if node.kind != .struct_init && fixed_array_type_text_may_need_typedef(node.typ) + && fixed_array_text_first_seen(node.typ, cur_module, mut text_seen) { + g.collect_fixed_array_typedef_text(node.typ, cur_module, mut needed) + } + match node.kind { + .array_init, .array_literal, .cast_expr, .sizeof_expr, .typeof_expr { + if fixed_array_type_text_may_need_typedef(node.value) + && fixed_array_text_first_seen(node.value, cur_module, mut text_seen) { + g.collect_fixed_array_typedef_text(node.value, cur_module, mut needed) + } + } + else {} + } + } + g.tc.cur_module = old_module + g.tc.cur_file = old_file + g.fixed_array_typedefs_needed = needed.move() + g.fixed_array_typedefs_ready = true + return g.fixed_array_typedefs_needed +} + +@[inline] +fn fixed_array_type_first_seen(typ &types.Type, module_name string, mut seen FixedArrayTypeSeen) bool { + words := unsafe { &u64(voidptr(typ)) } + w0 := unsafe { words[0] } + w1 := unsafe { words[1] } + slot := int((w0 >> 4 ^ w1 ^ u64(module_name.len)) & 4095) + if seen.seen[slot] && seen.w0[slot] == w0 && seen.w1[slot] == w1 + && seen.modules[slot] == module_name { + return false + } + seen.w0[slot] = w0 + seen.w1[slot] = w1 + seen.modules[slot] = module_name + seen.seen[slot] = true + return true +} + +@[inline] +fn fixed_array_text_first_seen(text string, module_name string, mut seen FixedArrayTextSeen) bool { + if text.len == 0 { + return false + } + ptr := voidptr(text.str) + slot := int((u64(ptr) >> 4 ^ u64(text.len) ^ u64(module_name.len)) & 4095) + if seen.ptrs[slot] == ptr && seen.lens[slot] == text.len && seen.modules[slot] == module_name { + return false + } + seen.ptrs[slot] = ptr + seen.lens[slot] = text.len + seen.modules[slot] = module_name + return true +} + +fn (mut g FlatGen) fixed_array_typedefs() { + needed := g.collect_fixed_array_typedefs_needed() + old_len := g.emitted_fixed_array_typedefs.len + for name, info in needed { + g.emit_fixed_array_typedef(name, info, needed, mut g.emitted_fixed_array_typedefs) + } + // Return wrappers for non-early element types (struct/`string`/nested fixed array): + // their bare typedef (above) and element definitions are available now, so a C + // function returning `[N]Foo`/`[N]string` can return the wrapper struct instead of the + // raw array type C rejects. Sorted for deterministic output. + mut wrapper_names := []string{} + for name, _ in needed { + wrapper_names << name + } + wrapper_names.sort() + mut emitted_wrapper := false + for name in wrapper_names { + if name !in g.fixed_array_ret_wrappers { + continue + } + info := needed[name] or { continue } + if g.fixed_array_typedef_is_early(info.arr) { + continue + } + // Completes the struct forward-declared in fixed_array_early_typedefs(). + g.emit_fixed_array_ret_wrapper(name, info, true) + emitted_wrapper = true + } + if g.emitted_fixed_array_typedefs.len > old_len || emitted_wrapper { + g.writeln('') + } +} + +// fixed_array_typedef_is_early reports whether a fixed array's bare typedef can be +// emitted before struct definitions: its element chain must bottom out in a +// primitive/pointer/enum (not a struct or `string`, whose definitions come later). +fn (mut g FlatGen) fixed_array_typedef_is_early(arr types.ArrayFixed) bool { + if !g.fixed_array_len_types_are_early_complete(arr) { + return false + } + elem := arr.elem_type + if elem is types.ArrayFixed { + return g.fixed_array_typedef_is_early(elem) + } + return fixed_array_elem_is_early_complete(elem) +} + +fn (mut g FlatGen) fixed_array_len_types_are_early_complete(arr types.ArrayFixed) bool { + expr := g.fixed_array_len_value(arr) + mut offset := 0 + for offset < expr.len { + rel := expr[offset..].index('sizeof(') or { return true } + open := offset + rel + 'sizeof'.len + close := fixed_array_len_matching_paren(expr, open) + if close < 0 { + return false + } + target := expr[open + 1..close].trim_space() + if target.ends_with('*') { + offset = close + 1 + continue + } + if !g.fixed_array_len_type_is_early_complete(g.tc.parse_type(target)) { + return false + } + offset = close + 1 + } + return true +} + +fn (mut g FlatGen) fixed_array_len_type_is_early_complete(typ types.Type) bool { + if typ is types.Alias { + return g.fixed_array_len_type_is_early_complete(typ.base_type) + } + if typ is types.Pointer { + return true + } + if typ is types.ArrayFixed { + return g.fixed_array_typedef_is_early(typ) + } + return typ is types.Primitive || typ is types.Enum +} + +// populate_fixed_array_ret_wrappers records which fixed-array types get a return +// wrapper struct. It must run before function bodies are generated, so that fn +// signatures, return statements and call sites all agree on whether a given +// fixed-array return is wrapped. EVERY fixed-array type is wrapped, because C +// functions and fn pointers cannot return a raw array type regardless of the element: +// primitive/pointer/enum element wrappers are emitted early (before structs), while +// struct/`string`/nested element wrappers are emitted by fixed_array_typedefs(), after +// the element type is defined. +fn (mut g FlatGen) populate_fixed_array_ret_wrappers() { + old_module := g.tc.cur_module + for name, ret_type in g.tc.fn_ret_types { + g.tc.cur_module = module_from_qualified_name(name) + g.collect_fixed_array_return_wrapper(ret_type) + g.collect_fn_type_fixed_array_return_wrappers(ret_type) + } + for name, param_types in g.tc.fn_param_types { + g.tc.cur_module = module_from_qualified_name(name) + for param_type in param_types { + g.collect_fn_type_fixed_array_return_wrappers(param_type) + } + } + for name, fields in g.tc.structs { + g.tc.cur_module = g.fixed_array_typedef_type_module(name, old_module) + for field in fields { + g.collect_fn_type_fixed_array_return_wrappers(field.typ) + } + } + for name, fields in g.tc.interface_fields { + g.tc.cur_module = module_from_qualified_name(name) + for field in fields { + g.collect_fn_type_fixed_array_return_wrappers(field.typ) + } + } + for name, typ in g.global_types { + g.tc.cur_module = g.global_modules[name] or { old_module } + g.collect_fn_type_fixed_array_return_wrappers(typ) + } + for _, typ in g.tc.c_globals { + g.tc.cur_module = old_module + g.collect_fn_type_fixed_array_return_wrappers(typ) + } + for name, typ in g.tc.const_types { + g.tc.cur_module = g.const_modules[name] or { old_module } + g.collect_fn_type_fixed_array_return_wrappers(typ) + } + g.tc.cur_module = old_module +} + +fn (mut g FlatGen) collect_fixed_array_return_wrapper(typ types.Type) { + if typ is types.ArrayFixed { + g.fixed_array_ret_wrappers[g.fixed_array_c_type(typ)] = true + } else if typ is types.Alias { + g.collect_fixed_array_return_wrapper(typ.base_type) + } +} + +fn (mut g FlatGen) collect_fn_type_fixed_array_return_wrappers(typ types.Type) { + if typ is types.FnType { + g.collect_fixed_array_return_wrapper(typ.return_type) + for param in typ.params { + g.collect_fn_type_fixed_array_return_wrappers(param) + } + } else if typ is types.Pointer { + g.collect_fn_type_fixed_array_return_wrappers(typ.base_type) + } else if typ is types.Alias { + g.collect_fn_type_fixed_array_return_wrappers(typ.base_type) + } else if typ is types.OptionType { + g.collect_fn_type_fixed_array_return_wrappers(typ.base_type) + } else if typ is types.ResultType { + g.collect_fn_type_fixed_array_return_wrappers(typ.base_type) + } else if typ is types.Array { + g.collect_fn_type_fixed_array_return_wrappers(typ.elem_type) + } else if typ is types.ArrayFixed { + g.collect_fn_type_fixed_array_return_wrappers(typ.elem_type) + } else if typ is types.Map { + g.collect_fn_type_fixed_array_return_wrappers(typ.key_type) + g.collect_fn_type_fixed_array_return_wrappers(typ.value_type) + } else if typ is types.MultiReturn { + for item in typ.types { + g.collect_fn_type_fixed_array_return_wrappers(item) + } + } +} + +// emit_fixed_array_ret_wrapper writes the one-field `struct { T ret_arr[N]; }` wrapper +// for a fixed-array return type. The element type's C definition must already be emitted. +// `tagged` completes a previously forward-declared named struct (`struct X { ... };`), +// used for non-early element types whose wrapper is referenced by an fn-pointer typedef +// emitted earlier; otherwise a fresh anonymous typedef is written. +fn (mut g FlatGen) emit_fixed_array_ret_wrapper(name string, info FixedArrayTypedefInfo, tagged bool) { + arr := info.arr + old_module := g.tc.cur_module + g.tc.cur_module = info.module + elem_ct := g.fixed_array_elem_c_type(arr.elem_type) + len_expr := g.fixed_array_len_value(arr) + g.tc.cur_module = old_module + wname := fixed_array_ret_wrapper_name(name) + if tagged { + g.writeln('struct ${wname} { ${elem_ct} ret_arr[${len_expr}]; };') + } else { + g.writeln('typedef struct { ${elem_ct} ret_arr[${len_expr}]; } ${wname};') + } +} + +// emit_fixed_array_ret_wrapper_forward forward-declares a named return-wrapper struct so an +// fn-pointer typedef can name it as a (by-value) return type before the wrapper's element +// type is defined; the struct body is completed later by emit_fixed_array_ret_wrapper. +fn (mut g FlatGen) emit_fixed_array_ret_wrapper_forward(name string) { + wname := fixed_array_ret_wrapper_name(name) + g.writeln('typedef struct ${wname} ${wname};') +} + +// fixed_array_early_typedefs emits, before the fn-ptr typedef block, the bare +// typedefs for fixed arrays whose element chain is a primitive/pointer/enum, plus +// a one-field struct wrapper `struct { T ret_arr[N]; }` for each fixed-array +// return type. fn-ptr typedefs may name a fixed array in param position (bare +// typedef) or return position (wrapper); both must therefore be defined first. C +// functions cannot return raw array types, hence the wrapper (as V1 does). Bare +// typedefs of struct/`string`-element fixed arrays are deferred to +// fixed_array_typedefs(), after the struct definitions. +fn (mut g FlatGen) fixed_array_early_typedefs() { + needed := g.collect_fixed_array_typedefs_needed() + mut names := []string{} + for name, _ in needed { + names << name + } + names.sort() + mut emitted_any := false + for name in names { + info := needed[name] or { continue } + if !g.fixed_array_typedef_is_early(info.arr) { + continue + } + g.emit_fixed_array_typedef(name, info, needed, mut g.emitted_fixed_array_typedefs) + emitted_any = true + } + // Wrapper structs for fixed-array return types. Primitive/pointer/enum element wrappers + // are fully defined here. Struct/`string`/nested element wrappers can't be defined yet + // (their element type comes later), but an fn-pointer typedef in the next block may name + // one as a return type, so forward-declare the named struct now and complete it in + // fixed_array_typedefs(), after the element definitions. + for name in names { + if name !in g.fixed_array_ret_wrappers { + continue + } + info := needed[name] or { continue } + if g.fixed_array_typedef_is_early(info.arr) { + g.emit_fixed_array_ret_wrapper(name, info, false) + } else { + g.emit_fixed_array_ret_wrapper_forward(name) + } + emitted_any = true + } + if emitted_any { + g.writeln('') + } +} + +// fixed_array_ret_wrapper_name is the struct name wrapping a fixed-array return. +fn fixed_array_ret_wrapper_name(bare_c_name string) string { + return '_v_ret_${bare_c_name}' +} + +// fixed_array_elem_is_early_complete reports whether a fixed-array element type's +// C definition is available before the fn_ptr/return-wrapper typedef block. +fn fixed_array_elem_is_early_complete(elem types.Type) bool { + return elem is types.Primitive || elem is types.Pointer || elem is types.Enum + || elem is types.FnType + || (elem is types.Struct && (elem.name == 'thread' || elem.name.ends_with('.thread'))) +} + +// fn_return_type_name is the C type to write for a function/fn-ptr return type, +// substituting the fixed-array wrapper struct when one exists. +fn (mut g FlatGen) fn_return_type_name(t types.Type) string { + return g.fn_return_type_name_for_context(t, g.cur_fn_is_specialized) +} + +fn (mut g FlatGen) fn_return_type_name_for_context(t types.Type, concrete_optional bool) string { + if fixed := array_fixed_type(t) { + bare := g.fixed_array_c_type(fixed) + return fixed_array_ret_wrapper_name(bare) + } + if g.tc.autofree_mode && t is types.Alias { + return g.tc.c_type(t) + } + ct := g.optional_type_name_for_context(t, concrete_optional) + // A function/fn-ptr-valued return (`fn f() fn () int`) has the internal `fn_ptr:...` + // encoding for its C type; map it to the shared `_fn_ptr_N` typedef, since a C function + // cannot be declared returning that raw encoding (it would emit invalid C). + if ct.starts_with('fn_ptr:') { + return g.resolve_fn_ptr_type(ct) + } + return ct +} + +// fn_ptr_return_ct maps a fixed-array return c_type name (string form, used by the +// fn-ptr typedef machinery) to its wrapper struct name when one exists. +fn (g &FlatGen) fn_ptr_return_ct(ct string) string { + if ct.starts_with('Array_fixed_') && ct in g.fixed_array_ret_wrappers { + return fixed_array_ret_wrapper_name(ct) + } + return ct +} + +// emit_ready_fixed_array_typedefs emits, during the topological struct emission, +// any fixed-array bare typedef whose element type is now fully defined (i.e. its +// element struct has been emitted). Struct fields reference the typedef name +// (`Array_fixed_vec__Vec4_f32 x`), so the typedef must precede any struct that +// uses it — which a single later pass cannot guarantee. +fn (mut g FlatGen) emit_ready_fixed_array_typedefs(needed map[string]FixedArrayTypedefInfo, emitted_structs map[string]bool) { + for name, info in needed { + if g.emitted_fixed_array_typedefs[name] { + continue + } + old_module := g.tc.cur_module + g.tc.cur_module = info.module + ready := g.fixed_array_elem_defined(info.arr, emitted_structs) + && g.fixed_array_len_types_defined(info.arr, emitted_structs) + g.tc.cur_module = old_module + if ready { + g.emit_fixed_array_typedef(name, info, needed, mut g.emitted_fixed_array_typedefs) + } + } +} + +fn (mut g FlatGen) fixed_array_len_types_defined(arr types.ArrayFixed, emitted_structs map[string]bool) bool { + expr := g.fixed_array_len_value(arr) + mut offset := 0 + for offset < expr.len { + rel := expr[offset..].index('sizeof(') or { return true } + open := offset + rel + 'sizeof'.len + close := fixed_array_len_matching_paren(expr, open) + if close < 0 { + return false + } + target := expr[open + 1..close].trim_space() + // fixed_array_len_value() has already rendered V struct names as their C + // spelling, which parse_type() cannot resolve back to the original type. + if target !in emitted_structs + && !g.fixed_array_type_defined(g.tc.parse_type(target), emitted_structs) { + return false + } + offset = close + 1 + } + return true +} + +// fixed_array_elem_defined reports whether a fixed array's element type is fully +// available: a primitive/pointer/enum (always), or a struct/`string` already +// emitted. Aliases are unwrapped to their underlying type first (`SimdFloat4` -> +// `vec.Vec4[f32]` struct), so an alias to a not-yet-emitted struct is not treated +// as ready. +fn (g &FlatGen) fixed_array_elem_defined(arr types.ArrayFixed, emitted_structs map[string]bool) bool { + return g.fixed_array_type_defined(arr.elem_type, emitted_structs) +} + +fn (g &FlatGen) fixed_array_type_defined(typ0 types.Type, emitted_structs map[string]bool) bool { + mut typ := typ0 + for typ is types.Alias { + typ = typ.base_type + } + if typ is types.ArrayFixed { + return g.fixed_array_type_defined(typ.elem_type, emitted_structs) + } + if typ is types.OptionType { + return g.fixed_array_type_defined(typ.base_type, emitted_structs) + } + if typ is types.ResultType { + return g.fixed_array_type_defined(typ.base_type, emitted_structs) + } + if typ is types.Struct { + if typ.name == 'thread' || typ.name.ends_with('.thread') { + return true + } + return g.tc.c_type(typ) in emitted_structs + } + if typ is types.Interface || typ is types.SumType { + return g.tc.c_type(typ) in emitted_structs + } + if typ is types.String { + return 'string' in emitted_structs + } + if typ is types.Map { + return 'map' in emitted_structs + } + return true +} + +fn (mut g FlatGen) fixed_array_typedef_type_module(name string, fallback string) string { + if info := g.struct_decl_infos[name] { + return info.module + } + mod := module_from_qualified_name(name) + if mod.len > 0 { + return mod + } + return fallback +} + +fn module_from_qualified_name(name string) string { + if name.contains('.') { + return name.all_before_last('.') + } + if name.contains('__') { + return name.all_before_last('__').replace('__', '.') + } + return '' +} + +fn fixed_array_typedef_module_priority(module_name string) int { + if module_name.len == 0 || module_name == 'C' { + return 0 + } + if module_name == 'main' || module_name == 'builtin' { + return 1 + } + return 2 +} + +fn (mut g FlatGen) collect_fixed_array_typedef(typ types.Type, source_module string, mut needed map[string]FixedArrayTypedefInfo) { + if typ is types.ArrayFixed { + // A raw AST type can be revisited under an importing module's context. If a + // bare alias such as gfx.Range is then parsed as a nonexistent gg.Range, + // do not materialize an unusable fixed-array typedef for that phantom struct. + if g.fixed_array_type_has_unknown_struct(typ.elem_type) { + return + } + old_module := g.tc.cur_module + g.tc.cur_module = source_module + name := g.fixed_array_c_type(typ) + g.tc.cur_module = old_module + existing_priority := if name in needed { + fixed_array_typedef_module_priority(needed[name].module) + } else { + -1 + } + current_priority := fixed_array_typedef_module_priority(source_module) + if name !in needed || current_priority > existing_priority { + needed[name] = FixedArrayTypedefInfo{ + arr: typ + module: source_module + } + } + g.collect_fixed_array_typedef(typ.elem_type, source_module, mut needed) + } else if typ is types.Pointer { + g.collect_fixed_array_typedef(typ.base_type, source_module, mut needed) + } else if typ is types.Alias { + alias_module := module_from_qualified_name(typ.name) + base_module := if alias_module.len > 0 { alias_module } else { source_module } + g.collect_fixed_array_typedef(typ.base_type, base_module, mut needed) + } else if typ is types.OptionType { + g.collect_fixed_array_typedef(typ.base_type, source_module, mut needed) + } else if typ is types.ResultType { + g.collect_fixed_array_typedef(typ.base_type, source_module, mut needed) + } else if typ is types.Array { + g.collect_fixed_array_typedef(typ.elem_type, source_module, mut needed) + } else if typ is types.Map { + g.collect_fixed_array_typedef(typ.key_type, source_module, mut needed) + g.collect_fixed_array_typedef(typ.value_type, source_module, mut needed) + } else if typ is types.FnType { + for param in typ.params { + g.collect_fixed_array_typedef(param, source_module, mut needed) + } + g.collect_fixed_array_typedef(typ.return_type, source_module, mut needed) + } else if typ is types.MultiReturn { + for item in typ.types { + g.collect_fixed_array_typedef(item, source_module, mut needed) + } + } +} + +fn (g &FlatGen) fixed_array_type_has_unknown_struct(typ types.Type) bool { + if typ is types.Struct { + if typ.name == 'thread' || typ.name.ends_with('.thread') { + return false + } + return typ.name !in g.tc.structs + } + if typ is types.ArrayFixed { + return g.fixed_array_type_has_unknown_struct(typ.elem_type) + } + if typ is types.Pointer { + // C can declare a pointer without a complete definition of its pointee. + return false + } + if typ is types.Alias { + return g.fixed_array_type_has_unknown_struct(typ.base_type) + } + if typ is types.OptionType { + return g.fixed_array_type_has_unknown_struct(typ.base_type) + } + if typ is types.ResultType { + return g.fixed_array_type_has_unknown_struct(typ.base_type) + } + return false +} + +fn (mut g FlatGen) collect_fixed_array_typedef_text(type_text string, source_module string, mut needed map[string]FixedArrayTypedefInfo) { + if type_text.len == 0 || !fixed_array_type_text_may_need_typedef(type_text) { + return + } + mut clean := trimmed_space(type_text) + if clean.len == 0 { + return + } + if clean.starts_with('&void[') && clean.ends_with(']') { + clean = 'voidptr' + clean['&void'.len..] + } + if g.collect_postfix_fn_fixed_array_typedef_text(clean, source_module, mut needed) { + return + } + typ := g.tc.parse_type(clean) + if g.fixed_array_typedef_has_unresolved_len(typ) { + return + } + g.collect_fixed_array_typedef(typ, source_module, mut needed) +} + +fn (mut g FlatGen) collect_postfix_fn_fixed_array_typedef_text(clean string, source_module string, mut needed map[string]FixedArrayTypedefInfo) bool { + if (!clean.starts_with('fn(') && !clean.starts_with('fn (')) || !clean.ends_with(']') { + return false + } + bracket := clean.last_index_u8(`[`) + bracket_end := clean.last_index_u8(`]`) + if bracket <= 0 || bracket_end != clean.len - 1 { + return false + } + len_text := clean[bracket + 1..bracket_end].trim_space() + if !fixed_array_typedef_decimal_len_text(len_text) { + return false + } + fn_type := g.tc.parse_type(clean[..bracket].trim_space()) + if fn_type !is types.FnType { + return false + } + g.collect_fixed_array_typedef(types.Type(types.ArrayFixed{ + elem_type: fn_type + len: len_text.int() + }), source_module, mut needed) + return true +} + +fn fixed_array_typedef_decimal_len_text(text string) bool { + if text.len == 0 { + return false + } + for ch in text { + if ch < `0` || ch > `9` { + return false + } + } + return true +} + +fn fixed_array_type_text_may_need_typedef(type_text string) bool { + for i in 0 .. type_text.len - 1 { + if type_text[i] == `[` { + return true + } + } + return false +} + +fn (mut g FlatGen) fixed_array_typedef_has_unresolved_len(typ types.Type) bool { + if typ is types.ArrayFixed { + if typ.len_expr.len > 0 { + if g.tc.fixed_array_len_value(typ) == none { + return true + } + } + return g.fixed_array_typedef_has_unresolved_len(typ.elem_type) + } + if typ is types.Pointer { + return g.fixed_array_typedef_has_unresolved_len(typ.base_type) + } + if typ is types.Alias { + return g.fixed_array_typedef_has_unresolved_len(typ.base_type) + } + if typ is types.OptionType { + return g.fixed_array_typedef_has_unresolved_len(typ.base_type) + } + if typ is types.ResultType { + return g.fixed_array_typedef_has_unresolved_len(typ.base_type) + } + if typ is types.Array { + return g.fixed_array_typedef_has_unresolved_len(typ.elem_type) + } + if typ is types.Map { + return g.fixed_array_typedef_has_unresolved_len(typ.key_type) + || g.fixed_array_typedef_has_unresolved_len(typ.value_type) + } + if typ is types.FnType { + for param in typ.params { + if g.fixed_array_typedef_has_unresolved_len(param) { + return true + } + } + return g.fixed_array_typedef_has_unresolved_len(typ.return_type) + } + if typ is types.MultiReturn { + for item in typ.types { + if g.fixed_array_typedef_has_unresolved_len(item) { + return true + } + } + } + return false +} + +fn (mut g FlatGen) emit_fixed_array_typedef(name string, info FixedArrayTypedefInfo, needed map[string]FixedArrayTypedefInfo, mut emitted map[string]bool) { + if emitted[name] { + return + } + if g.cached_support_identifiers[name] { + emitted[name] = true + return + } + arr := info.arr + old_module := g.tc.cur_module + g.tc.cur_module = info.module + g.emit_fixed_array_elem_deps(arr.elem_type, needed, mut emitted) + elem_ct := g.fixed_array_elem_c_type(arr.elem_type) + len_expr := g.fixed_array_len_value(arr) + g.writeln('typedef ${elem_ct} ${name}[${len_expr}];') + g.tc.cur_module = old_module + emitted[name] = true +} + +fn (mut g FlatGen) emit_fixed_array_elem_deps(elem types.Type, needed map[string]FixedArrayTypedefInfo, mut emitted map[string]bool) { + if elem is types.ArrayFixed { + inner_name := g.fixed_array_c_type(elem) + if inner := needed[inner_name] { + g.emit_fixed_array_typedef(inner_name, inner, needed, mut emitted) + } + } else if elem is types.FnType { + // The function-pointer typedef below can pass fixed arrays by value. Emit + // those bare typedefs first even when this function type was discovered as + // the element of another fixed array. + for param in elem.params { + g.emit_fixed_array_elem_deps(param, needed, mut emitted) + } + encoded := g.tc.c_type(elem) + name := g.resolve_fn_ptr_type(encoded) + g.emit_fn_ptr_typedef(encoded, name, mut g.emitted_fn_ptr_typedefs) + } else if elem is types.OptionType { + g.emit_fixed_array_optional_elem_deps(elem, needed, mut emitted) + } else if elem is types.ResultType { + g.emit_fixed_array_optional_elem_deps(elem, needed, mut emitted) + } else if elem is types.Alias { + g.emit_fixed_array_elem_deps(elem.base_type, needed, mut emitted) + } +} + +fn (mut g FlatGen) emit_fixed_array_optional_elem_deps(elem types.Type, needed map[string]FixedArrayTypedefInfo, mut emitted map[string]bool) { + base := if elem is types.OptionType { + elem.base_type + } else if elem is types.ResultType { + elem.base_type + } else { + return + } + if base is types.ArrayFixed { + g.emit_fixed_array_elem_deps(base, needed, mut emitted) + } + opt_name := g.optional_type_name(elem) + if opt_name != 'Optional' { + val_ct, _ := g.optional_value_ct(elem) + g.emit_optional_typedef(opt_name, val_ct) + } +} + +fn (mut g FlatGen) global_decls() { + old_module := g.tc.cur_module + for name, typ in g.global_types { + if mod := g.global_modules[name] { + g.tc.cur_module = mod + } else { + g.tc.cur_module = old_module + } + decl_typ := g.global_storage_type(name, typ) + is_fn_capture := name.contains('__anon_fn_') + if decl_typ is types.ArrayFixed { + c_elem, dims := g.fixed_array_decl_parts(decl_typ) + init := if g.has_zero_sized_leading_init_slot(decl_typ) { '' } else { ' = {0}' } + if is_fn_capture { + g.writeln('#if defined(__TINYC__)') + g.writeln('static pthread_key_t ${g.cname(name)}_key;') + g.writeln('static void ${g.cname(name)}_key_init(void) __attribute__((constructor));') + g.writeln('static void ${g.cname(name)}_key_init(void) { pthread_key_create(&${g.cname(name)}_key, free); }') + g.writeln('static ${c_elem} (*${g.cname(name)}_slot(void))${dims} { void* p = pthread_getspecific(${g.cname(name)}_key); if (!p) { p = calloc(1, sizeof(*${g.cname(name)}_slot())); pthread_setspecific(${g.cname(name)}_key, p); } return p; }') + g.writeln('#define ${g.cname(name)} (*${g.cname(name)}_slot())') + g.writeln('#else') + g.writeln('_Thread_local ${c_elem} ${g.cname(name)}${dims}${init};') + g.writeln('#endif') + } else { + g.writeln('${c_elem} ${g.cname(name)}${dims}${init};') + } + continue + } + mut ct := g.tc.c_type(decl_typ) + if ct == 'Optional' { + if concrete_ct := g.global_init_optional_c_type(name) { + ct = concrete_ct + } + } + if ct == 'void' { + continue + } + if ct.starts_with('fn_ptr:') { + ct = g.resolve_fn_ptr_type(ct) + } + if extern_name := g.c_extern_global_names[name] { + g.writeln('extern ${ct} ${extern_name};') + continue + } + if name.starts_with('C.') { + if name in g.global_inits { + g.writeln('${ct} ${g.global_c_name(name)};') + } + continue + } + init := if g.can_use_global_brace_zero_init(decl_typ, ct) { ' = {0}' } else { '' } + // Capturing fn literals are immediately consumed callbacks in the V3 + // frontend. Their lifted capture slots must nevertheless be per-thread: + // a process-global slot lets concurrent invocations overwrite one + // another before the callback runs. Native C compilers use TLS directly; + // TinyCC uses pthread keys because it does not implement `_Thread_local`. + if is_fn_capture { + cname := g.cname(name) + if shared_ct := g.fn_capture_shared_global_c_type(name) { + g.writeln('#if defined(__TINYC__)') + g.writeln('static pthread_key_t ${cname}_key;') + g.writeln('static void ${cname}_key_init(void) __attribute__((constructor));') + g.writeln('static void ${cname}_key_init(void) { pthread_key_create(&${cname}_key, free); }') + g.writeln('static ${shared_ct}* ${cname}_slot(void) { void* p = pthread_getspecific(${cname}_key); if (!p) { p = calloc(1, sizeof(${shared_ct})); pthread_setspecific(${cname}_key, p); } return (${shared_ct}*)p; }') + g.writeln('#define ${cname} (*${cname}_slot())') + g.writeln('#else') + g.writeln('_Thread_local ${shared_ct} ${cname};') + g.writeln('#endif') + continue + } + g.writeln('#if defined(__TINYC__)') + g.writeln('static pthread_key_t ${cname}_key;') + g.writeln('static void ${cname}_key_init(void) __attribute__((constructor));') + g.writeln('static void ${cname}_key_init(void) { pthread_key_create(&${cname}_key, free); }') + g.writeln('static ${ct}* ${cname}_slot(void) { void* p = pthread_getspecific(${cname}_key); if (!p) { p = calloc(1, sizeof(${ct})); pthread_setspecific(${cname}_key, p); } return (${ct}*)p; }') + g.writeln('#define ${cname} (*${cname}_slot())') + g.writeln('#else') + g.writeln('_Thread_local ${ct} ${cname}${init};') + g.writeln('#endif') + continue + } + // With -prealloc the arena base block is per-thread; a shared pointer + // would make all threads bump the same block without synchronization. + // The block-recycle cache hangs off this TLS root, keeping bootstrap + // compilers that only know about g_memory_block safe as well. + // cc gets real TLS; tcc implements no _Thread_local, so it gets a + // pthread-key emulation behind an lvalue macro. + if g.prealloc && name == 'g_memory_block' { + cn := g.cname(name) + g.writeln('#if defined(__TINYC__)') + g.writeln('static pthread_key_t ${cn}_key;') + g.writeln('static void ${cn}_key_init(void) __attribute__((constructor));') + g.writeln('static void ${cn}_key_init(void) { pthread_key_create(&${cn}_key, 0); }') + g.writeln('static ${ct}* ${cn}_slot(void) {') + g.writeln(' void* p = pthread_getspecific(${cn}_key);') + g.writeln(' if (p == 0) {') + g.writeln(' p = calloc(1, sizeof(${ct}));') + g.writeln(' pthread_setspecific(${cn}_key, p);') + g.writeln(' }') + g.writeln(' return (${ct}*)p;') + g.writeln('}') + g.writeln('#define ${cn} (*${cn}_slot())') + g.writeln('#else') + g.writeln('_Thread_local ${ct} ${cn}${init};') + g.writeln('#endif') + continue + } + g.writeln('${ct} ${g.cname(name)}${init};') + } + g.tc.cur_module = old_module + if g.global_types.len > 0 { + g.writeln('') + } + g.emit_global_inits() +} + +// queue_global_struct_default_init applies source-level field defaults to a global +// struct that has no explicit initializer. C's static zero initialization alone is +// insufficient for defaults that call functions or initialize fixed-array fields. +fn (mut g FlatGen) queue_global_struct_default_init(name string, typ types.Type) { + if name in g.global_inits { + return + } + if g.tc.diagnostic_files.len > 0 { + file := g.global_files[name] or { return } + if file !in g.tc.diagnostic_files { + return + } + } + clean := default_init_unalias_type(typ) + struct_name := if clean is types.Struct { clean.name } else { return } + if !g.struct_needs_default_init(struct_name) { + return + } + mut visited := map[string]bool{} + init_module := g.global_modules[name] or { g.tc.cur_module } + g.queue_global_struct_field_defaults(g.cname(name), struct_name, init_module, mut visited) +} + +fn (mut g FlatGen) queue_global_struct_field_defaults(target string, struct_name string, init_module string, mut visited map[string]bool) { + if struct_name in visited { + return + } + visited[struct_name] = true + info := g.find_struct_decl(struct_name) or { return } + old_module := g.tc.cur_module + old_file := g.tc.cur_file + old_default_module := g.struct_default_module + g.tc.cur_module = info.module + g.tc.cur_file = info.file + g.struct_default_module = info.module + for i in 0 .. info.node.children_count { + field := g.a.child_node(&info.node, i) + if field.kind != .field_decl { + continue + } + field_type := g.struct_default_field_type(info, field) + field_target := '${target}.${g.cname(field.value)}' + if field.children_count == 0 { + clean_field_type := default_init_unalias_type(field_type) + if clean_field_type is types.Struct && !clean_field_type.name.starts_with('C.') + && g.struct_needs_default_init(clean_field_type.name) { + g.queue_global_struct_field_defaults(field_target, clean_field_type.name, + init_module, mut visited) + } + continue + } + old_sb := g.sb + old_line_start := g.line_start + g.sb = strings.new_builder(128) + g.line_start = true + g.gen_struct_field_expr_for_field(g.a.child(field, 0), info.full_name, field.value, + field_type) + expr := g.sb.str() + g.sb = old_sb + g.line_start = old_line_start + if trimmed_space(expr).len == 0 { + continue + } + if _ := array_fixed_type(field_type) { + g.queue_runtime_init_for_module('\tmemmove(${field_target}, ${expr}, sizeof(${field_target}));', + init_module) + } else { + g.queue_runtime_init_for_module('\t${field_target} = ${expr};', init_module) + } + } + g.tc.cur_module = old_module + g.tc.cur_file = old_file + g.struct_default_module = old_default_module + visited.delete(struct_name) +} + +fn (mut g FlatGen) global_storage_type(name string, typ types.Type) types.Type { + if typ is types.Struct && typ.name == 'Optional' { + if val_id := g.global_inits[name] { + init_type := g.usable_expr_type(val_id) + if init_type is types.OptionType || init_type is types.ResultType { + return init_type + } + if init_type is types.Struct && init_type.name.starts_with('Optional_') { + return init_type + } + } + } + return typ +} + +fn (g &FlatGen) global_c_name(name string) string { + if extern_name := g.c_extern_global_names[name] { + return extern_name + } + if extern_name := g.c_extern_global_names[g.cname(name)] { + return extern_name + } + if name.starts_with('C.') { + return g.cname(name[2..]) + } + return g.cname(name) +} + +fn (mut g FlatGen) fn_capture_shared_global_c_type(name string) ?string { + if !g.cname(name).contains('__anon_fn_') { + return none + } + raw := g.global_raw_type_texts[name] or { return none } + inner := shared_inner_type_text(raw) or { return none } + mod := g.global_modules[name] or { g.tc.cur_module } + qualified := g.shared_qualify_type_text(inner, mod) + return '${g.shared_wrapper_c_name(qualified)}*' +} + +fn (mut g FlatGen) global_init_optional_c_type(name string) ?string { + val_id := g.global_inits[name] or { return none } + init_type := g.usable_expr_type(val_id) + if init_type is types.OptionType || init_type is types.ResultType { + return g.optional_type_name(init_type) + } + if init_type is types.Struct && init_type.name.starts_with('Optional_') { + return g.tc.c_type(init_type) + } + decl_type := g.declared_call_return_type(val_id) + if decl_type is types.OptionType || decl_type is types.ResultType { + return g.optional_type_name(decl_type) + } + if decl_type is types.Struct && decl_type.name.starts_with('Optional_') { + return g.tc.c_type(decl_type) + } + return none +} + +fn (mut g FlatGen) test_failure_helpers() { + g.writeln('static void v3_eprint_lit(const char* s) {') + g.writeln('\tfprintf(stderr, "%s", s);') + g.writeln('}') + g.writeln('static void v3_eprintln_string(string s) {') + g.writeln('\tfprintf(stderr, "%.*s\\n", s.len, (char*)s.str);') + g.writeln('}') + g.writeln('') +} + +// emit_global_inits queues explicit `__global x = expr` assignments and implicit +// struct-field defaults into _vinit in source declaration order. The C globals are +// emitted zero-initialized above; initializer expressions (often function calls like +// `new_timers(...)`) cannot be C static initializers, so they must run at startup. +// +// Plain initializers are emitted as `name = expr;`. Fixed-array globals are +// copied from a generated compound literal with `memmove`, since C arrays are +// not assignable. `&Struct{}` is emitted as a self-contained heap allocation +// (`(T*)memdup(&(T){...}, sizeof(T))`), so it is safe. Other prefix/array +// initializers that would need a dropped temporary are skipped, leaving the +// global zero/NULL -- no regression versus never initializing globals at all. +fn (mut g FlatGen) emit_global_inits() { + old_module := g.tc.cur_module + old_file := g.tc.cur_file + defer { + g.tc.cur_file = old_file + } + for qname in g.global_init_order { + if mod := g.global_modules[qname] { + g.tc.cur_module = mod + } else { + g.tc.cur_module = old_module + } + // Type texts in the initializer may be import-alias qualified + // (`json.Any` under `import x.json2 as json`); alias resolution is + // file-scoped, so parse_type needs the declaring file's context. + if file := g.global_files[qname] { + g.tc.cur_file = file + } else { + g.tc.cur_file = old_file + } + val_id := g.global_inits[qname] or { + if typ := g.global_types[qname] { + clean_type := default_init_unalias_type(typ) + if clean_type is types.Array { + c_elem := g.value_c_type(clean_type.elem_type) + g.queue_runtime_init('\t${g.global_c_name(qname)} = array_new(sizeof(${c_elem}), 0, 0);') + continue + } + if clean_type is types.Map { + g.register_fixed_array_map_key_type(clean_type.key_type) + tmp_sb := g.sb + tmp_line_start := g.line_start + g.sb = strings.new_builder(64) + g.line_start = true + g.write_new_map(clean_type.key_type, clean_type.value_type) + expr_str := g.sb.str() + g.sb = tmp_sb + g.line_start = tmp_line_start + g.queue_runtime_init('\t${g.global_c_name(qname)} = ${expr_str};') + continue + } + g.queue_global_struct_default_init(qname, typ) + } + continue + } + if int(val_id) < 0 { + continue + } + // g_main_argc/g_main_argv are filled in by main's preamble (from argc/argv) + // *before* _vinit runs, and are zero by default in C anyway. Re-emitting their + // `= 0` initializer here would clobber the real argv, leaving os.args empty. + cqname := g.global_c_name(qname) + if cqname == 'g_main_argc' || cqname == 'g_main_argv' { + continue + } + if typ := g.global_types[qname] { + if typ is types.ArrayFixed { + target := g.global_c_name(qname) + g.queue_fixed_array_runtime_init(target, val_id, typ) + continue + } + } + if !g.is_safe_global_init(val_id) { + continue + } + tmp_sb := g.sb + tmp_line_start := g.line_start + g.sb = strings.new_builder(64) + g.line_start = true + g.gen_expr(val_id) + expr_str := g.sb.str() + g.sb = tmp_sb + g.line_start = tmp_line_start + if trimmed_space(expr_str).len == 0 { + continue + } + target := g.global_c_name(qname) + g.queue_runtime_init('\t${target} = ${expr_str};') + if typ := g.global_types[qname] { + if typ is types.Map { + g.queue_map_literal_sets(target, val_id, typ) + } + } + } + g.tc.cur_module = old_module +} + +// is_safe_global_init reports whether a global initializer can be emitted as a +// self-contained `name = expr;` assignment in _vinit, i.e. without auxiliary +// declarations/temporaries that the global context cannot host. +fn (g &FlatGen) is_safe_global_init(val_id flat.NodeId) bool { + if int(val_id) < 0 { + return false + } + node := g.a.nodes[int(val_id)] + if node.kind == .prefix { + // `&Struct{}` becomes an inline `(T*)memdup(&(T){...}, sizeof(T))`, which is + // self-contained; allow it. Other prefixes (e.g. `&local`) would need a + // dropped temporary, so skip them. + if node.op == .amp && node.children_count > 0 { + child := g.a.nodes[int(g.a.child(&node, 0))] + return child.kind == .struct_init || child.kind == .assoc + } + return false + } + return match node.kind { + .array_literal, .array_init { + // Array literals need a backing temp the transformer drops for globals; + // leave them zero/NULL instead of emitting a reference to an undeclared + // symbol. + false + } + else { + true + } + } +} + +fn (g &FlatGen) const_get_deps(val_id flat.NodeId) []string { + mut deps := []string{} + mut visited_fns := map[string]bool{} + g.const_collect_deps_inner(val_id, mut deps, mut visited_fns, map[string]bool{}) + return deps +} + +fn (g &FlatGen) const_collect_deps(val_id flat.NodeId, mut deps []string) { + mut visited_fns := map[string]bool{} + g.const_collect_deps_inner(val_id, mut deps, mut visited_fns, map[string]bool{}) +} + +// const_collect_deps_inner walks a const initializer (recursing into called helper +// bodies) collecting the consts it reads. `shadowed` holds names bound by the current +// helper's parameters/locals so an identifier that shadows a const is not mistaken for +// a dependency (which could invent a false dependency cycle). +fn (g &FlatGen) const_collect_deps_inner(val_id flat.NodeId, mut deps []string, mut visited_fns map[string]bool, shadowed map[string]bool) { + if int(val_id) < 0 || int(val_id) >= g.a.nodes.len { + return + } + node := g.a.nodes[int(val_id)] + match node.kind { + .fn_literal, .lambda_expr { + // Nested functions have their own lexical scope and are not executed merely + // because the enclosing helper is called. + return + } + .fn_decl { + mut fn_shadowed := shadowed.clone() + for i in 0 .. node.children_count { + child := g.a.child_node(&node, i) + if child.kind != .param { + if g.prefix_param_scan { + break + } + continue + } + if child.value.len > 0 { + fn_shadowed[child.value] = true + } + } + g.const_collect_scope_children(node, 0, mut deps, mut visited_fns, mut fn_shadowed) + return + } + .block { + mut block_shadowed := shadowed.clone() + g.const_collect_scope_children(node, 0, mut deps, mut visited_fns, mut block_shadowed) + return + } + .decl_assign { + g.const_collect_decl_assign_deps(node, mut deps, mut visited_fns, shadowed) + return + } + .if_expr { + g.const_collect_if_deps(node, mut deps, mut visited_fns, shadowed) + return + } + .for_stmt { + g.const_collect_for_deps(node, mut deps, mut visited_fns, shadowed) + return + } + .for_in_stmt { + g.const_collect_for_in_deps(node, mut deps, mut visited_fns, shadowed) + return + } + .match_branch { + g.const_collect_match_branch_deps(node, mut deps, mut visited_fns, shadowed) + return + } + else {} + } + + if node.kind == .ident || node.kind == .selector { + if !g.const_ref_base_shadowed(node, shadowed) { + const_name := g.const_ref_name_from_node(node) + if const_name.len > 0 { + deps << const_name + } + } + } + if node.kind == .call && node.children_count > 0 + && !g.const_ref_base_shadowed(g.a.child_node(&node, 0), shadowed) { + callee := g.a.child_node(&node, 0) + mut callee_name := '' + mut callee_module := '' + if callee.kind == .ident { + callee_name = callee.value + } else if callee.kind == .selector { + callee_name = callee.value + if callee.children_count > 0 { + base_id := g.a.child(callee, 0) + base := g.a.nodes[int(base_id)] + if base.kind == .ident && base.value in g.modules { + // Resolve an import alias (`import some.mod as m` makes the base + // ident `m`) to the real module name so the module match compares + // against the actual `module` declaration. + callee_module = g.modules[base.value] + } else { + receiver := g.const_call_receiver_type_name(base_id) + if receiver.len > 0 { + callee_name = '${receiver}.${callee.value}' + } else if base.kind == .ident { + callee_module = base.value + } + } + } + } + if resolved := g.tc.resolved_call_name(val_id) { + callee_name = resolved + } + // Key the visited set by the qualified name so `a.foo` and `b.foo` are treated + // as distinct bodies rather than one being skipped. + visit_key := if callee_module.len > 0 { + '${callee_module}.${callee_name}' + } else { + callee_name + } + if callee_name.len > 0 && !visited_fns[visit_key] { + visited_fns[visit_key] = true + if target := g.const_fn_decl_node(callee_name, callee_module) { + // A callee starts a fresh lexical scope; bindings in the caller do not + // shadow const references inside the called helper. + g.const_collect_deps_inner(target, mut deps, mut visited_fns, map[string]bool{}) + } + } + } + for i in 0 .. node.children_count { + g.const_collect_deps_inner(g.a.child(&node, i), mut deps, mut visited_fns, shadowed) + } +} + +fn (g &FlatGen) const_fn_decl_node(callee_name string, callee_module string) ?flat.NodeId { + if g.fn_decl_nodes_by_name.len == 0 && g.fn_decl_nodes_by_short.len == 0 + && g.fn_decl_nodes_by_module_short.len == 0 { + return g.const_fn_decl_node_scan(callee_name, callee_module) + } + short := callee_name.all_after_last('.') + if callee_module.len > 0 { + if id := g.fn_decl_nodes_by_module_short['${callee_module}\x01${short}'] { + return id + } + module_short := callee_module.all_after_last('.') + if module_short != callee_module { + if id := g.fn_decl_nodes_by_module_short['${module_short}\x01${short}'] { + return id + } + } + } + // A declaration value can itself be qualified (for example `Type.method`). + // Try the complete callee name and each dotted suffix, selecting the earliest + // declaration just like the former AST-order scan. + mut exact_target := -1 + mut suffix := callee_name + for { + if id := g.fn_decl_nodes_by_name[suffix] { + if exact_target < 0 || int(id) < exact_target { + exact_target = int(id) + } + } + dot := suffix.index('.') or { break } + suffix = suffix[dot + 1..] + } + if exact_target >= 0 { + return flat.NodeId(exact_target) + } + return g.fn_decl_nodes_by_short[short] or { none } +} + +fn (g &FlatGen) const_fn_decl_node_scan(callee_name string, callee_module string) ?flat.NodeId { + short := callee_name.all_after_last('.') + mut cur_module := '' + mut module_target := -1 + mut exact_target := -1 + mut suffix_target := -1 + for i, candidate in g.a.nodes { + if candidate.kind == .module_decl { + cur_module = candidate.value + continue + } + if candidate.kind != .fn_decl + || (candidate.value != callee_name && candidate.value.all_after_last('.') != short) { + continue + } + if module_target < 0 && callee_module.len > 0 + && (cur_module == callee_module || cur_module == callee_module.all_after_last('.')) { + module_target = i + } + if exact_target < 0 + && (candidate.value == callee_name || callee_name.ends_with('.${candidate.value}')) { + exact_target = i + } + if suffix_target < 0 { + suffix_target = i + } + } + target := if module_target >= 0 { + module_target + } else if exact_target >= 0 { + exact_target + } else { + suffix_target + } + if target >= 0 { + return flat.NodeId(target) + } + return none +} + +fn (g &FlatGen) const_call_receiver_type_name(base_id flat.NodeId) string { + resolved := types.unwrap_pointer(g.tc.resolve_type(base_id)) + name := resolved.name() + if name.len > 0 && name !in ['void', 'unknown'] { + return name + } + base := g.a.nodes[int(base_id)] + if base.typ.len > 0 && base.typ !in ['void', 'unknown'] { + return base.typ.trim_left('&') + } + if base.kind == .struct_init { + return base.value.trim_left('&') + } + return '' +} + +fn (g &FlatGen) const_collect_scope_children(node flat.Node, start int, mut deps []string, mut visited_fns map[string]bool, mut shadowed map[string]bool) { + for i in start .. node.children_count { + child_id := g.a.child(&node, i) + child := g.a.nodes[int(child_id)] + if child.kind == .decl_assign { + g.const_collect_decl_assign_deps(child, mut deps, mut visited_fns, shadowed) + g.const_add_decl_assign_bindings(child, mut shadowed) + } else { + g.const_collect_deps_inner(child_id, mut deps, mut visited_fns, shadowed) + } + } +} + +fn (g &FlatGen) const_decl_assign_is_multi_return(node flat.Node) bool { + if node.children_count < 3 { + return false + } + if _ := g.multi_return_expr_type_for_lhs_count(g.a.child(&node, 1), node.children_count - 1) { + return true + } + return false +} + +fn (g &FlatGen) const_collect_decl_assign_deps(node flat.Node, mut deps []string, mut visited_fns map[string]bool, shadowed map[string]bool) { + if node.children_count < 2 { + return + } + if g.const_decl_assign_is_multi_return(node) { + g.const_collect_deps_inner(g.a.child(&node, 1), mut deps, mut visited_fns, shadowed) + return + } + mut i := 1 + for i < node.children_count { + g.const_collect_deps_inner(g.a.child(&node, i), mut deps, mut visited_fns, shadowed) + i += 2 + } +} + +fn (g &FlatGen) const_add_decl_assign_bindings(node flat.Node, mut shadowed map[string]bool) { + if node.children_count < 2 { + return + } + if g.const_decl_assign_is_multi_return(node) { + for i in 0 .. node.children_count { + if i == 1 { + continue + } + g.const_add_shadow_binding(g.a.child_node(&node, i), mut shadowed) + } + return + } + mut i := 0 + for i < node.children_count { + g.const_add_shadow_binding(g.a.child_node(&node, i), mut shadowed) + i += 2 + } +} + +fn (g &FlatGen) const_add_shadow_binding(node flat.Node, mut shadowed map[string]bool) { + if node.kind == .ident && node.value.len > 0 && node.value != '_' { + shadowed[node.value] = true + } +} + +fn (g &FlatGen) const_collect_if_deps(node flat.Node, mut deps []string, mut visited_fns map[string]bool, shadowed map[string]bool) { + if node.children_count == 0 { + return + } + cond_id := g.a.child(&node, 0) + cond := g.a.nodes[int(cond_id)] + if cond.kind == .decl_assign { + mut then_shadowed := shadowed.clone() + g.const_collect_decl_assign_deps(cond, mut deps, mut visited_fns, shadowed) + g.const_add_decl_assign_bindings(cond, mut then_shadowed) + if node.children_count > 1 { + g.const_collect_deps_inner(g.a.child(&node, 1), mut deps, mut visited_fns, + then_shadowed) + } + } else { + g.const_collect_deps_inner(cond_id, mut deps, mut visited_fns, shadowed) + if node.children_count > 1 { + g.const_collect_deps_inner(g.a.child(&node, 1), mut deps, mut visited_fns, shadowed) + } + } + for i in 2 .. node.children_count { + g.const_collect_deps_inner(g.a.child(&node, i), mut deps, mut visited_fns, shadowed) + } +} + +fn (g &FlatGen) const_collect_for_deps(node flat.Node, mut deps []string, mut visited_fns map[string]bool, shadowed map[string]bool) { + mut loop_shadowed := shadowed.clone() + if node.children_count > 0 { + init_id := g.a.child(&node, 0) + init := g.a.nodes[int(init_id)] + if init.kind == .decl_assign { + g.const_collect_decl_assign_deps(init, mut deps, mut visited_fns, shadowed) + g.const_add_decl_assign_bindings(init, mut loop_shadowed) + } else { + g.const_collect_deps_inner(init_id, mut deps, mut visited_fns, shadowed) + } + } + header_end := if node.children_count < 3 { + node.children_count + } else { + 3 + } + for i in 1 .. header_end { + g.const_collect_deps_inner(g.a.child(&node, i), mut deps, mut visited_fns, loop_shadowed) + } + if node.children_count > 3 { + mut body_shadowed := loop_shadowed.clone() + g.const_collect_scope_children(node, 3, mut deps, mut visited_fns, mut body_shadowed) + } +} + +fn (g &FlatGen) const_collect_for_in_deps(node flat.Node, mut deps []string, mut visited_fns map[string]bool, shadowed map[string]bool) { + body_start := node.value.int() + header_end := if body_start < node.children_count { + body_start + } else { + node.children_count + } + for i in 2 .. header_end { + g.const_collect_deps_inner(g.a.child(&node, i), mut deps, mut visited_fns, shadowed) + } + mut body_shadowed := shadowed.clone() + if node.children_count > 0 { + g.const_add_shadow_binding(g.a.child_node(&node, 0), mut body_shadowed) + } + if node.children_count > 1 { + g.const_add_shadow_binding(g.a.child_node(&node, 1), mut body_shadowed) + } + if body_start < node.children_count { + g.const_collect_scope_children(node, body_start, mut deps, mut visited_fns, mut + body_shadowed) + } +} + +fn (g &FlatGen) const_collect_match_branch_deps(node flat.Node, mut deps []string, mut visited_fns map[string]bool, shadowed map[string]bool) { + condition_count := if node.value == 'else' { 0 } else { node.value.int() } + body_start := if condition_count < node.children_count { + condition_count + } else { + node.children_count + } + for i in 0 .. body_start { + g.const_collect_deps_inner(g.a.child(&node, i), mut deps, mut visited_fns, shadowed) + } + if condition_count < node.children_count { + mut branch_shadowed := shadowed.clone() + g.const_collect_scope_children(node, condition_count, mut deps, mut visited_fns, mut + branch_shadowed) + } +} + +// const_ref_base_shadowed reports whether `node` (an ident, or a selector whose base +// is an ident) refers to a name bound by the current helper scope rather than a const. +fn (g &FlatGen) const_ref_base_shadowed(node flat.Node, shadowed map[string]bool) bool { + if shadowed.len == 0 { + return false + } + if node.kind == .ident { + return shadowed[node.value] + } + if node.kind == .selector && node.children_count > 0 { + base := g.a.child_node(&node, 0) + return base.kind == .ident && shadowed[base.value] + } + return false +} + +fn (g &FlatGen) const_refs_other_const(val_id flat.NodeId) bool { + if int(val_id) < 0 || int(val_id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(val_id)] + if node.kind == .ident || node.kind == .selector { + return g.const_ref_name_from_node(node).len > 0 + } + for i in 0 .. node.children_count { + if g.const_refs_other_const(g.a.child(&node, i)) { + return true + } + } + return false +} + +fn (mut g FlatGen) emit_const(name string, val_id flat.NodeId) { + old_module := g.tc.cur_module + old_file := g.tc.cur_file + defer { + g.tc.cur_file = old_file + } + const_owner := g.const_modules[name] or { '' } + if const_owner.len > 0 { + g.tc.cur_module = const_owner + } else if g.const_primary_name(name).contains('.') { + // Cache headers can retain only the qualified const key. Use its owner + // while lowering initializer references to sibling consts. + g.tc.cur_module = g.const_primary_name(name).all_before_last('.') + } + // Import-alias qualified type texts (`json.Any`) in the initializer resolve + // per file, so parse_type needs the declaring file's context. + if file := g.const_files[name] { + g.tc.cur_file = file + } + val_node := g.a.nodes[int(val_id)] + if val_node.kind == .empty { + g.tc.cur_module = old_module + return + } + mut v_type := if val_node.kind == .offsetof_expr { + types.Type(types.usize_) + } else { + g.const_storage_type_for_value(name, val_id, g.tc.resolve_type(val_id)) + } + // A const initialised by a generic call (e.g. `stdatomic.new_atomic(0)`) + // keeps the generic return type `&AtomicVal[T]`. The initializer is already + // monomorphized, so recover the concrete storage type from it to avoid + // emitting an undeclared `AtomicVal_T`. + if g.type_contains_generic_placeholder(v_type) { + concrete := g.usable_expr_type(val_id) + if !g.type_contains_generic_placeholder(concrete) { + v_type = concrete + } + } + mut ct := if v_type is types.OptionType || v_type is types.ResultType { + g.optional_type_name(v_type) + } else { + g.tc.c_type(v_type) + } + if ct.starts_with('fn_ptr:') { + ct = g.resolve_fn_ptr_type(ct) + } + qname := g.const_ident_c_name(name) + if qname == 'builtin__error_sentinel' { + type_id := g.ierror_type_id_for_pattern('MessageError') + object_name := '${qname}__object' + message := '(string){"error", 5, 1}' + g.writeln('MessageError ${object_name} = (MessageError){.msg = ${message}};') + g.writeln('IError ${qname} = (IError){._typ = ${type_id}, ._object = &${object_name}, .message = ${message}, .code = 0};') + g.tc.cur_module = old_module + return + } + if val_node.kind == .block && val_node.children_count > 0 { + // A lowered const initializer (`.map()` chains): leading statements + // compute temps, the last child is the value expression. + if ct != 'void' { + g.writeln('${ct} ${qname};') + g.queue_const_runtime_init(g.const_block_init_to_string(qname, val_node, v_type)) + } + g.tc.cur_module = old_module + return + } + expr_str := if v_type !is types.ArrayFixed && ct == 'Array' { + arr := array_like_type(default_init_unalias_type(v_type)) or { + types.Array{ + elem_type: types.Type(types.void_) + } + } + elem_type := if (arr.elem_type is types.Void || arr.elem_type is types.Unknown) + && val_node.children_count > 0 { + g.usable_expr_type(g.a.child(&val_node, 0)) + } else { + arr.elem_type + } + g.expr_to_string_with_expected_type(val_id, types.Type(types.Array{ + elem_type: elem_type + })) + } else if g.is_const_expr(val_id) { + g.const_expr_to_string(val_id, []string{}) + } else { + g.expr_to_string(val_id) + } + if trimmed_space(expr_str).len == 0 { + g.tc.cur_module = old_module + return + } + if v_type is types.String && g.ccompiler == 'msvc' && val_node.kind == .string_literal { + g.writeln('string ${qname};') + g.queue_const_runtime_init('\t${qname} = _S(${c_segmented_string_literal(val_node.value)});') + g.tc.cur_module = old_module + return + } + mut is_static_const := g.is_const_expr(val_id) && !g.const_expr_needs_runtime_storage(expr_str) + if v_type is types.Array || ct == 'Array' { + is_static_const = false + } + if v_type is types.ArrayFixed && v_type.elem_type is types.ArrayFixed { + is_static_const = false + } + if !is_static_const { + if v_type is types.ArrayFixed { + c_elem, dims := g.fixed_array_decl_parts(v_type) + g.writeln('${c_elem} ${qname}${dims};') + g.queue_const_fixed_array_runtime_init(qname, val_id, v_type) + } else if ct != 'void' { + g.writeln('${ct} ${qname};') + // The initializer is not a compile-time constant (e.g. `os.args = + // arguments()`), so it cannot be a C static initializer. Run it at startup + // in _vinit; otherwise the const stays zero/empty and first use is wrong. + g.queue_const_runtime_init('\t${qname} = ${expr_str};') + if v_type is types.Map { + g.queue_const_map_literal_sets(qname, val_id, v_type) + } + } + g.tc.cur_module = old_module + return + } + if v_type is types.String { + g.writeln('string ${qname} = ${expr_str};') + } else if v_type is types.ArrayFixed { + c_elem, dims := g.fixed_array_decl_parts(v_type) + // A fixed-array object declaration cannot be initialized from a + // compound-literal array rvalue (`= (u8[16]){...}`); C requires a bare + // brace list (`= {...}`) for the array elements. Strip the redundant + // leading cast that the value expression carries when present. + mut init_str := expr_str + cast_prefix := '(${c_elem}${dims})' + if init_str.starts_with(cast_prefix) { + init_str = init_str[cast_prefix.len..].trim_space() + } + g.writeln('const ${c_elem} ${qname}${dims} = ${init_str};') + } else if v_type is types.Primitive || v_type is types.Char || v_type is types.Rune + || v_type is types.ISize || v_type is types.USize || v_type is types.Enum + || ct in ['bool', 'char', 'i8', 'i16', 'i32', 'int', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'float', 'double', 'isize', 'usize'] { + if qname == 'max_len' && ct == 'int' { + g.writeln('enum { ${qname} = ${expr_str} };') + } else if ct == 'u8' || g.fixed_storage_consts[g.const_primary_name(name)] + || g.name_collides_with_struct_field(qname) { + // A `#define` whose name matches a struct field would wrongly expand every + // `.field` access. Byte constants are also passed by reference by generic + // binary I/O helpers, so they need addressable storage rather than a macro. + g.writeln('static const ${ct} ${qname} = ${expr_str};') + } else { + g.writeln('#define ${qname} (${expr_str})') + } + } else if fixed := array_fixed_type(default_init_unalias_type(v_type)) { + // An alias whose underlying type is a fixed array still declares a C + // array object (`const Array_fixed_u8_16 name`), which cannot be + // initialized from a compound-literal array rvalue (`= (u8[16]){...}`). + // Strip the redundant cast to a bare brace list. + c_elem, dims := g.fixed_array_decl_parts(fixed) + mut init_str := expr_str + cast_prefix := '(${c_elem}${dims})' + if init_str.starts_with(cast_prefix) { + init_str = init_str[cast_prefix.len..].trim_space() + } + g.writeln('const ${ct} ${qname} = ${init_str};') + } else { + g.writeln('const ${ct} ${qname} = ${expr_str};') + } + g.tc.cur_module = old_module +} + +// name_collides_with_struct_field reports whether a name is the C name of any struct +// field, building the set lazily on first use. +fn (mut g FlatGen) name_collides_with_struct_field(name string) bool { + if g.field_name_set.len == 0 { + for _, fields in g.tc.structs { + for f in fields { + g.field_name_set[c_field_name(f.name)] = true + } + } + // Guard against an all-fieldless program re-scanning every call. + g.field_name_set[''] = true + } + return name in g.field_name_set +} + +fn (g &FlatGen) const_expr_needs_runtime_storage(expr string) bool { + return expr.contains('array_new(') || expr.contains('new_map(') || expr.contains('({') + || expr.contains('sync__new_channel_st(') || expr.contains('__map_') + || expr.contains('_str_') +} + +fn (mut g FlatGen) queue_map_literal_sets(target string, val_id flat.NodeId, map_type types.Map) { + if int(val_id) < 0 || int(val_id) >= g.a.nodes.len { + return + } + node := g.a.nodes[int(val_id)] + if node.kind != .map_init { + return + } + c_key := g.map_key_temp_c_type(map_type.key_type) + c_val := g.value_c_type(map_type.value_type) + for i := 0; i + 1 < node.children_count; i += 2 { + key := g.expr_to_string_with_expected_type(g.a.child(&node, i), map_type.key_type) + val := g.expr_to_string_with_expected_type(g.a.child(&node, i + 1), map_type.value_type) + g.queue_runtime_init('\tmap__set(&${target}, &(${c_key}[]){${key}}, &(${c_val}[]){${val}});') + } +} + +fn (mut g FlatGen) queue_const_map_literal_sets(target string, val_id flat.NodeId, map_type types.Map) { + if int(val_id) < 0 || int(val_id) >= g.a.nodes.len { + return + } + node := g.a.nodes[int(val_id)] + if node.kind != .map_init { + return + } + c_key := g.map_key_temp_c_type(map_type.key_type) + c_val := g.value_c_type(map_type.value_type) + for i := 0; i + 1 < node.children_count; i += 2 { + key := g.expr_to_string_with_expected_type(g.a.child(&node, i), map_type.key_type) + val := g.expr_to_string_with_expected_type(g.a.child(&node, i + 1), map_type.value_type) + g.queue_const_runtime_init('\tmap__set(&${target}, &(${c_key}[]){${key}}, &(${c_val}[]){${val}});') + } +} + +fn (mut g FlatGen) queue_fixed_array_runtime_init(target string, val_id flat.NodeId, fixed types.ArrayFixed) bool { + expr := g.fixed_array_runtime_copy_source_expr(val_id, fixed) + if trimmed_space(expr).len == 0 { + return false + } + g.queue_runtime_init('\tmemmove(${target}, ${expr}, sizeof(${target}));') + return true +} + +fn (mut g FlatGen) queue_const_fixed_array_runtime_init(target string, val_id flat.NodeId, fixed types.ArrayFixed) bool { + expr := g.fixed_array_runtime_copy_source_expr(val_id, fixed) + if trimmed_space(expr).len == 0 { + return false + } + g.queue_const_runtime_init('\tmemmove(${target}, ${expr}, sizeof(${target}));') + return true +} + +fn (mut g FlatGen) fixed_array_runtime_copy_source_expr(val_id flat.NodeId, fixed types.ArrayFixed) string { + literal := g.fixed_array_compound_literal_expr(val_id, fixed) + if trimmed_space(literal).len > 0 { + return literal + } + return g.fixed_array_copy_source_string(val_id, types.Type(fixed)) +} + +fn (mut g FlatGen) fixed_array_compound_literal_expr(val_id flat.NodeId, fixed types.ArrayFixed) string { + init := g.fixed_array_initializer_string(val_id, fixed) + if trimmed_space(init).len == 0 { + return '' + } + c_elem, dims := g.fixed_array_decl_parts(fixed) + return '(${c_elem}${dims})${init}' +} + +fn (mut g FlatGen) fixed_array_initializer_string(val_id flat.NodeId, fixed types.ArrayFixed) string { + mut builder := strings.new_builder(64) + if !g.write_fixed_array_initializer(mut builder, val_id, fixed) { + unsafe { builder.free() } + return '' + } + result := builder.str() + unsafe { builder.free() } + return result +} + +fn (mut g FlatGen) write_fixed_array_initializer(mut builder strings.Builder, val_id flat.NodeId, fixed types.ArrayFixed) bool { + if int(val_id) < 0 || int(val_id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(val_id)] + if node.kind in [.ident, .selector] { + const_name := g.const_ref_name_from_node(node) + if const_name.len > 0 { + if const_id := g.const_vals[const_name] { + return g.write_fixed_array_initializer(mut builder, const_id, fixed) + } + } + if g.write_fixed_array_value_initializer(mut builder, val_id, fixed) { + return true + } + } + if node.kind == .postfix && node.children_count > 0 { + return g.write_fixed_array_initializer(mut builder, g.a.child(&node, 0), fixed) + } + if node.kind == .cast_expr && node.children_count > 0 { + return g.write_fixed_array_initializer(mut builder, g.a.child(&node, 0), fixed) + } + if node.kind in [.array_init, .array_literal, .struct_init] && node.children_count == 0 { + g.write_empty_fixed_array_initializer(mut builder, fixed) + return true + } + if node.kind != .array_literal { + return false + } + builder.write_u8(`{`) + for i in 0 .. node.children_count { + if i > 0 { + builder.write_string(', ') + } + child_id := g.a.child(&node, i) + if fixed.elem_type is types.ArrayFixed { + if !g.write_fixed_array_initializer(mut builder, child_id, fixed.elem_type) { + return false + } + } else { + g.write_fixed_array_elem_initializer(mut builder, child_id, fixed.elem_type) + } + } + builder.write_u8(`}`) + return true +} + +fn (mut g FlatGen) write_fixed_array_value_initializer(mut builder strings.Builder, val_id flat.NodeId, fixed types.ArrayFixed) bool { + len_text := trimmed_space(g.fixed_array_len_value(fixed)) + if !cgen_decimal_text(len_text) { + return false + } + base := trimmed_space(g.expr_to_string(val_id)) + if base.len == 0 { + return false + } + g.write_fixed_array_value_initializer_from_text(mut builder, base, fixed) + return true +} + +fn (mut g FlatGen) write_fixed_array_value_initializer_from_text(mut builder strings.Builder, base string, fixed types.ArrayFixed) { + len := trimmed_space(g.fixed_array_len_value(fixed)).int() + builder.write_u8(`{`) + for i in 0 .. len { + if i > 0 { + builder.write_string(', ') + } + elem := '${base}[${i}]' + if fixed.elem_type is types.ArrayFixed { + g.write_fixed_array_value_initializer_from_text(mut builder, elem, fixed.elem_type) + } else { + builder.write_string(elem) + } + } + builder.write_u8(`}`) +} + +fn (mut g FlatGen) write_empty_fixed_array_initializer(mut builder strings.Builder, fixed types.ArrayFixed) { + if fixed_array_empty_initializer_is_zero(fixed) { + builder.write_string('{0}') + return + } + len_text := trimmed_space(g.fixed_array_len_value(fixed)) + if !cgen_decimal_text(len_text) { + builder.write_string('{0}') + return + } + len := len_text.int() + builder.write_u8(`{`) + for i in 0 .. len { + if i > 0 { + builder.write_string(', ') + } + if fixed.elem_type is types.ArrayFixed { + g.write_empty_fixed_array_initializer(mut builder, fixed.elem_type) + } else { + g.write_fixed_array_default_elem_initializer(mut builder, fixed.elem_type) + } + } + builder.write_u8(`}`) +} + +fn fixed_array_empty_initializer_is_zero(fixed types.ArrayFixed) bool { + elem_type := default_init_unalias_type(fixed.elem_type) + if elem_type is types.ArrayFixed { + return fixed_array_empty_initializer_is_zero(elem_type) + } + return elem_type !is types.Array && elem_type !is types.Struct && elem_type !is types.Map +} + +fn (mut g FlatGen) write_fixed_array_default_elem_initializer(mut builder strings.Builder, elem_type types.Type) { + if elem_type is types.Array { + c_elem := g.value_c_type(elem_type.elem_type) + builder.write_string('array_new(sizeof(${c_elem}), 0, 0)') + return + } + if elem_type is types.Struct { + builder.write_string(g.default_value_to_string(elem_type)) + return + } + if elem_type is types.Map { + builder.write_string(g.new_map_expr_string(elem_type.key_type, elem_type.value_type)) + return + } + builder.write_u8(`0`) +} + +fn (mut g FlatGen) new_map_expr_string(key_type types.Type, value_type types.Type) string { + orig := g.sb + orig_line_start := g.line_start + g.sb = strings.new_builder(64) + g.line_start = false + g.write_new_map(key_type, value_type) + result := g.sb.str() + g.sb = orig + g.line_start = orig_line_start + return result +} + +fn cgen_decimal_text(s string) bool { + if s.len == 0 { + return false + } + for ch in s { + if ch < `0` || ch > `9` { + return false + } + } + return true +} + +fn (mut g FlatGen) write_fixed_array_elem_initializer(mut builder strings.Builder, val_id flat.NodeId, elem_type types.Type) { + if int(val_id) < 0 || int(val_id) >= g.a.nodes.len { + builder.write_u8(`0`) + return + } + node := g.a.nodes[int(val_id)] + clean_elem_type := default_init_unalias_type(elem_type) + if node.kind == .map_init && clean_elem_type is types.Map { + builder.write_string(g.new_map_expr_string(clean_elem_type.key_type, + clean_elem_type.value_type)) + return + } + if node.kind == .array_init && clean_elem_type is types.Array { + c_elem := g.value_c_type(clean_elem_type.elem_type) + builder.write_string('array_new(sizeof(${c_elem}), 0, 0)') + return + } + if g.is_const_expr(val_id) && !(node.kind == .prefix && node.op == .amp) { + const_val := g.const_expr_to_string(val_id, []string{}) + if trimmed_space(const_val).len > 0 { + builder.write_string(const_val) + return + } + } + expr := g.expr_to_string_with_expected_type(val_id, elem_type) + if trimmed_space(expr).len > 0 { + builder.write_string(expr) + return + } + builder.write_u8(`0`) +} + +fn (mut g FlatGen) precompute_consts() string { + old_sb := g.sb + old_line_start := g.line_start + g.sb = strings.new_builder(1024) + g.line_start = true + names := g.const_emission_order_owned() + for name in names { + val_id := g.const_vals[name] + g.emit_const(name, val_id) + } + if g.const_vals.len > 0 { + g.writeln('') + } + result := g.sb.str() + // `.str()` copies out of the temporary const builder. + unsafe { g.sb.free() } + g.sb = old_sb + g.line_start = old_line_start + return result +} + +fn (mut g FlatGen) const_emission_order_owned() []string { + if !g.scope_parallel_workers { + return g.const_emission_order() + } + scope := cgen_worker_scope_begin(true) + scoped_names := g.const_emission_order() + cgen_worker_scope_leave(scope) + names := clone_cgen_string_list(scoped_names) + cgen_worker_scope_free(scope) + return names +} + +fn (mut g FlatGen) const_emission_order() []string { + mut emitted := map[string]bool{} + mut deferred := []string{} + mut names := g.const_init_order.clone() + for name, _ in g.const_vals { + if g.is_const_alias_name(name) || name in names { + continue + } + names << name + } + names = g.ordered_const_init_names(names) + mut result := []string{cap: names.len} + for name in names { + val_id := g.const_vals[name] or { continue } + if g.is_const_alias_name(name) { + continue + } + if int(val_id) < 0 || int(val_id) >= g.a.nodes.len { + continue + } + old_module := g.tc.cur_module + if name in g.const_modules { + g.tc.cur_module = g.const_modules[name] + } + deps := g.const_get_deps(val_id) + g.tc.cur_module = old_module + mut all_met := true + for dep in deps { + if dep !in emitted { + all_met = false + break + } + } + if !all_met { + deferred << name + } else { + result << name + emitted[name] = true + } + } + for _ in 0 .. 20 { + if deferred.len == 0 { + break + } + mut remaining := []string{} + for name in deferred { + val_id := g.const_vals[name] + deps := g.const_get_deps(val_id) + mut all_met := true + for dep in deps { + if dep !in emitted { + all_met = false + break + } + } + if all_met { + result << name + emitted[name] = true + } else { + remaining << name + } + } + deferred = remaining.clone() + } + result << deferred + return result +} + +fn (g &FlatGen) ordered_const_init_names(names []string) []string { + mut names_by_module := map[string][]string{} + mut module_order := []string{} + for name in names { + mod := g.const_modules[name] or { '' } + if mod !in names_by_module { + names_by_module[mod] = []string{} + module_order << mod + } + names_by_module[mod] << name + } + mut result := []string{} + mut visiting := map[string]bool{} + mut visited := map[string]bool{} + for mod in module_order { + g.visit_const_init_module(mod, names_by_module, mut visiting, mut visited, mut result) + } + return result +} + +fn (g &FlatGen) visit_const_init_module(mod string, names_by_module map[string][]string, mut visiting map[string]bool, mut visited map[string]bool, mut result []string) { + if mod in visited || mod in visiting { + return + } + visiting[mod] = true + for dep in g.module_imports[mod] or { []string{} } { + dep_module := if dep in names_by_module || dep in g.module_imports { + dep + } else { + startup_module_key(dep) + } + if dep_module in names_by_module { + g.visit_const_init_module(dep_module, names_by_module, mut visiting, mut visited, mut + result) + } + } + visiting.delete(mod) + visited[mod] = true + if module_names := names_by_module[mod] { + result << module_names + } +} + +fn startup_module_key(mod string) string { + if mod.contains('.') { + return mod.all_after_last('.') + } + return mod +} + +fn (mut g FlatGen) is_const_expr(id flat.NodeId) bool { + mut visiting := map[int]bool{} + return g.is_const_expr_inner(id, mut visiting) +} + +fn (mut g FlatGen) is_const_expr_inner(id flat.NodeId, mut visiting map[int]bool) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(id)] + return match node.kind { + .int_literal, .float_literal, .bool_literal, .char_literal, .string_literal, .enum_val, + .sizeof_expr, .offsetof_expr { + true + } + .prefix { + if node.op == .amp { + false + } else { + g.is_const_expr_inner(g.a.child(&node, 0), mut visiting) + } + } + .infix { + // Power lowers to a helper or pow() call, neither of which is a valid C + // constant expression. Initialize power-containing consts in _vinit. + if node.op == .power { + false + } else { + g.is_const_expr_inner(g.a.child(&node, 0), mut visiting) + && g.is_const_expr_inner(g.a.child(&node, 1), mut visiting) + } + } + .paren { + g.is_const_expr_inner(g.a.child(&node, 0), mut visiting) + } + .cast_expr { + g.is_const_expr_inner(g.a.child(&node, 0), mut visiting) + } + .ident { + g.const_ref_is_static(node.value, mut visiting) + } + .selector { + const_name := g.const_ref_name_from_node(node) + if const_name.len > 0 { + g.const_ref_is_static(const_name, mut visiting) + } else if node.children_count > 0 { + base := g.a.child_node(&node, 0) + base.kind == .ident && base.value == 'C' + } else { + false + } + } + .array_literal { + mut all_const := true + for ci in 0 .. node.children_count { + if !g.is_const_expr_inner(g.a.child(&node, ci), mut visiting) { + all_const = false + break + } + } + all_const + } + .struct_init { + if g.struct_needs_default_init(node.value) { + return false + } + mut all_const := true + for ci in 0 .. node.children_count { + child := g.a.child_node(&node, ci) + if child.kind == .field_init { + if ftyp := g.struct_field_type(node.value, child.value) { + if ftyp is types.Array || ftyp is types.Map { + all_const = false + break + } + } + } + if child.children_count > 0 + && !g.is_const_expr_inner(g.a.child(child, 0), mut visiting) { + all_const = false + break + } + } + all_const + } + else { + false + } + } +} + +fn (mut g FlatGen) const_ref_is_static(name string, mut visiting map[int]bool) bool { + const_name := g.const_ref_name(name) + if const_name.len == 0 { + return false + } + val_id := g.const_vals[const_name] or { return false } + idx := int(val_id) + if visiting[idx] { + return false + } + visiting[idx] = true + is_static := g.is_const_expr_inner(val_id, mut visiting) + visiting.delete(idx) + return is_static +} + +fn (g &FlatGen) is_string_plus_call(node flat.Node) bool { + if node.kind != .call || node.children_count != 3 { + return false + } + callee := g.a.child_node(&node, 0) + return callee.kind == .ident && callee.value == 'string__plus' +} + +fn (g &FlatGen) string_plus_call_is_nested(_id flat.NodeId, node flat.Node) bool { + if !g.is_string_plus_call(node) { + return false + } + lhs := g.a.child_node(&node, 1) + rhs := g.a.child_node(&node, 2) + return g.is_string_plus_call(lhs) || g.is_string_plus_call(rhs) +} + +fn (g &FlatGen) collect_string_plus_parts(id flat.NodeId, mut parts []flat.NodeId) { + node := g.a.nodes[int(id)] + if g.is_string_plus_call(node) { + g.collect_string_plus_parts(g.a.child(&node, 1), mut parts) + g.collect_string_plus_parts(g.a.child(&node, 2), mut parts) + return + } + parts << id +} + +// Nested string concatenation owns each intermediate result. Emit the chain as +// ordered statements and release every superseded accumulator. +fn (mut g FlatGen) gen_owned_string_plus_chain(id flat.NodeId) { + mut parts := []flat.NodeId{} + g.collect_string_plus_parts(id, mut parts) + if parts.len < 3 { + g.gen_call(id, g.a.nodes[int(id)]) + return + } + tmp := g.tmp_count + g.tmp_count++ + g.write('({') + for i, part in parts { + g.write(' string __str_plus_part_${tmp}_${i} = ') + g.gen_expr_as_string(part) + g.write(';') + } + g.write(' string __str_plus_acc_${tmp}_1 = string__plus(__str_plus_part_${tmp}_0, __str_plus_part_${tmp}_1);') + mut previous := '__str_plus_acc_${tmp}_1' + for i := 2; i < parts.len; i++ { + next := '__str_plus_acc_${tmp}_${i}' + g.write(' string ${next} = string__plus(${previous}, __str_plus_part_${tmp}_${i});') + g.write(' string__free(&${previous});') + previous = next + } + g.write(' ${previous}; })') +} + +fn (g &FlatGen) is_runtime_assignable(id flat.NodeId) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(id)] + return match node.kind { + .string_literal, .string_interp { + true + } + .call { + g.is_runtime_assignable_call(&node) + } + .ident { + true + } + .or_expr { + true + } + .infix { + if node.children_count >= 2 { + lhs_type := g.tc.resolve_type(g.a.child(&node, 0)) + rhs_type := g.tc.resolve_type(g.a.child(&node, 1)) + lhs_type is types.String || rhs_type is types.String + } else { + false + } + } + .cast_expr, .prefix, .struct_init, .map_init { + true + } + else { + false + } + } +} + +fn (g &FlatGen) is_runtime_assignable_call(node &flat.Node) bool { + if node.children_count == 0 { + return false + } + callee_id := g.a.child(node, 0) + if int(callee_id) < 0 { + return false + } + callee := g.a.nodes[int(callee_id)] + return callee.kind == .ident || callee.kind == .selector +} + +// gen_small_int_arith_operand_truncated emits a comparison operand wrapped in +// a cast to its own sub-int type when it is an arithmetic expression whose C +// evaluation would be integer-promoted (u8/u16/i8/i16). Returns false when the +// operand does not need truncation (caller emits it normally). +fn (mut g FlatGen) gen_small_int_arith_operand_truncated(id flat.NodeId, node flat.Node, typ types.Type) bool { + mut inner := node + for inner.kind == .paren && inner.children_count > 0 { + inner = g.a.nodes[int(g.a.child(&inner, 0))] + } + if inner.kind != .infix { + return false + } + if inner.op !in [.plus, .minus, .mul, .left_shift] { + return false + } + mut ct := g.value_c_type(typ) + if ct !in ['u8', 'u16', 'i8', 'i16'] { + // The annotated infix type is often widened; fall back to the operand + // types (`u8 + u8` must wrap at 8 bits even if annotated as int). + if inner.children_count < 2 { + return false + } + lct := g.value_c_type(g.usable_expr_type(g.a.child(&inner, 0))) + if lct !in ['u8', 'u16', 'i8', 'i16'] { + return false + } + rct := g.value_c_type(g.usable_expr_type(g.a.child(&inner, 1))) + if rct != lct { + return false + } + ct = lct + } + g.write('(${ct})(') + g.gen_expr(id) + g.write(')') + return true +} + +// shift_needs_64bit_widening reports whether `lhs << rhs` has an int-literal +// lhs and a constant shift count that overflows C's 32-bit `int` arithmetic. +fn (g &FlatGen) shift_needs_64bit_widening(node &flat.Node) bool { + if node.children_count < 2 { + return false + } + lhs := g.a.child_node(node, 0) + if lhs.kind != .int_literal { + return false + } + rhs_value := g.shift_count_const_value(g.a.child(node, 1), []string{}) or { return false } + return rhs_value >= 31 && rhs_value < 64 +} + +fn (g &FlatGen) shift_count_const_value(id flat.NodeId, seen []string) ?int { + if !g.valid_node_id(id) { + return none + } + node := g.a.nodes[int(id)] + match node.kind { + .int_literal { + return g.tc.const_int_value(node.value, seen) + } + .ident, .selector { + name := g.const_ref_name_from_node(node) + if name.len == 0 || name in seen { + return none + } + module_name := g.const_modules[name] or { g.tc.cur_module } + return g.tc.const_int_value_in_module(name, module_name, seen) + } + .paren { + if node.children_count > 0 { + return g.shift_count_const_value(g.a.child(&node, 0), seen) + } + } + .prefix { + if node.children_count == 0 { + return none + } + value := g.shift_count_const_value(g.a.child(&node, 0), seen) or { return none } + return match node.op { + .plus { value } + .minus { -value } + .bit_not { ~value } + else { none } + } + } + .infix { + if node.children_count < 2 { + return none + } + left := g.shift_count_const_value(g.a.child(&node, 0), seen) or { return none } + right := g.shift_count_const_value(g.a.child(&node, 1), seen) or { return none } + if node.op in [.div, .mod] && right == 0 { + return none + } + return match node.op { + .plus { left + right } + .minus { left - right } + .mul { left * right } + .div { left / right } + .mod { left % right } + .pipe { left | right } + .xor { left ^ right } + .amp { left & right } + else { none } + } + } + else {} + } + + return none +} + +// gen_prefix_op_operand writes a prefix operator and its operand, adding +// parentheses when the operand is a binary expression: `!(a && b)` — without +// them the `!` would bind to the first operand only. +@[direct_array_access] +fn (mut g FlatGen) gen_prefix_op_operand(op flat.Op, child_id flat.NodeId) { + g.write(g.op_str(op)) + child := g.a.nodes[int(child_id)] + needs_paren := child.kind in [.infix, .in_expr] + if needs_paren { + g.write('(') + } + g.gen_expr(child_id) + if needs_paren { + g.write(')') + } +} + +// gen_unsigned_right_shift emits `>>>`: a logical shift that reinterprets the +// lhs bit pattern as the same-width unsigned type, then casts the result back, +// so sign bits are shifted out instead of extended. Shift counts >= the type +// width (UB in C, wrapped on ARM) yield 0, matching V semantics. +fn (mut g FlatGen) gen_unsigned_right_shift(lhs_id flat.NodeId, rhs_id flat.NodeId, lhs_type types.Type) { + g.gen_unsigned_right_shift_from_text(g.expr_to_string(lhs_id), rhs_id, lhs_type) +} + +fn (mut g FlatGen) gen_guarded_shift(lhs_id flat.NodeId, rhs_id flat.NodeId, lhs_type types.Type, op flat.Op) { + g.gen_guarded_shift_from_text(g.expr_to_string(lhs_id), rhs_id, lhs_type, op) +} + +// gen_unsigned_right_shift_from_text is gen_unsigned_right_shift with the lhs +// already rendered as a C expression (used by `>>>=` to shift through a +// pointer temp so the lvalue is evaluated exactly once). +// unsigned_shift_parts maps an operand's C type to the unsigned counterpart +// used for `>>>` logical shifts and its bit-width text. isize/usize lower to +// ptrdiff_t/size_t in C and are pointer-width, so their unsigned view and bit +// width come from size_t, not a fixed 64. +fn unsigned_shift_parts(ct string) (string, string) { + return match ct { + 'i8', 'u8' { 'u8', '8' } + 'i16', 'u16' { 'u16', '16' } + 'int', 'i32', 'u32' { 'u32', '32' } + 'i64', 'u64' { 'u64', '64' } + 'isize', 'usize', 'ptrdiff_t', 'size_t' { 'size_t', '(sizeof(size_t) * 8)' } + else { 'u64', '64' } + } +} + +fn unsigned_shift_unalias_type(typ types.Type) types.Type { + if typ is types.Alias { + return unsigned_shift_unalias_type(typ.base_type) + } + return typ +} + +fn (mut g FlatGen) unsigned_shift_type_parts(typ types.Type) (string, string) { + return unsigned_shift_parts(g.value_c_type(unsigned_shift_unalias_type(typ))) +} + +fn (mut g FlatGen) gen_unsigned_right_shift_from_text(lhs_text string, rhs_id flat.NodeId, lhs_type types.Type) { + g.gen_guarded_shift_from_text(lhs_text, rhs_id, lhs_type, .right_shift_unsigned) +} + +fn (mut g FlatGen) gen_guarded_shift_from_text(lhs_text string, rhs_id flat.NodeId, lhs_type types.Type, op flat.Op) { + ut, bits := g.unsigned_shift_type_parts(lhs_type) + value_type := g.value_c_type(unsigned_shift_unalias_type(lhs_type)) + result_type := if op == .right_shift_unsigned { ut } else { value_type } + lhs_type_name := if op == .right_shift_unsigned { ut } else { value_type } + op_text := if op == .left_shift { '<<' } else { '>>' } + if count := g.shift_count_const_value(rhs_id, []string{}) { + if width := fixed_integer_c_type_width(value_type) { + if count >= 0 && count < width { + g.write('(${result_type})(((${lhs_type_name})(${lhs_text})) ${op_text} (') + g.gen_expr(rhs_id) + g.write('))') + return + } + } + } + lhs_tmp := g.tmp_name() + rhs_tmp := g.tmp_name() + g.write('({ ${lhs_type_name} ${lhs_tmp} = (${lhs_type_name})(${lhs_text}); u64 ${rhs_tmp} = (u64)(') + g.gen_expr(rhs_id) + g.write('); ${rhs_tmp} >= ${bits} ? (${result_type})0 : (${result_type})(${lhs_tmp} ${op_text} ${rhs_tmp}); })') +} + +fn fixed_integer_c_type_width(c_type string) ?int { + return match c_type { + 'i8', 'u8' { 8 } + 'i16', 'u16' { 16 } + 'int', 'i32', 'u32' { 32 } + 'i64', 'u64' { 64 } + else { none } + } +} + +fn integer_sign_kind(typ types.Type) int { + if typ is types.Alias { + return integer_sign_kind(typ.base_type) + } + if typ is types.Primitive && typ.props.has(.integer) { + return if typ.props.has(.unsigned) { 1 } else { -1 } + } + return 0 +} + +struct CheckedIntegerBounds { + is_unsigned bool + min_value string + max_value string +} + +fn checked_integer_bounds(typ types.Type) ?CheckedIntegerBounds { + if typ is types.Alias { + return checked_integer_bounds(typ.base_type) + } + if typ is types.Rune { + return CheckedIntegerBounds{ + is_unsigned: true + max_value: 'UINT32_MAX' + } + } + if typ is types.ISize { + return CheckedIntegerBounds{ + min_value: '(-((ptrdiff_t)(((size_t)-1) >> 1)) - 1)' + max_value: '((ptrdiff_t)(((size_t)-1) >> 1))' + } + } + if typ is types.USize { + return CheckedIntegerBounds{ + is_unsigned: true + max_value: '((size_t)-1)' + } + } + if typ !is types.Primitive { + return none + } + primitive := typ as types.Primitive + if !primitive.props.has(.integer) { + return none + } + bits := if primitive.size == 0 { 32 } else { int(primitive.size) } + if primitive.props.has(.unsigned) { + max_value := match bits { + 8 { 'UINT8_MAX' } + 16 { 'UINT16_MAX' } + 32 { 'UINT32_MAX' } + 64 { 'UINT64_MAX' } + else { return none } + } + return CheckedIntegerBounds{ + is_unsigned: true + max_value: max_value + } + } + min_value, max_value := match bits { + 8 { 'INT8_MIN', 'INT8_MAX' } + 16 { 'INT16_MIN', 'INT16_MAX' } + 32 { 'INT32_MIN', 'INT32_MAX' } + 64 { 'INT64_MIN', 'INT64_MAX' } + else { return none } + } + return CheckedIntegerBounds{ + min_value: min_value + max_value: max_value + } +} + +fn (mut g FlatGen) gen_checked_integer_infix(node flat.Node, lhs_id flat.NodeId, rhs_id flat.NodeId, lhs_type types.Type) bool { + if !g.check_overflow || g.ignore_overflow || node.op !in [.plus, .minus, .mul] { + return false + } + bounds := checked_integer_bounds(lhs_type) or { return false } + c_type := g.value_c_type(lhs_type) + if c_type.len == 0 { + return false + } + lhs_tmp := g.tmp_name() + rhs_tmp := g.tmp_name() + result_tmp := g.tmp_name() + g.write('({ ${c_type} ${lhs_tmp} = (${c_type})(') + g.gen_expr(lhs_id) + g.write('); ${c_type} ${rhs_tmp} = (${c_type})(') + g.gen_expr(rhs_id) + g.write('); if (') + if bounds.is_unsigned { + match node.op { + .plus { g.write('${lhs_tmp} > (${bounds.max_value}) - ${rhs_tmp}') } + .minus { g.write('${lhs_tmp} < ${rhs_tmp}') } + .mul { g.write('${rhs_tmp} != 0 && ${lhs_tmp} > (${bounds.max_value}) / ${rhs_tmp}') } + else {} + } + } else { + match node.op { + .plus { + g.write('(${rhs_tmp} > 0 && ${lhs_tmp} > (${bounds.max_value}) - ${rhs_tmp}) || (${rhs_tmp} < 0 && ${lhs_tmp} < (${bounds.min_value}) - ${rhs_tmp})') + } + .minus { + g.write('(${rhs_tmp} < 0 && ${lhs_tmp} > (${bounds.max_value}) + ${rhs_tmp}) || (${rhs_tmp} > 0 && ${lhs_tmp} < (${bounds.min_value}) + ${rhs_tmp})') + } + .mul { + g.write('(${lhs_tmp} > 0 ? (${rhs_tmp} > 0 ? ${lhs_tmp} > (${bounds.max_value}) / ${rhs_tmp} : ${rhs_tmp} < (${bounds.min_value}) / ${lhs_tmp}) : (${lhs_tmp} < 0 ? (${rhs_tmp} > 0 ? ${lhs_tmp} < (${bounds.min_value}) / ${rhs_tmp} : (${rhs_tmp} != 0 && ${lhs_tmp} < (${bounds.max_value}) / ${rhs_tmp})) : false))') + } + else {} + } + } + g.write(') v_panic(_S("integer overflow")); ${c_type} ${result_tmp} = (${c_type})(${lhs_tmp} ${g.op_str(node.op)} ${rhs_tmp}); ${result_tmp}; })') + return true +} + +fn (mut g FlatGen) gen_mixed_sign_integer_comparison(lhs_id flat.NodeId, rhs_id flat.NodeId, lhs_type types.Type, rhs_type types.Type, op flat.Op) bool { + lhs_sign := integer_sign_kind(lhs_type) + rhs_sign := integer_sign_kind(rhs_type) + if lhs_sign == 0 || rhs_sign == 0 || lhs_sign == rhs_sign { + return false + } + // Untyped positive integer literals inherit a narrow semantic `int` type, but + // C gives large hexadecimal literals an unsigned type. Preserve that constant + // behavior instead of narrowing `0xffff_ffff_ffff_ffff` to signed `int` first. + if (lhs_sign < 0 && g.a.nodes[int(lhs_id)].kind == .int_literal) + || (rhs_sign < 0 && g.a.nodes[int(rhs_id)].kind == .int_literal) { + return false + } + lhs_ct := g.value_c_type(unsigned_shift_unalias_type(lhs_type)) + rhs_ct := g.value_c_type(unsigned_shift_unalias_type(rhs_type)) + lhs_tmp := g.tmp_name() + rhs_tmp := g.tmp_name() + g.write('({ ${lhs_ct} ${lhs_tmp} = (${lhs_ct})(') + g.gen_expr(lhs_id) + g.write('); ${rhs_ct} ${rhs_tmp} = (${rhs_ct})(') + g.gen_expr(rhs_id) + g.write('); ') + if lhs_sign < 0 { + negative_result := if op in [.ne, .lt, .le] { '1' } else { '0' } + g.write('${lhs_tmp} < 0 ? ${negative_result} : ') + } else { + negative_result := if op in [.ne, .gt, .ge] { '1' } else { '0' } + g.write('${rhs_tmp} < 0 ? ${negative_result} : ') + } + g.write('((u64)(${lhs_tmp}) ${g.op_str(op)} (u64)(${rhs_tmp})); })') + return true +} + +fn (mut g FlatGen) gen_power_expr(lhs_id flat.NodeId, rhs_id flat.NodeId, result_type types.Type) { + g.gen_power_expr_from_lhs_text(g.expr_to_string(lhs_id), rhs_id, result_type) +} + +fn (mut g FlatGen) gen_power_expr_from_lhs_text(lhs string, rhs_id flat.NodeId, result_type types.Type) { + rhs := g.expr_to_string(rhs_id) + g.write(g.power_expr_string(lhs, rhs, result_type)) +} + +fn (mut g FlatGen) power_expr_string(lhs string, rhs string, result_type types.Type) string { + clean := default_init_unalias_type(result_type) + result_ct := g.value_c_type(result_type) + if clean.is_float() { + return '((${result_ct})pow((double)(${lhs}), (double)(${rhs})))' + } + helper := if power_type_is_unsigned(clean) { '__v_pow_u64' } else { '__v_pow_i64' } + base_ct := if power_type_is_unsigned(clean) { 'u64' } else { 'i64' } + return '((${result_ct})${helper}((${base_ct})(${lhs}), (i64)(${rhs})))' +} + +fn power_type_is_unsigned(typ types.Type) bool { + if typ is types.Primitive { + return typ.props.has(.unsigned) + } + return typ is types.USize +} + +fn (g &FlatGen) op_str(op flat.Op) string { + return match op { + .plus { '+' } + .minus { '-' } + .mul { '*' } + .power { '**' } + .div { '/' } + .mod { '%' } + .eq { '==' } + .ne { '!=' } + .lt { '<' } + .gt { '>' } + .le { '<=' } + .ge { '>=' } + .amp { '&' } + .pipe { '|' } + .xor { '^' } + .left_shift { '<<' } + .right_shift { '>>' } + .right_shift_unsigned { '>>' } + .logical_and { '&&' } + .logical_or { '||' } + .not { '!' } + .bit_not { '~' } + .assign { '=' } + .plus_assign { '+=' } + .minus_assign { '-=' } + .mul_assign { '*=' } + .power_assign { '**=' } + .div_assign { '/=' } + .mod_assign { '%=' } + .amp_assign { '&=' } + .pipe_assign { '|=' } + .xor_assign { '^=' } + .left_shift_assign { '<<=' } + .right_shift_assign { '>>=' } + .right_shift_unsigned_assign { '>>=' } + .inc { '++' } + .dec { '--' } + .dot { '.' } + .arrow { '->' } + .none { '' } + .gated_index { '' } + } +} + +fn (mut g FlatGen) write(s string) { + if g.line_start { + g.write_indent() + } + if s.len == 0 { + if g.indent > 0 { + g.line_start = false + } + return + } + g.sb.write_string(s) + g.line_start = s[s.len - 1] == `\n` +} + +fn (mut g FlatGen) writeln(s string) { + if s.len > 0 { + if g.line_start { + g.write_indent() + } + g.sb.write_string(s) + } + g.sb.write_string('\n') + g.line_start = true +} + +fn (mut g FlatGen) write_indent() { + for _ in 0 .. g.indent { + g.sb.write_string('\t') + } +} diff --git a/vlib/v3/gen/fastc/coverage.v b/vlib/v3/gen/fastc/coverage.v new file mode 100644 index 00000000000000..ca16f0afc8f95e --- /dev/null +++ b/vlib/v3/gen/fastc/coverage.v @@ -0,0 +1,131 @@ +module fastc + +import hash +import os +import time +import v3.flat + +@[heap] +struct CoverageInfo { + path string + fhash string +mut: + points []int + counters []int + counter_by_line map[int]int +} + +// set_coverage enables V-compatible line coverage output. +pub fn (mut g FlatGen) set_coverage(dir string, build_options string) { + g.coverage_dir = dir + g.coverage_build_options = build_options +} + +fn (mut g FlatGen) write_coverage_point(node flat.Node) { + if g.coverage_dir.len == 0 || g.cur_fn_name.len == 0 + || node.kind !in [.expr_stmt, .assign, .decl_assign, .selector_assign, .index_assign, .return_stmt, .break_stmt, .continue_stmt, .defer_stmt, .assert_stmt, .goto_stmt] { + return + } + position := g.a.source_position(node.pos) or { return } + path := os.real_path(position.filename) + line := position.line + mut info := g.coverage_files[path] or { + fhash := hash.sum64_string('${g.coverage_build_options}:${path}', 32).hex_full() + created := &CoverageInfo{ + path: path + fhash: fhash + points: [] + counter_by_line: map[int]int{} + } + g.coverage_files[path] = created + created + } + mut counter := info.counter_by_line[line] + if line !in info.counter_by_line { + counter = g.coverage_counter_count + info.counter_by_line[line] = counter + info.points << line + info.counters << counter + g.coverage_counter_count++ + } + g.writeln('_v3_cov[${counter}]++;') +} + +fn (mut g FlatGen) gen_coverage_registration() { + if g.coverage_dir.len > 0 { + g.writeln('atexit(v3_write_coverage_stats);') + } +} + +fn coverage_json_escape(value string) string { + return json_string_content_escape(value) +} + +fn (mut g FlatGen) write_coverage_metadata() { + if g.coverage_dir.len == 0 { + return + } + os.mkdir_all(g.coverage_dir) or { return } + meta_dir := os.join_path_single(g.coverage_dir, 'meta') + os.mkdir_all(meta_dir) or { return } + for _, info in g.coverage_files { + path := os.join_path_single(meta_dir, '${info.fhash}.json') + mut file := os.create(path) or { continue } + file.writeln('{') or { continue } + file.writeln(' "file": "${coverage_json_escape(info.path)}", "fhash": "${info.fhash}",') or { + continue + } + file.writeln(' "v_version": "V3 ${@VHASH}",') or { continue } + file.writeln(' "build_options": "${coverage_json_escape(g.coverage_build_options)}",') or { + continue + } + file.writeln(' "npoints": ${info.points.len},') or { continue } + file.write_string(' "points": [ ') or { continue } + for index, point in info.points { + file.write_string(point.str()) or { continue } + if index + 1 < info.points.len { + file.write_string(',') or { continue } + } + } + file.writeln(' ]') or { continue } + file.writeln('}') or { continue } + file.close() + } +} + +fn (mut g FlatGen) emit_coverage_support() { + if g.coverage_dir.len == 0 { + return + } + g.write_coverage_metadata() + counter_count := if g.coverage_counter_count > 0 { g.coverage_counter_count } else { 1 } + compile_tag := '${os.getpid()}_${time.now().unix_micro()}' + g.writeln('static unsigned long long _v3_cov[${counter_count}];') + g.writeln('static void v3_write_coverage_stats(void) {') + g.writeln('\tchar cov_filename[4096];') + g.writeln('\tlong long cov_secs = 0;') + g.writeln('\tlong cov_nsecs = 0;') + g.writeln('#if defined(_WIN32)') + g.writeln('\tcov_secs = (long long)(GetTickCount64() / 1000);') + g.writeln('\tcov_nsecs = (long)((GetTickCount64() % 1000) * 1000000);') + g.writeln('#else') + g.writeln('\tstruct timespec cov_ts;') + g.writeln('\tclock_gettime(CLOCK_MONOTONIC, &cov_ts);') + g.writeln('\tcov_secs = (long long)cov_ts.tv_sec;') + g.writeln('\tcov_nsecs = cov_ts.tv_nsec;') + g.writeln('#endif') + g.writeln('\tsnprintf(cov_filename, sizeof(cov_filename), "%s/vcounters_v3_${compile_tag}.%lld.%09ld.csv", "${c_escape(g.coverage_dir)}", cov_secs, cov_nsecs);') + g.writeln('\tFILE* cov_file = fopen(cov_filename, "wb+");') + g.writeln('\tif (cov_file == NULL) return;') + g.writeln('\tfprintf(cov_file, "# path: %s\\n", "${c_escape(g.coverage_dir)}");') + g.writeln('\tfprintf(cov_file, "# build_options: %s\\n", "${c_escape(g.coverage_build_options)}");') + g.writeln('\tfprintf(cov_file, "meta,point,hits\\n");') + for _, info in g.coverage_files { + for point_index, counter in info.counters { + g.writeln('\tif (_v3_cov[${counter}] != 0) fprintf(cov_file, "${info.fhash},${point_index},%llu\\n", _v3_cov[${counter}]);') + } + } + g.writeln('\tfclose(cov_file);') + g.writeln('}') + g.writeln('') +} diff --git a/vlib/v3/gen/fastc/fastc.v b/vlib/v3/gen/fastc/fastc.v index 5449f15fdacc19..4f4dc1eb1b2dde 100644 --- a/vlib/v3/gen/fastc/fastc.v +++ b/vlib/v3/gen/fastc/fastc.v @@ -5,6 +5,9 @@ import v3.pref import v3.scanner import v3.token +// This file is the scanner-direct optimization. The complete checked backend is +// FlatGen, split across the other files in this module. + const c_preamble = r'#include #include #include @@ -62,7 +65,7 @@ mut: // generate scans V source and emits C as each declaration and statement is consumed. It does // not construct a flat AST or invoke semantic type checking. Unsupported syntax is returned as -// an error so the driver can retry the source with the normal C backend. +// an error so the driver can promote the source to fastc's complete checked lane. pub fn generate(source string, path string, prefs &pref.Preferences) !string { mut file_set := token.FileSet.new() mut file := file_set.add_file(path, source.len) diff --git a/vlib/v3/gen/fastc/fn.v b/vlib/v3/gen/fastc/fn.v new file mode 100644 index 00000000000000..38fe27f9479f55 --- /dev/null +++ b/vlib/v3/gen/fastc/fn.v @@ -0,0 +1,16507 @@ +module fastc + +import os +import strings +import v3.flat +import v3.gen.fastc.naming +import v3.types + +struct TestHarnessFn { + node_id flat.NodeId + name string + c_name string + ret types.Type + file string + failure_line int +} + +struct TestHarnessHooks { +mut: + testsuite_begin string + testsuite_end string + before_each string + after_each string +} + +struct TopLevelStmt { + id flat.NodeId + file string + module string +} + +struct GenericMethodCandidate { + name string + ret types.Type + params []types.Type +} + +struct SpawnPackedArg { + field_ct string + assign_expr string + call_expr string + copy_array bool +} + +struct SpawnClosureCapture { + global_cname string + field_ct string + copy_array bool +} + +struct GuardedAnonSelfCall { + callee string + callee_ct string +} + +// FlatFnGenItem represents one top-level function selected for C emission. +struct FlatFnGenItem { + node_id flat.NodeId + file string + module string + c_name string + is_program_specialization bool + is_program bool + direct_array_access bool + ignore_overflow bool +mut: + cost int + skip_prelude_scan bool +} + +struct FlatFnGenCandidate { + preferred_name string + item FlatFnGenItem +} + +struct DirectArrayAccessFns { + node_ids map[int]bool + source_positions map[u64]bool +} + +@[inline] +fn (attrs &DirectArrayAccessFns) contains(node_idx int, node flat.Node) bool { + if node.pos.is_valid() { + return attrs.source_positions[flat_fn_source_position_key(node)] + } + return attrs.node_ids[node_idx] +} + +@[inline] +fn flat_fn_source_position_key(node flat.Node) u64 { + return (u64(node.pos.id) << 32) | u64(node.pos.offset) +} + +// gen_fns emits fns output for c. +fn (mut g FlatGen) gen_fns() { + g.gen_fn_items(g.ensure_fn_gen_items()) +} + +fn (mut g FlatGen) gen_test_failure_global() { + if g.test_files.len > 0 { + if g.cache_split { + g.writeln('/* V3CACHE_MODULE main */') + } + g.writeln('#include ') + g.writeln('static int __v3_test_failures = 0;') + g.writeln('static jmp_buf __v3_test_jump_buffer;') + g.writeln('static int __v3_test_jump_active = 0;') + g.writeln('void __v3_test_fail_transfer(void) {') + g.writeln('\t__v3_test_failures++;') + g.writeln('\tif (__v3_test_jump_active) { longjmp(__v3_test_jump_buffer, 1); }') + g.writeln('\texit(1);') + g.writeln('}') + if g.show_test_stats { + g.writeln('static int __v3_test_assertions = 0;') + } + } +} + +fn (mut g FlatGen) ensure_fn_gen_items() []FlatFnGenItem { + if g.fn_gen_items.len == 0 { + g.fn_gen_items = g.collect_fn_gen_items() + } + return g.fn_gen_items +} + +// collect_fn_gen_items updates collect fn gen items state for c. +fn (mut g FlatGen) collect_fn_gen_items() []FlatFnGenItem { + direct_array_access_fns := g.direct_array_access_fns() + ignore_overflow_fns := g.function_attribute_fns('ignore_overflow') + program_modules := g.cache_program_module_names() + // Defer cost/prep until after preferred-file and emission filtering. When + // the parallel path asks for prep, that walk also collects C-extern refs. + prep := g.want_parallel_prep + mut candidates := if prep && g.scope_parallel_workers && par_cgen_prep_enabled() { + g.collect_fn_gen_candidates_parallel(direct_array_access_fns, ignore_overflow_fns, + program_modules) + } else { + nodes := g.top_level_nodes() + g.collect_fn_gen_candidates_range(nodes, 0, nodes.len, '', '', direct_array_access_fns, + ignore_overflow_fns, program_modules) + } + mut preferred_fns := map[string]int{} + mut ranks := map[string]int{} + mut program_specializations := map[string]bool{} + mut preferred_program_specializations := map[string]bool{} + for candidate in candidates { + if candidate.item.is_program_specialization { + program_specializations[candidate.preferred_name] = true + } + rank := c_backend_fn_file_rank(candidate.item.file) + preferred_is_program_specialization := program_specializations[candidate.preferred_name] + if candidate.preferred_name !in preferred_fns + || rank > ranks[candidate.preferred_name] + || (rank == ranks[candidate.preferred_name] && preferred_is_program_specialization + && !preferred_program_specializations[candidate.preferred_name]) { + preferred_fns[candidate.preferred_name] = int(candidate.item.node_id) + ranks[candidate.preferred_name] = rank + preferred_program_specializations[candidate.preferred_name] = preferred_is_program_specialization + } + } + mut items := []FlatFnGenItem{cap: candidates.len} + mut prep_stack := []flat.NodeId{cap: 256} + mut prep_type_text_cache := map[string]bool{} + // In parallel-prep mode the exact-cost pass (or its serial fallback) also + // computes costs and fn-ptr preseeds after selection; see refine_fn_item_costs. + par_prep := prep && par_cgen_prep_enabled() + if prep { + // The prep walk no longer collects per-body C-extern refs; the exact-cost + // pass that follows it does (or its serial fallback). + g.prep_externs_pending = true + g.prep_costs_pending = par_prep + g.prep_typ_text_cache = &PrepTypTextCache{} + g.preseed_type_seen = &PreseedTypeSeen{} + } + for candidate in candidates { + if preferred_idx := preferred_fns[candidate.preferred_name] { + if preferred_idx != int(candidate.item.node_id) { + continue + } + } + item := candidate.item + qfn := item.c_name + if g.emitted_fn_contains(qfn) { + continue + } + g.emitted_fns[qfn] = true + cost := if par_prep { + // The parallel exact-cost pass overwrites this value before cgen + // dispatch. Retain the O(1) source-span estimate meanwhile so that + // pass can split its own uneven function bodies by work, not count. + flat_fn_gen_item_cost(g.a, item.node_id) + } else if prep { + if item.file != g.tc.cur_file || item.module != g.tc.cur_module { + prep_type_text_cache.clear() + if !isnil(g.prep_typ_text_cache) { + g.prep_typ_text_cache.generation++ + } + } + g.tc.cur_file = item.file + g.tc.cur_module = item.module + g.fn_item_cost_and_prep(item.node_id, mut prep_stack, mut prep_type_text_cache) + } else { + flat_fn_gen_item_cost(g.a, item.node_id) + } + items << FlatFnGenItem{ + node_id: item.node_id + file: item.file + module: item.module + c_name: item.c_name + cost: cost + is_program_specialization: program_specializations[candidate.preferred_name] + is_program: item.is_program + direct_array_access: item.direct_array_access + ignore_overflow: item.ignore_overflow + } + } + items.sort(a.c_name < b.c_name) + return items +} + +fn (mut g FlatGen) collect_fn_gen_candidates_range(nodes []int, start int, end int, first_file string, first_module string, direct_array_access_fns DirectArrayAccessFns, ignore_overflow_fns DirectArrayAccessFns, program_modules map[string]bool) []FlatFnGenCandidate { + mut candidates := []FlatFnGenCandidate{cap: end - start} + mut cur_module := first_module + mut cur_file := first_file + for pos in start .. end { + i := nodes[pos] + node := g.a.nodes[i] + kind_id := node_kind_id(node) + if kind_id == 77 { + cur_file = node.value + g.tc.cur_file = cur_file + cur_module = '' + g.tc.cur_module = cur_module + continue + } + if kind_id == 73 { + cur_module = node.value + g.tc.cur_file = cur_file + g.tc.cur_module = cur_module + continue + } + if kind_id != 61 { + continue + } + is_specialization := g.a.specialized_fn_nodes[i] + item_module := g.a.specialized_fn_modules[i] or { cur_module } + item_file := g.a.specialized_fn_files[i] or { + if is_specialization { + g.tc.fn_type_files[node.value] or { cur_file } + } else { + cur_file + } + } + if g.program_body_only && !is_specialization && !g.cache_program_files[item_file] + && item_module !in ['', 'main'] { + continue + } + if g.incremental_fn_names.len > 0 { + qname := if item_module in ['', 'main', 'builtin'] { + node.value + } else { + '${item_module}.${node.value}' + } + if !g.incremental_fn_names[qname] && !g.incremental_fn_names[node.value] { + continue + } + } + qfn := g.qualified_fn_name_in_module_c(item_module, node.value) + is_program_specialization := g.is_program_specialization_fn_node_with_qfn(node, i, qfn) + if !g.should_emit_fn_node_in_module_known(node, item_module, item_file, qfn, + is_program_specialization) { + continue + } + preferred_name := g.fn_c_name_in_module(item_module, node.value) + candidates << FlatFnGenCandidate{ + preferred_name: preferred_name + item: FlatFnGenItem{ + node_id: flat.NodeId(i) + file: item_file + module: item_module + c_name: preferred_name + is_program_specialization: is_program_specialization + is_program: g.cache_program_files[item_file] + || program_modules[item_module] + direct_array_access: direct_array_access_fns.contains(i, node) + ignore_overflow: ignore_overflow_fns.contains(i, node) + } + } + } + return candidates +} + +fn (g &FlatGen) cache_program_module_names() map[string]bool { + mut modules := map[string]bool{} + if g.cache_program_files.len == 0 { + return modules + } + mut cur_file_is_program := false + for id in g.top_level_nodes() { + node := g.a.nodes[id] + if node.kind == .file { + cur_file_is_program = g.cache_program_files[node.value] + || g.cache_program_files[os.real_path(node.value)] + continue + } + if cur_file_is_program && node.kind == .module_decl { + modules[node.value] = true + } + } + return modules +} + +fn (g &FlatGen) direct_array_access_fns() DirectArrayAccessFns { + if g.force_bounds_checking { + return DirectArrayAccessFns{} + } + return g.function_attribute_fns('direct_array_access') +} + +fn (g &FlatGen) function_attribute_fns(attr_name string) DirectArrayAccessFns { + mut node_ids := map[int]bool{} + mut source_positions := map[u64]bool{} + for directive_idx in g.top_level_nodes() { + directive := g.a.nodes[directive_idx] + if directive.kind != .directive || !directive.value.starts_with('@attributes:') { + continue + } + mut has_attr := false + for raw_attr in directive.generic_params() { + if raw_attr.all_before(':').trim_space() == attr_name { + has_attr = true + break + } + } + if !has_attr { + continue + } + target_idx := directive.value['@attributes:'.len..].int() + if target_idx < 0 || target_idx >= g.a.nodes.len { + continue + } + target := g.a.nodes[target_idx] + // Generic templates can be replaced with an empty node after their + // concrete declarations are cloned. The directive keeps targeting the + // template id, and both nodes retain its source position. + node_ids[target_idx] = true + if target.pos.is_valid() { + source_positions[flat_fn_source_position_key(target)] = true + } + } + return DirectArrayAccessFns{ + node_ids: node_ids + source_positions: source_positions + } +} + +fn flat_fn_gen_item_cost(a &flat.FlatAst, node_id flat.NodeId) int { + idx := int(node_id) + if idx < 0 || idx >= a.nodes.len { + return 1 + } + node := a.nodes[idx] + // Source span is an O(1) and sufficiently accurate proxy for generated work. + // Walking every selected subtree just to balance chunks repeats the entire + // function-body traversal immediately before cgen does the real work. + span := node.pos.end - node.pos.offset + return if span > 0 { span + 64 } else { int(node.children_count) + 65 } +} + +fn exact_flat_fn_gen_item_cost(a &flat.FlatAst, node_id flat.NodeId, mut c_extern_refs map[string]bool, mut stack []flat.NodeId) (int, bool) { + mut cost := 0 + mut needs_prelude_scan := false + stack.clear() + stack << node_id + for stack.len > 0 { + id := stack.pop() + idx := int(id) + if idx < 0 || idx >= a.nodes.len { + continue + } + node := unsafe { &a.nodes[idx] } + cost += flat_cgen_node_cost(node.kind) + if node.kind == .lock_expr || node.kind == .label_stmt + || (node.kind == .defer_stmt && node.value == 'function') { + needs_prelude_scan = true + } + if node.kind == .selector && node.children_count > 0 && node.value.len > 0 { + base_id := a.children[node.children_start] + if int(base_id) >= 0 { + base := unsafe { &a.nodes[int(base_id)] } + if base.kind == .ident && base.value == 'C' { + raw_name := 'C.${node.value}' + raw_cfn := naming.c_name(raw_name) + c_extern_refs[raw_name] = true + c_extern_refs[raw_cfn] = true + c_extern_refs[c_winapi_wide_export_name(raw_cfn)] = true + } + } + } + for i in 0 .. node.children_count { + child_id := a.children[node.children_start + i] + if int(child_id) >= 0 { + stack << child_id + } + } + } + return cost, needs_prelude_scan +} + +@[inline] +fn flat_cgen_node_cost(kind flat.NodeKind) int { + return match kind { + .call, .struct_init { 8 } + .selector { 6 } + .assign, .decl_assign, .selector_assign, .index_assign { 5 } + .array_literal, .array_init, .map_init, .fn_literal, .lambda_expr, .string_interp { 4 } + .index, .if_expr, .match_stmt, .for_stmt, .for_in_stmt, .select_stmt { 3 } + .infix, .cast_expr, .as_expr, .or_expr, .return_stmt { 2 } + else { 1 } + } +} + +fn cache_fn_marker_key(file string, module_name string, name string) string { + mut hash := u64(1469598103934665603) + for part in [file, module_name, name] { + for byte in part.bytes() { + hash = (hash ^ u64(byte)) * u64(1099511628211) + } + hash = (hash ^ u64(0xff)) * u64(1099511628211) + } + return hash.hex() +} + +// gen_fn_items emits fn items output for c. +fn (mut g FlatGen) gen_fn_items(items []FlatFnGenItem) { + for item in items { + if int(item.node_id) < 0 || int(item.node_id) >= g.a.nodes.len { + continue + } + g.tc.cur_file = item.file + g.tc.cur_module = item.module + node := g.a.nodes[int(item.node_id)] + is_anon_fn := node.value.starts_with('__anon_fn_') || node.value.contains('.__anon_fn_') + is_program_fn := item.is_program_specialization + || g.is_program_specialization_fn_node(node, int(item.node_id), item.module) + || (is_anon_fn && item.module in ['', 'main']) || g.test_files[item.file] + || g.cache_program_files[item.file] || item.is_program + if node.is_mut && item.file.ends_with('.vh') && !is_program_fn { + continue + } + if g.cache_split { + // Generic templates are source-parsed even when their module object is + // cached. Program-specific concrete specializations are separated from + // both source-stable module objects and the frequently edited program + // translation unit, so the dev dylib can retain them across body edits. + module_name := if item.is_program_specialization && item.module !in ['', 'main'] { + '__v3_program_specializations' + } else if is_program_fn || item.module.len == 0 { + 'main' + } else { + item.module + } + g.writeln('/* V3CACHE_MODULE ${module_name} */') + g.writeln('/* V3CACHE_FN_BEGIN ${cache_fn_marker_key(item.file, item.module, node.value)} */') + } + old_direct_array_access := g.direct_array_access + g.direct_array_access = item.direct_array_access + old_ignore_overflow := g.ignore_overflow + g.ignore_overflow = item.ignore_overflow + old_cur_fn_is_specialized := g.cur_fn_is_specialized + g.cur_fn_is_specialized = g.a.specialized_fn_nodes[int(item.node_id)] + || g.is_program_specialization_fn_node(node, int(item.node_id), item.module) + g.gen_fn_in_module(item.node_id, node, item.module, item.skip_prelude_scan) + g.cur_fn_is_specialized = old_cur_fn_is_specialized + g.ignore_overflow = old_ignore_overflow + g.direct_array_access = old_direct_array_access + if g.cache_split { + g.writeln('/* V3CACHE_FN_END ${cache_fn_marker_key(item.file, item.module, node.value)} */') + } + } +} + +fn c_backend_fn_file_rank(file string) int { + if file.ends_with('.c.v') { + return 1 + } + return 0 +} + +fn (mut g FlatGen) gen_synthetic_main_after_fns() { + if g.suppress_main { + if g.needs_no_main_runtime_init_caller() { + g.gen_no_main_runtime_init_caller() + } + return + } + if g.test_files.len > 0 { + if g.cache_split { + g.writeln('/* V3CACHE_MODULE main */') + } + g.gen_test_main() + return + } + if g.has_entry_main() { + return + } + if g.is_shared { + g.gen_shared_runtime_callers() + if g.needs_no_main_runtime_init_caller() { + g.gen_no_main_runtime_init_caller() + } + return + } + top_level_stmts := g.top_level_stmts() + if top_level_stmts.len == 0 { + needs_runtime_init_caller := g.needs_no_main_runtime_init_caller() + if needs_runtime_init_caller { + g.gen_no_main_runtime_init_caller() + } + if g.object_file_mode || g.suppress_main || g.has_no_main_module() + || g.postinclude_directives.len > 0 { + return + } + if g.cache_split && !needs_runtime_init_caller { + g.writeln('/* V3CACHE_MODULE main */') + } + g.gen_top_level_main([]TopLevelStmt{}) + return + } + needs_runtime_init_caller := g.needs_no_main_runtime_init_caller() + if needs_runtime_init_caller { + g.gen_no_main_runtime_init_caller() + } + if g.cache_split && !needs_runtime_init_caller { + g.writeln('/* V3CACHE_MODULE main */') + } + g.gen_top_level_main(top_level_stmts) +} + +fn (g &FlatGen) has_no_main_module() bool { + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + if node.kind == .module_decl && node.value == 'no_main' { + return true + } + } + return false +} + +fn (mut g FlatGen) gen_executable_cleanup_registration() { + if g.module_cleanup_fns.len > 0 { + g.writeln('atexit(_vcleanup);') + } +} + +fn (g &FlatGen) needs_no_main_runtime_init_caller() bool { + return g.test_files.len == 0 && (g.suppress_main || !g.has_entry_main()) + && (g.a.export_fn_names.len > 0 || g.is_shared) +} + +fn (g &FlatGen) runtime_init_is_needed() bool { + return g.const_runtime_inits.len > 0 || g.runtime_inits.len > 0 || g.module_init_fns.len > 0 + || g.global_inits.len > 0 +} + +fn (mut g FlatGen) gen_no_main_runtime_init_caller() { + if g.cache_split { + g.writeln('/* V3CACHE_MODULE main */') + } + // Keep the guard translation-unit-local, but not function-local. Clang's + // `internal_linkage` pragma for `-is_o` otherwise gives a local static both a + // private definition and an external relocation on macOS. + g.writeln('static bool _v3_no_main_initialized = false;') + g.writeln('static void _vno_main_init_caller(void) {') + g.writeln('\tif (_v3_no_main_initialized) { return; }') + g.writeln('\t_v3_no_main_initialized = true;') + if g.has_builtins { + g.writeln('\tg_main_argc = 0;') + g.writeln('\tg_main_argv = NULL;') + } + g.gen_profile_startup_enable() + if g.runtime_init_is_needed() { + g.writeln('\t_vinit();') + } + g.gen_profile_registration() + if !g.is_shared { + g.gen_executable_cleanup_registration() + } + g.writeln('}') + g.writeln('') +} + +fn (mut g FlatGen) gen_shared_runtime_callers() { + if g.cache_split { + g.writeln('/* V3CACHE_MODULE main */') + } + if g.target.os != 'windows' { + g.writeln('__attribute__((constructor))') + } + g.writeln('void _vinit_caller(void) {') + g.writeln('\t_vno_main_init_caller();') + g.writeln('}') + g.writeln('') + if g.target.os != 'windows' { + g.writeln('__attribute__((destructor))') + } + g.writeln('void _vcleanup_caller(void) {') + g.writeln('\tstatic bool once = false;') + g.writeln('\tif (once) { return; }') + g.writeln('\tonce = true;') + g.writeln('\t_vcleanup();') + if g.coverage_dir.len > 0 { + g.writeln('\tv3_write_coverage_stats();') + } + g.writeln('}') + g.writeln('') +} + +fn (g &FlatGen) has_entry_main() bool { + mut cur_module := '' + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + kind_id := node_kind_id(node) + if kind_id == 77 { + cur_module = '' + continue + } + if kind_id == 73 { + cur_module = node.value + continue + } + if kind_id == 61 && node.value == 'main' && (cur_module.len == 0 || cur_module == 'main') { + return true + } + } + return false +} + +fn (g &FlatGen) top_level_stmts() []TopLevelStmt { + mut stmts := []TopLevelStmt{} + for file_idx in g.top_level_nodes() { + file_node := g.a.nodes[file_idx] + if !g.should_emit_top_level_file(file_idx, file_node) { + continue + } + module_name := g.top_level_file_module_name(file_node) + for i in 0 .. file_node.children_count { + child_id := g.a.child(&file_node, i) + if int(child_id) < g.a.user_code_start { + continue + } + if g.cgen_is_top_level_stmt(child_id) { + stmts << TopLevelStmt{ + id: child_id + file: file_node.value + module: if module_name.len == 0 { 'main' } else { module_name } + } + } + } + } + return stmts +} + +fn (g &FlatGen) should_emit_top_level_file(file_idx int, file_node flat.Node) bool { + if file_idx < g.a.user_code_start || file_node.kind != .file || file_node.children_count == 0 { + return false + } + module_name := g.top_level_file_module_name(file_node) + return module_name.len == 0 || module_name == 'main' +} + +fn (g &FlatGen) top_level_file_module_name(file_node flat.Node) string { + for i in 0 .. file_node.children_count { + child := g.a.child_node(&file_node, i) + if child.kind == .module_decl { + return child.value + } + } + return '' +} + +fn (g &FlatGen) cgen_is_top_level_stmt(id flat.NodeId) bool { + if int(id) < 0 { + return false + } + node := g.a.nodes[int(id)] + return match node.kind { + .expr_stmt, .assign, .decl_assign, .selector_assign, .index_assign, .for_stmt, + .for_in_stmt, .if_expr, .assert_stmt, .defer_stmt { + true + } + .block, .comptime_if { + for i in 0 .. node.children_count { + if g.cgen_is_top_level_stmt(g.a.child(&node, i)) { + return true + } + } + false + } + else { + false + } + } +} + +// should_emit_fn_node reports whether should emit fn node applies in c. +fn (mut g FlatGen) should_emit_fn_node(node flat.Node, node_index int) bool { + return g.should_emit_fn_node_in_module(node, node_index, g.tc.cur_module, g.tc.cur_file) +} + +// should_emit_fn_node_in_module reports whether should emit fn node in module applies in c. +fn (mut g FlatGen) should_emit_fn_node_in_module(node flat.Node, node_index int, module_name string, file_name string) bool { + qfn := g.qualified_fn_name_in_module_c(module_name, node.value) + is_program_specialization := g.is_program_specialization_fn_node_with_qfn(node, node_index, qfn) + return g.should_emit_fn_node_in_module_known(node, module_name, file_name, qfn, + is_program_specialization) +} + +fn (mut g FlatGen) should_emit_fn_node_in_module_known(node flat.Node, module_name string, file_name string, qfn string, is_program_specialization bool) bool { + if g.should_rename_user_main_for_tests(module_name, node.value) { + return true + } + if module_name == 'builtin' && node.value == 'exit' { + return true + } + if module_name == 'sync' && g.needs_shared_runtime + && node.value in ['cpanic', 'cpanic_errno', 'should_be_zero', 'RwMutex.init', 'RwMutex.lazy_init', 'RwMutex.lock', 'RwMutex.unlock', 'RwMutex.rlock', 'RwMutex.runlock'] { + return true + } + // `array.pointers` is emitted as an intrinsic at call sites; the raw + // builtin body has an erased `array` receiver and generates invalid C. + if module_name == 'builtin' && node.value == 'array.pointers' { + return false + } + // Concrete drop_owned calls are emitted as ownership intrinsics at every call + // site. Their generic bodies retain erased recursive calls and must not be + // emitted as ordinary C helpers. + if module_name == 'builtin' && node.value.starts_with('drop_owned_T_') { + return false + } + // `&u8.vbytes` call sites are canonicalized to `byteptr.vbytes`; emitting the + // raw helper would duplicate the ABI under a misleading `u8__vbytes` name. + if module_name == 'builtin' && node.value == 'u8.vbytes' { + return false + } + if module_name == 'fastc' && file_name.ends_with('/gen/fastc/interface.v') { + return true + } + if module_name == 'fastc' + && (node.value in ['sum_type_index', 'sum_type_index_resolved', 'FlatGen.sum_type_index', 'FlatGen.sum_type_index_resolved'] + || qfn.ends_with('__FlatGen__sum_type_index') + || qfn.ends_with('__FlatGen__sum_type_index_resolved')) { + return true + } + is_anon_fn := node.value.starts_with('__anon_fn_') || qfn.contains('__anon_fn_') + if is_anon_fn { + if !g.has_used_fn_filter() || module_name in ['', 'main'] || g.test_files[file_name] + || g.cache_program_files[file_name] + || g.used_fn_contains_in_module(node.value, module_name) { + return true + } + return false + } + if node.generic_params().len == 0 && cgen_is_operator_overload_fn(node.value) + && g.test_files[file_name] { + return true + } + if g.should_emit_ierror_method(node.value, qfn) { + return true + } + // Every specialization materialized from the combined program/module-cache + // graph is a concrete body needed by either main or one of the cached objects. + if is_program_specialization { + return true + } + if g.fn_node_is_open_generic_template(node, module_name) { + return false + } + if g.has_used_fn_filter() { + if g.used_fn_contains_in_module(node.value, module_name) { + return true + } + return g.interface_dispatch_method_is_required(node.value) + || g.interface_dispatch_method_is_required(dotted_fn_name_in_module(module_name, node.value)) + || g.interface_dispatch_method_is_required(qfn) + } + return true +} + +fn (g &FlatGen) fn_node_is_open_generic_template(node flat.Node, module_name string) bool { + if node.generic_params().len > 0 { + return true + } + if node.value.index_u8(`.`) < 0 { + return false + } + receiver := node.value.all_before_last('.') + base, args, ok := g.shared_generic_app_parts(receiver) + if !ok || args.len == 0 { + return false + } + mut candidates := [base] + if !base.contains('.') && module_name.len > 0 && module_name !in ['main', 'builtin'] { + candidates << '${module_name}.${base}' + } + for candidate in candidates { + params := g.tc.struct_generic_params[candidate] or { continue } + for arg in args { + if arg.trim_space() in params { + return true + } + } + } + return false +} + +fn (g &FlatGen) is_program_specialization_fn_node(node flat.Node, node_index int, module_name string) bool { + qfn := g.qualified_fn_name_in_module_c(module_name, node.value) + return g.is_program_specialization_fn_node_with_qfn(node, node_index, qfn) +} + +fn (g &FlatGen) is_program_specialization_fn_node_with_qfn(node flat.Node, node_index int, qfn string) bool { + if g.a.specialized_fn_nodes[node_index] { + return true + } + return node.value in g.tc.specialized_generic_fns || qfn in g.tc.specialized_generic_fns + || g.cname(node.value) in g.tc.specialized_generic_fns +} + +fn cgen_is_operator_overload_fn(name string) bool { + if !name.contains('.') { + return false + } + method := name.all_after_last('.') + return method in ['+', '-', '*', '/', '%', '==', '!=', '<', '>', '<=', '>=', '[]', '[]='] +} + +fn is_generated_fn_after_markused(name string) bool { + return name.starts_with('__anon_fn_') +} + +// used_fn_contains reports whether used fn contains applies in c. +fn (g &FlatGen) used_fn_contains(name string) bool { + if name.len == 0 || isnil(g.used_fns) { + return false + } + return (*g.used_fns)[name] +} + +fn (g &FlatGen) used_fn_contains_in_module(name string, module_name string) bool { + if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' + && name.starts_with('${module_name}.') { + if g.used_fn_contains(name) { + return true + } + cfn := g.cname(name) + if cfn != name && g.used_fn_contains(cfn) { + return true + } + } + dfn := dotted_fn_name_in_module(module_name, name) + qfn := g.qualified_fn_name_in_module_c(module_name, name) + if g.used_fn_contains(dfn) || g.used_fn_contains(qfn) { + return true + } + if module_name.len == 0 || module_name == 'main' || module_name == 'builtin' { + cfn := g.cname(name) + return g.used_fn_contains(name) || g.used_fn_contains(cfn) + } + return false +} + +// has_used_fn_filter reports whether has used fn filter applies in c. +fn (g &FlatGen) has_used_fn_filter() bool { + return !isnil(g.used_fns) && g.used_fns.len > 0 && g.used_fn_contains('main') +} + +// emitted_fn_contains reports whether emitted fn contains applies in c. +fn (g &FlatGen) emitted_fn_contains(name string) bool { + return name.len > 0 && g.emitted_fns[name] +} + +fn generic_method_candidate_key(receiver string, method string) string { + return '${receiver}\n${method}' +} + +fn (mut g FlatGen) precompute_generic_method_candidate_index() { + g.generic_method_candidates.clear() + for name, ret in g.tc.fn_ret_types { + if !name.contains('[') || !name.contains('.') { + continue + } + method := name.all_after_last('.') + receiver := name.all_before_last('.') + if method.len == 0 || !receiver.contains('[') { + continue + } + base_receiver := receiver.all_before('[') + if base_receiver.len == 0 { + continue + } + candidate := GenericMethodCandidate{ + name: name + ret: ret + params: g.tc.fn_param_types[name] or { []types.Type{} } + } + g.add_generic_method_candidate(base_receiver, method, candidate) + short_receiver := base_receiver.all_after_last('.') + if short_receiver != base_receiver { + g.add_generic_method_candidate(short_receiver, method, candidate) + } + } +} + +fn (mut g FlatGen) add_generic_method_candidate(receiver string, method string, candidate GenericMethodCandidate) { + key := generic_method_candidate_key(receiver, method) + mut candidates := g.generic_method_candidates[key] or { []GenericMethodCandidate{} } + candidates << candidate + g.generic_method_candidates[key] = candidates +} + +// qualified_fn_name supports qualified fn name handling for FlatGen. +fn (g &FlatGen) qualified_fn_name(name string) string { + return g.qualified_fn_name_in_module_c(g.tc.cur_module, name) +} + +fn (g &FlatGen) export_fn_name_in_module(module_name string, name string) ?string { + qname := dotted_fn_name_in_module(module_name, name) + if export_name := g.a.export_fn_names[qname] { + return export_name + } + if (module_name.len == 0 || module_name == 'main' || module_name == 'builtin') + && name in g.a.export_fn_names { + return g.a.export_fn_names[name] + } + return none +} + +// qualified_fn_name_in_module supports qualified fn name in module handling for c. +// qualified_fn_name_in_module_c is the memoizing FlatGen variant of +// qualified_fn_name_in_module (the c_name cache absorbs the sanitize cost; +// asked ~46k times per build on the call-emission path). +fn (g &FlatGen) qualified_fn_name_in_module_c(module_name string, name string) string { + if module_name == 'builtin' && name == 'free' { + return 'v_free' + } + if name == 'panic' + && (module_name.len == 0 || module_name == 'main' || module_name == 'builtin') { + return 'v_panic' + } + synthetic_name := name.all_after_last('.') + if synthetic_name.starts_with('__v3_sum_eq_') || synthetic_name.starts_with('__v3_autostr_') + || synthetic_name.starts_with('__v3_default_clone_') { + return g.cname(synthetic_name) + } + if g.tc.autofree_mode && module_name in ['', 'main'] { + clean_name := name.trim_string_left('main.') + if clean_name.contains('.') { + receiver := clean_name.all_before_last('.') + method := clean_name.all_after_last('.') + return 'main__${g.cname(receiver)}_${g.cname(method)}' + } + return 'main__${g.cname(clean_name)}' + } + if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' { + return g.cname('${module_name}.${name}') + } + if name == 'free' { + return 'v_free' + } + if name == 'new_map' && (module_name.len == 0 || module_name == 'main') { + return 'main__new_map' + } + return g.cname(name) +} + +fn qualified_fn_name_in_module(module_name string, name string) string { + if module_name == 'builtin' && name == 'free' { + return 'v_free' + } + if name == 'panic' + && (module_name.len == 0 || module_name == 'main' || module_name == 'builtin') { + return 'v_panic' + } + synthetic_name := name.all_after_last('.') + if synthetic_name.starts_with('__v3_sum_eq_') || synthetic_name.starts_with('__v3_autostr_') { + return c_name(synthetic_name) + } + if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' { + return c_name('${module_name}.${name}') + } + if name == 'free' { + return 'v_free' + } + if name == 'new_map' && (module_name.len == 0 || module_name == 'main') { + return 'main__new_map' + } + return c_name(name) +} + +fn is_main_fn_in_main_module(module_name string, name string) bool { + return name == 'main' && (module_name.len == 0 || module_name == 'main') +} + +fn (g &FlatGen) should_rename_user_main_for_tests(module_name string, name string) bool { + return g.test_files.len > 0 && is_main_fn_in_main_module(module_name, name) +} + +fn (g &FlatGen) fn_c_name_in_module(module_name string, name string) string { + if collision_name := g.operator_overload_collision_c_name(module_name, name) { + return collision_name + } + if enum_method_name := g.enum_method_c_name_in_module(module_name, name) { + return enum_method_name + } + if g.suppress_main && is_main_fn_in_main_module(module_name, name) { + return g.cname('main.main') + } + if g.should_rename_user_main_for_tests(module_name, name) { + return g.test_user_main_c_name() + } + if shadow_name := g.main_runtime_shadow_fn_c_name(module_name, name) { + return shadow_name + } + return g.qualified_fn_name_in_module_c(module_name, name) +} + +const c_main_runtime_shadow_fn_names = { + 'new_map': true + 'accept': true + 'perror': true +} + +fn (g &FlatGen) main_runtime_shadow_fn_c_name(module_name string, name string) ?string { + if !c_main_runtime_shadow_fn_names[name] { + return none + } + if module_name.len == 0 || module_name == 'main' { + return g.cname('main.${name}') + } + return none +} + +fn (g &FlatGen) test_user_main_c_name() string { + base := 'main__user_main' + if !g.c_fn_symbol_exists(base) { + return base + } + limit := g.a.nodes.len + 2 + for idx in 1 .. limit { + candidate := '${base}_${idx}' + if !g.c_fn_symbol_exists(candidate) { + return candidate + } + } + return '${base}_${limit}' +} + +fn (g &FlatGen) c_fn_symbol_exists(candidate string) bool { + mut cur_module := '' + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + kind_id := node_kind_id(node) + if kind_id == 77 { + cur_module = '' + continue + } + if kind_id == 73 { + cur_module = node.value + continue + } + if kind_id != 61 { + continue + } + if g.qualified_fn_name_in_module_c(cur_module, node.value) == candidate { + return true + } + if export_name := g.export_fn_name_in_module(cur_module, node.value) { + if export_name == candidate { + return true + } + } + } + return false +} + +// direct_call_name supports direct call name handling for FlatGen. +fn (mut g FlatGen) direct_call_name(name string) string { + synthetic_name := name.all_after_last('.') + if synthetic_name.starts_with('__v3_sum_eq_') || synthetic_name.starts_with('__v3_autostr_') { + return g.cname(synthetic_name) + } + if abi_name := g.c_decl_abi_names[name] { + return abi_name + } + if abi_name := g.c_decl_abi_names[g.cname(name)] { + return abi_name + } + if collision_name := g.operator_overload_collision_c_name('', name) { + return collision_name + } + if enum_method_name := g.enum_method_c_name_in_module('', name) { + return enum_method_name + } + if compat_name := g.libc_compat_call_name(name) { + return compat_name + } + if g.test_files.len > 0 && (name == 'main' || name == 'main.main') { + return g.test_user_main_c_name() + } + if g.suppress_main && (name == 'main' || name == 'main.main') { + // `-d no_main` renames the entry `main` to `main__main`; a call to it must use + // the renamed symbol rather than the bare `main`. + return g.cname('main.main') + } + if name == 'free' { + return 'v_free' + } + if name == 'new_map' { + if g.tc.cur_module == 'builtin' { + return 'new_map' + } + return 'main__new_map' + } + if name == 'int_str' { + return 'int__str' + } + if name == 'bool_str' { + return 'bool__str' + } + if name == 'char.vstring' { + return 'charptr__vstring' + } + if name == 'char.vstring_with_len' { + return 'charptr__vstring_with_len' + } + if g.tc.autofree_mode && g.tc.cur_module in ['', 'main'] { + legacy_name := name.trim_string_left('main.') + legacy_c_name := g.qualified_fn_name_in_module_c('main', legacy_name) + if 'main\x01${legacy_name}' in g.non_generic_fn_names_by_module + || 'main\x01main.${legacy_name}' in g.non_generic_fn_names_by_module + || '\x01${legacy_name}' in g.non_generic_fn_names_by_module + || '\x01main.${legacy_name}' in g.non_generic_fn_names_by_module + || g.c_fn_symbol_exists(legacy_c_name) { + return legacy_c_name + } + } + return g.cname(name) +} + +fn (g &FlatGen) operator_overload_collision_c_name(module_name string, name string) ?string { + if !cgen_is_operator_overload_fn(name) { + return none + } + qualified := dotted_fn_name_in_module(module_name, name) + receiver := qualified.all_before_last('.') + op := qualified.all_after_last('.') + mangled_method := g.cname('T.${op}').all_after_last('__') + ordinary := '${receiver}.${mangled_method}' + if ordinary !in g.tc.fn_param_types && ordinary !in g.tc.fn_ret_types { + return none + } + return '${g.cname(qualified)}__operator' +} + +fn (g &FlatGen) enum_method_c_name_in_module(module_name string, name string) ?string { + // Most direct calls are plain functions. Reject them before selecting a + // context or probing a cache; self-host cgen reaches this path hundreds of + // thousands of times. + if !name.contains('.') { + return none + } + mut cache := if module_name.len == 0 { + g.enum_method_cache + } else { + g.qualified_enum_method_cache + } + if !isnil(cache) { + context_module := if module_name.len == 0 { g.tc.cur_module } else { module_name } + cache.select_context(g.tc.cur_file, context_module) + if cache.last_valid && cache.last_name.len == name.len + && (unsafe { cache.last_name.str == name.str } || cache.last_name == name) { + if cache.last_value.len > 0 { + return cache.last_value + } + return none + } + if cached := cache.entries[name] { + cache.last_name = name + cache.last_value = cached + cache.last_valid = true + if cached.len > 0 { + return cached + } + return none + } + } + result := g.enum_method_c_name_in_module_uncached(module_name, name) or { + if !isnil(cache) { + cache.entries[name] = '' + cache.last_name = name + cache.last_value = '' + cache.last_valid = true + } + return none + } + if !isnil(cache) { + cache.entries[name] = result + cache.last_name = name + cache.last_value = result + cache.last_valid = true + } + return result +} + +fn (g &FlatGen) enum_method_c_name_in_module_uncached(module_name string, name string) ?string { + qualified := if module_name !in ['', 'main', 'builtin'] && !name.starts_with('${module_name}.') { + '${module_name}.${name}' + } else { + name + } + dot := qualified.last_index_u8(`.`) + if dot <= 0 || dot + 1 >= qualified.len { + return none + } + receiver := unsafe { qualified.substr_unsafe(0, dot) } + method := unsafe { qualified.substr_unsafe(dot + 1, qualified.len) } + if _ := g.enum_selector_base_name(receiver) { + return '${g.cname(receiver)}_${g.cname(method)}' + } + return none +} + +fn (mut g FlatGen) direct_call_name_for_call(id flat.NodeId, name string) string { + if int(id) >= 0 && int(id) < g.a.nodes.len { + call_node := g.a.nodes[int(id)] + if call_node.kind == .call && call_node.children_count > 0 { + fn_node := g.a.child_node(&call_node, 0) + if shadow_name := g.main_runtime_shadow_call_c_name(call_node, fn_node) { + return shadow_name + } + } + } + if enum_method := g.enum_method_c_name_in_module('', name) { + return enum_method + } + // Synthesized helpers carry their complete, globally unique C symbol in the + // source name. Their owning module only controls cache-object placement; it + // must not be prepended to calls made from that same module. + if name.starts_with('__v3_sum_eq_') || name.starts_with('__v3_autostr_') + || name.starts_with('__v3_default_clone_') { + return g.direct_call_name(name) + } + if !name.contains('.') && g.tc.cur_module.len > 0 && g.tc.cur_module !in ['main', 'builtin'] { + qname := '${g.tc.cur_module}.${name}' + qcname := g.qualified_fn_name_in_module_c(g.tc.cur_module, name) + if qname in g.tc.specialized_generic_fns || qcname in g.tc.specialized_generic_fns + || qname in g.tc.fn_generic_params + || g.non_generic_fn_decl_exists_in_module(name, g.tc.cur_module) { + return qcname + } + } + if g.test_files.len > 0 && (name == 'main' || name == 'main.main') { + if resolved := g.tc.resolved_call_name(id) { + if resolved == 'main' || resolved == 'main.main' { + return g.test_user_main_c_name() + } + } + return g.cname(name) + } + // Monomorphization has already selected this exact concrete method. Do not + // run overload-style candidate matching again: same-named types from two + // modules can have indistinguishable container arguments at C ABI level. + if specialized := g.exact_specialized_generic_call_name(name) { + return g.direct_call_name(specialized) + } + if alias := g.flattened_generic_method_short_alias(name) { + return g.cname(alias) + } + if specialized := g.specialized_generic_method_name_for_call_with_arg_count(id, name, -1) { + return g.cname(specialized) + } + return g.direct_call_name(name) +} + +fn (mut g FlatGen) direct_call_name_for_call_node(id flat.NodeId, node flat.Node, name string) string { + if node.children_count > 0 { + fn_node := g.a.child_node(&node, 0) + if shadow_name := g.main_runtime_shadow_call_c_name(node, fn_node) { + return shadow_name + } + } + // A bracketed callee is the exact specialization selected by monomorphization. + // Its runtime arguments may expose only the alias target (`string` for a + // `MyString` alias), so re-inferring here would silently select another body. + if name.contains('[') && name.contains(']') && (name in g.tc.fn_ret_types + || name in g.tc.fn_param_types + || g.cname(name) in g.tc.specialized_generic_fns) { + // Keep the exact type arguments, while still allowing the ordinary direct-call + // path to select the current module's concrete receiver specialization. + return g.direct_call_name_for_call(id, name) + } + if specialized := g.exact_specialized_generic_call_name(name) { + // A comptime-expanded call node can retain the first clone's specialization. + // Prefer the specialization inferred from this clone's concrete arguments. + if specialized.contains('_T_') { + call_ret := g.call_default_return_type(id) + if declared_ret := g.tc.fn_ret_types[specialized] { + // Transformed explicit generic calls already carry the correct concrete + // callee. Preserve it when its signature agrees with the call node; literal + // arguments alone are insufficient to recover `new_tls[i8](-3)`. + if declared_ret.name().len > 0 + && cgen_types_equal_after_alias_erasure(declared_ret, call_ret) { + return g.direct_call_name(specialized) + } + } + base := specialized.all_before('_T_') + if inferred := g.inferred_generic_plain_fn_name_for_call(id, node, base) { + return g.direct_call_name(inferred) + } + } + return g.direct_call_name(specialized) + } + // A transformed method call already carries the exact non-generic declaration + // selected by the checker/transformer. Do not reinterpret it as a same-spelled + // generic receiver method from another module (for example, + // decoder2.Decoder.decode_string vs json2.Decoder[T].decode_string). + if (name in g.tc.fn_ret_types || name in g.tc.fn_param_types) && name !in g.tc.fn_generic_params { + return g.direct_call_name(name) + } + if specialized := g.specialized_generic_method_name_for_call_args(node, name, + int(node.children_count) - 1) + { + return g.cname(specialized) + } + return g.direct_call_name_for_call(id, name) +} + +fn (g &FlatGen) exact_specialized_generic_call_name(name string) ?string { + if g.skip_generics { + return none + } + if name !in g.tc.specialized_generic_fns { + return none + } + if g.tc.cur_module.len > 0 && g.tc.cur_module !in ['main', 'builtin'] { + qualified := '${g.tc.cur_module}.${name}' + if qualified in g.tc.specialized_generic_fns { + return qualified + } + } + return name +} + +fn (g &FlatGen) flattened_generic_method_short_alias(name string) ?string { + if g.skip_generics { + return none + } + if !name.contains('.') { + return none + } + receiver := name.all_before_last('.') + method := name.all_after_last('.') + if receiver.len == 0 || method.len == 0 { + return none + } + for short_receiver in cgen_flattened_generic_receiver_short_variants(receiver) { + candidate := '${short_receiver}.${method}' + if candidate in g.tc.fn_param_types || candidate in g.tc.fn_ret_types { + return candidate + } + } + return none +} + +fn (mut g FlatGen) libc_compat_call_name(name string) ?string { + if name.starts_with('C.') { + cfn := g.cname(name) + wide_cfn := c_winapi_wide_export_name(cfn) + if wide_cfn != cfn { + return wide_cfn + } + } + // `builtin.v_gettid()` reaches libc through `C.gettid()` on Linux/glibc, but + // that symbol is not declared by all usable C header sets. Route it through a + // tiny runtime compatibility helper instead of emitting an undeclared call. + if name == 'C.gettid' { + g.libc_compat_fns['gettid'] = true + return 'v3_gettid' + } + if name in ['C.v_filelock_lock', 'C.v_filelock_unlock'] { + g.libc_compat_fns['filelock'] = true + return g.cname(name) + } + return none +} + +fn (mut g FlatGen) preseed_libc_compat_fns() { + refs := g.c_extern_referenced_symbols() + if refs['C.gettid'] || refs['gettid'] { + g.libc_compat_fns['gettid'] = true + } + if refs['C.v_filelock_lock'] || refs['C.v_filelock_unlock'] || refs['v_filelock_lock'] + || refs['v_filelock_unlock'] + || g.used_fn_contains_in_module('FileLock.lock_handle', 'filelock') + || g.used_fn_contains_in_module('FileLock.lock_fd', 'filelock') + || g.used_fn_contains_in_module('FileLock.close_lock', 'filelock') { + g.libc_compat_fns['filelock'] = true + } +} + +fn (g &FlatGen) test_user_main_fn_value_c_name(id flat.NodeId, node flat.Node) ?string { + if g.test_files.len == 0 || node.kind != .ident || node.value != 'main' { + return none + } + looked_up := g.tc.cur_scope.lookup(node.value) or { types.Type(types.void_) } + if looked_up !is types.Void { + return none + } + if resolved := g.tc.resolved_fn_value_name(id) { + if resolved == 'main' || resolved == 'main.main' { + return g.test_user_main_c_name() + } + return none + } + if g.usable_expr_type(id) is types.FnType { + return g.test_user_main_c_name() + } + return none +} + +fn (g &FlatGen) import_alias_module(alias string) ?string { + if alias.len == 0 { + return none + } + mut cache := g.import_alias_cache + if !isnil(cache) { + cur_file := if g.tc == unsafe { nil } { '' } else { g.tc.cur_file } + cache.select_context(cur_file, '') + if cache.last_valid && cache.last_name.len == alias.len + && (unsafe { cache.last_name.str == alias.str } || cache.last_name == alias) { + if cache.last_value.len > 0 { + return cache.last_value + } + return none + } + if cached := cache.entries[alias] { + cache.last_name = alias + cache.last_value = cached + cache.last_valid = true + if cached.len > 0 { + return cached + } + return none + } + } + result := g.import_alias_module_uncached(alias) or { + if !isnil(cache) { + cache.entries[alias] = '' + cache.last_name = alias + cache.last_value = '' + cache.last_valid = true + } + return none + } + if !isnil(cache) { + cache.entries[alias] = result + cache.last_name = alias + cache.last_value = result + cache.last_valid = true + } + return result +} + +fn (g &FlatGen) import_alias_module_uncached(alias string) ?string { + if g.tc != unsafe { nil } && g.tc.cur_file.len > 0 { + key := g.tc.cur_file + '\n' + alias + if mod := g.tc.file_imports[key] { + return mod + } + } + if mod := g.modules[alias] { + if !mod.contains('.') && mod == alias { + return none + } + return mod + } + return none +} + +fn (g &FlatGen) selector_base_module(name string) ?string { + if name.len == 0 { + return none + } + if g.tc != unsafe { nil } && g.tc.cur_file.len > 0 { + key := g.tc.cur_file + '\n' + name + if mod := g.tc.file_imports[key] { + return mod + } + } + if mod := g.modules[name] { + return mod + } + if mod := g.tc.imports[name] { + return mod + } + return none +} + +fn (g &FlatGen) selector_base_is_module(name string) bool { + if _ := g.selector_base_module(name) { + return true + } + return false +} + +fn (g &FlatGen) selector_base_is_value(name string) bool { + if name.len == 0 { + return false + } + if g.tc != unsafe { nil } && g.tc.cur_scope != unsafe { nil } { + if typ := g.tc.cur_scope.lookup(name) { + if typ !is types.Void { + return true + } + } + } + if _ := g.current_param_type(name) { + return true + } + if _ := g.global_type_for_ident(name) { + return true + } + return false +} + +fn (g &FlatGen) selector_base_is_local_value(name string) bool { + if name.len == 0 { + return false + } + if g.tc != unsafe { nil } && g.tc.cur_scope != unsafe { nil } { + if typ := g.tc.cur_scope.lookup(name) { + if typ !is types.Void { + return true + } + } + } + if _ := g.current_param_type(name) { + return true + } + return false +} + +fn (g &FlatGen) has_import_alias(alias string) bool { + if _ := g.import_alias_module(alias) { + return true + } + return false +} + +// dotted_fn_name supports dotted fn name handling for FlatGen. +fn (g &FlatGen) dotted_fn_name(name string) string { + return dotted_fn_name_in_module(g.tc.cur_module, name) +} + +// dotted_fn_name_in_module supports dotted fn name in module handling for c. +fn dotted_fn_name_in_module(module_name string, name string) string { + if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' { + return '${module_name}.${name}' + } + return name +} + +// qualify_name_in_module supports qualify name in module handling for c. +fn qualify_name_in_module(module_name string, name string) string { + if module_name.len == 0 || module_name == 'main' || module_name == 'builtin' { + return name + } + if name.contains('.') { + return name + } + return '${module_name}.${name}' +} + +// gen_fn emits fn output for c. +fn (mut g FlatGen) gen_fn(node flat.Node) { + g.gen_fn_in_module(flat.empty_node, node, g.tc.cur_module, false) +} + +fn (g &FlatGen) fn_decl_c_attribute(node_id flat.NodeId) string { + if int(node_id) < 0 || g.ccompiler == 'msvc' { + return '' + } + attrs := g.decl_attrs[int(node_id)] or { return '' } + mut c_attrs := []string{} + for raw_attr in attrs { + match raw_attr.all_before(':').trim_space() { + '_constructor' { c_attrs << 'constructor' } + '_destructor' { c_attrs << 'destructor' } + else {} + } + } + if c_attrs.len == 0 { + return '' + } + return ' __attribute__((${c_attrs.join(', ')}))' +} + +fn (mut g FlatGen) write_method_c_name(id flat.NodeId, node flat.Node, method_name string) { + call_name := g.method_call_name_for_call(id, node, method_name) + if node.children_count > 0 { + fn_node := g.a.child_node(&node, 0) + if fn_node.children_count > 0 { + receiver_type := concrete_receiver_type(g.usable_expr_type(g.a.child(fn_node, 0))) + if receiver_type is types.Enum { + g.write(g.direct_call_name(call_name)) + return + } + if receiver_type !is types.Unknown && receiver_type !is types.Void { + g.write(g.cname(call_name)) + return + } + } + } + // Keep the name-based fallback for synthetic or partially typed calls. + g.write(g.direct_call_name(call_name)) +} + +fn (mut g FlatGen) gen_channel_close_call(base_id flat.NodeId, node flat.Node) { + g.write('sync__Channel__close(') + if g.channel_close_receiver_needs_deref(base_id) { + g.write('*(') + g.gen_expr(base_id) + g.write(')') + } else { + g.write('(sync__Channel*)(') + g.gen_expr(base_id) + g.write(')') + } + g.write(', ') + g.gen_channel_close_errors(node) + g.write(')') +} + +fn (mut g FlatGen) gen_channel_close_errors(node flat.Node) { + if node.children_count <= 1 { + g.write('array_new(sizeof(IError), 0, 0)') + return + } + count := node.children_count - 1 + ierror_type := g.tc.parse_type('IError') + g.write('new_array_from_c_array(${count}, ${count}, sizeof(IError), (IError[]){') + for i in 1 .. node.children_count { + if i > 1 { + g.write(', ') + } + g.gen_expr_with_expected_type(g.a.child(&node, i), ierror_type) + } + g.write('})') +} + +fn (mut g FlatGen) gen_channel_try_call(node flat.Node, fn_node flat.Node) bool { + if fn_node.kind != .selector || fn_node.children_count == 0 + || fn_node.value !in ['try_push', 'try_pop'] || node.children_count < 2 { + return false + } + base_id := g.a.child(&fn_node, 0) + base_type := concrete_receiver_type(g.usable_expr_type(base_id)) + if base_type !is types.Channel { + return false + } + channel_type := base_type as types.Channel + arg_id := g.channel_try_push_source_arg(g.a.child(&node, 1)) + if fn_node.value == 'try_push' { + elem_ct := g.value_c_type(channel_type.elem_type) + if fixed := array_fixed_type(channel_type.elem_type) { + tmp := g.tmp_count + g.tmp_count++ + tmp_name := '_try_push_${tmp}' + src := g.fixed_array_copy_source_string(arg_id, types.Type(fixed)) + g.write('({ ${elem_ct} ${tmp_name}; memmove(${tmp_name}, ${src}, sizeof(${tmp_name})); sync__Channel__try_push(') + g.gen_channel_try_receiver(base_id) + g.write(', &${tmp_name}); })') + return true + } + g.write('sync__Channel__try_push(') + g.gen_channel_try_receiver(base_id) + g.write(', &(${elem_ct}[]){') + g.gen_expr_with_expected_type(arg_id, channel_type.elem_type) + g.write('})') + return true + } + g.write('sync__Channel__try_pop(') + g.gen_channel_try_receiver(base_id) + g.write(', ') + g.gen_channel_try_pop_arg(arg_id) + g.write(')') + return true +} + +fn (g &FlatGen) channel_try_push_source_arg(arg_id flat.NodeId) flat.NodeId { + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return arg_id + } + cast := g.a.nodes[int(arg_id)] + if cast.kind != .cast_expr || cast.children_count == 0 + || !type_is_void_pointer(g.tc.parse_type(cast.value)) || cast.pos.is_valid() { + return arg_id + } + addr_id := g.a.child(&cast, 0) + addr := g.a.nodes[int(addr_id)] + if addr.kind != .prefix || addr.op != .amp || addr.children_count == 0 { + return arg_id + } + value_id := g.a.child(&addr, 0) + // A synthetic cast has no source position. The transform wrapped this + // non-pointer channel value as `voidptr(&value)` solely for the runtime + // method's opaque parameter. The channel C lowering supplies that address + // itself, so recover the original value here. Preserve explicit source casts. + return value_id +} + +fn (mut g FlatGen) gen_channel_try_receiver(base_id flat.NodeId) { + if g.channel_close_receiver_needs_deref(base_id) { + g.write('*(') + g.gen_expr(base_id) + g.write(')') + return + } + g.gen_expr(base_id) +} + +fn (mut g FlatGen) gen_channel_try_pop_arg(arg_id flat.NodeId) { + arg_node := g.a.nodes[int(arg_id)] + if arg_node.kind == .prefix && arg_node.op == .amp { + g.gen_expr(arg_id) + return + } + if g.usable_expr_type(arg_id) is types.Pointer { + g.gen_expr(arg_id) + return + } + g.write('&') + g.gen_expr(arg_id) +} + +fn (mut g FlatGen) gen_compiler_default_free_call(fn_node flat.Node, resolved_target_name string) bool { + if fn_node.kind != .selector || fn_node.value != 'free' || fn_node.children_count == 0 { + return false + } + if resolved_target_name.len > 0 && resolved_target_name !in ['free', 'builtin.free'] { + return false + } + base_id := g.a.child(&fn_node, 0) + base_type := g.usable_expr_type(base_id) + if base_type is types.Void || base_type is types.Unknown { + return false + } + if resolved_target_name in ['free', 'builtin.free'] && cgen_type_is_pointer_like(base_type) { + if g.pointer_free_needs_aligned_free(base_type) { + g.write('v3_aligned_free(') + } else { + g.write('free(') + } + g.gen_expr(base_id) + g.write(')') + return true + } + clean := concrete_receiver_type(base_type) + if _ := array_like_type(clean) { + return false + } + if clean is types.Map || clean is types.String { + return false + } + if g.receiver_has_method(base_type, 'free') { + return false + } + g.write('((void)0)') + return true +} + +fn cgen_type_is_pointer_like(t types.Type) bool { + if t is types.Pointer { + return true + } + if t is types.Alias { + if t.name in ['charptr', 'byteptr', 'voidptr'] { + return true + } + return cgen_type_is_pointer_like(t.base_type) + } + return false +} + +fn pointer_free_base_type(t types.Type) ?types.Type { + clean := default_init_unalias_type(t) + if clean is types.Pointer { + return default_init_unalias_type(clean.base_type) + } + return none +} + +fn (g &FlatGen) pointer_free_needs_aligned_free(t types.Type) bool { + base_type := pointer_free_base_type(t) or { return false } + if base_type is types.Pointer { + return false + } + name := base_type.name() + if name.len == 0 { + return false + } + if _ := g.struct_decl_alignment_for_name(name) { + return true + } + return false +} + +fn (g &FlatGen) receiver_has_method(base_type types.Type, method string) bool { + mut names := []string{} + raw := types.unwrap_pointer(base_type) + if raw_name := receiver_method_type_name(raw) { + names << raw_name + } + clean := concrete_receiver_type(base_type) + if clean_name := receiver_method_type_name(clean) { + if clean_name !in names { + names << clean_name + } + } + for name in names { + if g.receiver_method_registered(name, method) { + return true + } + for alias, target in g.tc.type_aliases { + if target == name { + alias_method := '${alias}.${method}' + if g.fn_key_registered(alias_method) { + return true + } + } + } + } + return false +} + +fn (g &FlatGen) receiver_method_registered(type_name string, method string) bool { + if type_name.len == 0 { + return false + } + mut receivers := []string{} + for receiver in [type_name, type_name.all_after_last('.'), + g.tc.qualify_name(type_name)] { + if receiver.len > 0 && receiver !in receivers { + receivers << receiver + } + } + for receiver in receivers { + if resolved := g.tc.concrete_method_signature_key(receiver, method) { + if g.fn_key_registered(resolved) { + return true + } + } + resolved := g.resolve_method_name(receiver, method) + if resolved.len > 0 && g.fn_key_registered(resolved) { + return true + } + if g.fn_key_registered('${receiver}.${method}') { + return true + } + } + return false +} + +fn (g &FlatGen) fn_key_registered(name string) bool { + if name.len == 0 { + return false + } + if name in g.tc.fn_param_types || name in g.tc.fn_ret_types || name in g.fn_decl_param_types + || name in g.fn_decl_ret_types || name in g.fn_decl_mut_receivers { + return true + } + cname := g.cname(name) + return cname in g.tc.fn_param_types || cname in g.tc.fn_ret_types + || cname in g.fn_decl_param_types || cname in g.fn_decl_ret_types + || cname in g.fn_decl_mut_receivers +} + +fn receiver_method_type_name(t types.Type) ?string { + name := t.name() + if name.len == 0 { + return none + } + return name +} + +fn (g &FlatGen) channel_close_receiver_needs_deref(base_id flat.NodeId) bool { + base_type := g.tc.resolve_type(base_id) + return base_type is types.Pointer + && cgen_is_channel_close_receiver_type(types.unwrap_pointer(base_type)) +} + +fn (g &FlatGen) method_call_name_for_call(id flat.NodeId, node flat.Node, method_name string) string { + if explicit := g.explicit_generic_method_name_from_call(node, method_name) { + return explicit + } + if concrete := g.concrete_generic_method_name_from_call_receiver(node, method_name) { + return concrete + } + if specialized := g.specialized_generic_method_name_for_call_args(node, method_name, + int(node.children_count)) + { + return specialized + } + if specialized := g.specialized_generic_method_name_for_call_with_arg_count(id, method_name, + int(node.children_count)) + { + return specialized + } + return method_name +} + +fn (g &FlatGen) explicit_generic_method_name_from_call(node flat.Node, method_name string) ?string { + if !method_name.contains('.') || node.children_count == 0 { + return none + } + fn_node := g.a.child_node(&node, 0) + return g.explicit_generic_method_name_from_index(fn_node, method_name) +} + +fn (g &FlatGen) explicit_generic_method_name_from_index(fn_node flat.Node, method_name string) ?string { + if fn_node.kind != .index || fn_node.children_count < 2 || fn_node.value == 'range' { + return none + } + receiver := method_name.all_before_last('.') + method := method_name.all_after_last('.') + if receiver.len == 0 || method.len == 0 { + return none + } + mut args := []string{cap: int(fn_node.children_count) - 1} + for i in 1 .. fn_node.children_count { + arg := g.json_decode_type_arg_name(g.a.child(fn_node, i)) + if arg.len == 0 { + return none + } + args << arg + } + for suffix in generic_receiver_type_suffix_variants(args) { + mut candidates := ['${receiver}_${suffix}.${method}'] + if receiver.contains('.') { + receiver_mod := receiver.all_before_last('.') + receiver_short := receiver.all_after_last('.') + candidates << '${receiver_short}_${suffix}.${method}' + candidates << '${receiver_mod}.${receiver_short}_${suffix}.${method}' + } + for candidate in candidates { + if candidate in g.tc.fn_param_types || candidate in g.tc.fn_ret_types { + return candidate + } + } + } + return none +} + +fn (mut g FlatGen) gen_explicit_generic_callee_index(node flat.Node) bool { + explicit, _, _ := g.explicit_generic_method_callee_from_index(node) or { return false } + g.write(g.cname(explicit)) + return true +} + +fn (g &FlatGen) explicit_generic_method_callee_from_index(node flat.Node) ?(string, flat.NodeId, string) { + if node.children_count < 2 || node.value == 'range' { + return none + } + base := g.a.child_node(&node, 0) + if base.kind != .selector || base.children_count == 0 { + return none + } + receiver_id := g.a.child(base, 0) + receiver_type := concrete_receiver_type(g.usable_expr_type(receiver_id)) + receiver_name := receiver_type.name() + if receiver_name.len == 0 { + return none + } + mut method_name := g.resolve_method_name(receiver_name, base.value) + if method_name.len == 0 { + method_name = '${receiver_name}.${base.value}' + } + explicit := g.explicit_generic_method_name_from_index(node, method_name) or { return none } + return explicit, receiver_id, explicit +} + +fn (g &FlatGen) concrete_generic_method_name_from_call_receiver(node flat.Node, method_name string) ?string { + if !method_name.contains('.') || node.children_count == 0 { + return none + } + mut fn_node := g.a.child_node(&node, 0) + if fn_node.kind == .index && fn_node.children_count > 0 { + fn_node = g.a.child_node(fn_node, 0) + } + if fn_node.kind != .selector || fn_node.children_count == 0 { + return none + } + method := method_name.all_after_last('.') + if method.len == 0 { + return none + } + receiver_type := types.unwrap_pointer(g.usable_expr_type(g.a.child(fn_node, 0))) + receiver_name := receiver_type.name() + if receiver_name.contains('[') && receiver_name.contains(']') { + if resolved := g.resolve_concrete_generic_method_name(receiver_name, method) { + return resolved + } + } + for receiver in cgen_flattened_generic_receiver_short_variants(receiver_name) { + candidate := '${receiver}.${method}' + if candidate in g.tc.fn_param_types || candidate in g.tc.fn_ret_types { + return candidate + } + } + if receiver_name.contains('_') { + return g.method_name_by_receiver_param_type(receiver_type, method) + } + return none +} + +fn (g &FlatGen) is_explicit_generic_method_call_selector(fn_node &flat.Node, resolved_target_name string, target_name string) bool { + if fn_node.kind != .index || fn_node.children_count < 2 { + return false + } + selector := g.a.child_node(fn_node, 0) + if selector.kind != .selector || selector.children_count == 0 { + return false + } + for i in 1 .. fn_node.children_count { + arg := g.a.child_node(fn_node, i) + if arg.kind in [.ident, .selector] { + continue + } + if arg.kind == .index && arg.value != 'range' { + continue + } + return false + } + if resolved_target_name.contains('.') || target_name.contains('.') { + return true + } + receiver_type := types.unwrap_pointer(g.usable_expr_type(g.a.child(selector, 0))) + if g.resolve_method_name(receiver_type.name(), selector.value).len > 0 { + return true + } + return false +} + +fn (g &FlatGen) method_name_by_receiver_param_type(receiver_type types.Type, method string) ?string { + clean_receiver := concrete_receiver_type(receiver_type) + receiver_ct := g.tc.c_type(clean_receiver) + mut candidates := []string{} + for name, params in g.fn_decl_param_types { + if !name.contains('.') || name.contains('__') || name.all_after_last('.') != method + || params.len == 0 { + continue + } + param_receiver := concrete_receiver_type(params[0]) + if g.tc.c_type(param_receiver) != receiver_ct + && !g.type_names_match(param_receiver, clean_receiver) { + continue + } + candidates << name + } + if candidates.len == 0 { + return none + } + candidates.sort() + mut best := candidates[0] + mut best_score := g.receiver_param_method_candidate_score(best) + for candidate in candidates[1..] { + score := g.receiver_param_method_candidate_score(candidate) + if score > best_score { + best = candidate + best_score = score + } + } + return best +} + +fn (g &FlatGen) receiver_param_method_candidate_score(name string) int { + mut score := 0 + if g.tc.cur_module.len > 0 && g.tc.cur_module != 'main' && g.tc.cur_module != 'builtin' + && name.starts_with('${g.tc.cur_module}.') { + score += 100 + } + if name.contains('_') { + score += 10 + } + if !name.contains('[') { + score += 5 + } + return score +} + +fn (g &FlatGen) specialized_generic_method_name_for_call_with_arg_count(id flat.NodeId, method_name string, arg_count int) ?string { + if g.skip_generics { + return none + } + if !method_name.contains('.') { + return none + } + receiver := method_name.all_before_last('.') + method := method_name.all_after_last('.') + if receiver.len == 0 || method.len == 0 { + return none + } + // Array and fixed-array receivers use brackets as part of their ordinary type + // spelling. They are not generic applications, and their checker-selected + // method must not be replaced with an unrelated generic candidate that happens + // to have the same return type and method name. + receiver_bracket := receiver.index_u8(`[`) + if receiver_bracket == 0 || (receiver_bracket > 0 && receiver[receiver_bracket - 1] == `.`) { + return none + } + mut call_ret := g.call_default_return_type(id) + if g.type_contains_generic_placeholder(call_ret) && g.expected_expr_type !is types.Void + && g.expected_expr_type !is types.Unknown { + call_ret = g.expected_expr_type + } + if type_arg := generic_method_type_arg_from_return(call_ret) { + for arg in [type_arg, type_arg.all_after_last('.')] { + for candidate in ['${receiver}[${arg}].${method}', + '${receiver.all_after_last('.')}[${arg}].${method}'] { + if candidate in g.tc.fn_param_types || candidate in g.tc.fn_ret_types { + return candidate + } + } + } + } + for lookup_receiver in generic_method_lookup_receivers(receiver) { + candidates := g.generic_method_candidates[generic_method_candidate_key(lookup_receiver, + method)] or { continue } + for candidate in candidates { + if arg_count >= 0 && candidate.params.len != arg_count { + continue + } + if g.type_names_match(call_ret, candidate.ret) + || call_ret.name() == candidate.ret.name() { + return candidate.name + } + } + } + return none +} + +fn (g &FlatGen) specialized_generic_method_name_for_call_args(node flat.Node, method_name string, arg_count int) ?string { + if g.skip_generics { + return none + } + if !method_name.contains('.') { + return none + } + receiver := method_name.all_before_last('.') + method := method_name.all_after_last('.') + if receiver.len == 0 || method.len == 0 { + return none + } + mut best := '' + mut best_score := 0 + mut best_preference := -1 + mut ambiguous := false + for lookup_receiver in generic_method_lookup_receivers(receiver) { + candidates := g.generic_method_candidates[generic_method_candidate_key(lookup_receiver, + method)] or { continue } + for candidate in candidates { + if arg_count >= 0 && candidate.params.len != arg_count { + continue + } + score := g.generic_method_candidate_arg_score(node, candidate) + if score <= 0 { + continue + } + preference := generic_method_candidate_receiver_preference(candidate.name, receiver) + if score > best_score || (score == best_score && preference > best_preference) { + best = candidate.name + best_score = score + best_preference = preference + ambiguous = false + } else if score == best_score && preference == best_preference && candidate.name != best { + ambiguous = true + } + } + } + if best_score > 0 && !ambiguous { + return best + } + return none +} + +fn generic_method_lookup_receivers(receiver string) []string { + mut result := []string{} + mut roots := [receiver] + base, _, is_generic := parse_shared_generic_app_parts(receiver) + if is_generic && base.len > 0 { + roots << base + } + for root in roots { + if root.len > 0 && root !in result { + result << root + } + short := root.all_after_last('.') + if short.len > 0 && short !in result { + result << short + } + } + return result +} + +fn generic_method_candidate_receiver_preference(candidate_name string, receiver string) int { + candidate_receiver := candidate_name.all_before_last('.') + if candidate_receiver.len == 0 { + return 0 + } + if candidate_receiver == receiver { + return 30 + } + candidate_base, _, candidate_is_generic := parse_shared_generic_app_parts(candidate_receiver) + receiver_generic_base, _, receiver_is_generic := parse_shared_generic_app_parts(receiver) + receiver_base := if receiver_is_generic { receiver_generic_base } else { receiver } + if candidate_is_generic { + if candidate_base == receiver_base { + return 25 + } + if candidate_base == receiver_base.all_after_last('.') { + return 15 + } + } + if candidate_receiver == receiver.all_after_last('.') { + return 10 + } + return 0 +} + +fn (g &FlatGen) generic_method_candidate_arg_score(node flat.Node, candidate GenericMethodCandidate) int { + params := candidate.params + explicit_start := g.generic_method_candidate_explicit_arg_start(node, params.len) or { + return -1 + } + mut score := 0 + // The receiver usually does not carry method-level generic arguments. Concrete + // generic receivers are handled before this fallback. + for i in 1 .. params.len { + arg_child_idx := explicit_start + i - 1 + if arg_child_idx >= int(node.children_count) { + return -1 + } + arg_id := g.a.child(&node, arg_child_idx) + arg_node := g.a.nodes[int(arg_id)] + arg_type := g.const_type_for_arg_node(arg_node) or { g.usable_expr_type(arg_id) } + arg_score := g.generic_method_param_arg_score(params[i], arg_type, arg_node) + if arg_score < 0 { + return -1 + } + score += arg_score + } + score += g.generic_method_candidate_type_arg_score(node, candidate.name, explicit_start) + return score +} + +fn (g &FlatGen) generic_method_candidate_explicit_arg_start(node flat.Node, params_len int) ?int { + if node.children_count == 0 || params_len == 0 { + return none + } + if params_len == int(node.children_count) { + return 1 + } + if params_len == int(node.children_count) - 1 { + return 2 + } + return none +} + +fn (g &FlatGen) generic_method_candidate_type_arg_score(node flat.Node, candidate_name string, explicit_start int) int { + if !candidate_name.contains('.') { + return 0 + } + receiver := candidate_name.all_before_last('.') + _, type_args, ok := g.shared_generic_app_parts(receiver) + if !ok || type_args.len == 0 { + return 0 + } + mut score := 0 + for i in explicit_start .. node.children_count { + arg_id := g.a.child(&node, i) + arg_node := g.a.nodes[int(arg_id)] + actual := types.unwrap_pointer(g.const_type_for_arg_node(arg_node) or { + g.usable_expr_type(arg_id) + }) + for type_arg in type_args { + if g.generic_method_type_arg_matches_actual(type_arg, actual) { + score += 30 + } + } + } + return score +} + +fn (g &FlatGen) generic_method_type_arg_matches_actual(type_arg string, actual types.Type) bool { + clean := trimmed_space(type_arg) + if clean.len == 0 || actual is types.Unknown || actual is types.Void { + return false + } + expected := g.tc.parse_type(clean) + if !decl_annotation_is_unusable(expected, clean) + && g.generic_method_arg_types_match(actual, expected) { + return true + } + actual_name := actual.name() + return actual_name == clean || actual_name.all_after_last('.') == clean.all_after_last('.') +} + +fn (g &FlatGen) generic_method_param_arg_score(param types.Type, actual types.Type, arg_node flat.Node) int { + if param is types.Unknown || actual is types.Unknown || param is types.Void + || actual is types.Void { + return 0 + } + if g.generic_method_arg_types_match(actual, param) { + return 20 + } + if param is types.Pointer { + if actual is types.Pointer { + if g.generic_method_arg_types_match(actual.base_type, param.base_type) { + return 20 + } + } else if g.generic_method_arg_types_match(actual, param.base_type) { + return 20 + } + } + if actual is types.Pointer && g.generic_method_arg_types_match(actual.base_type, param) { + return 12 + } + clean_param := types.unwrap_pointer(param) + if (arg_node.kind == .int_literal || arg_node.kind == .float_literal) + && g.types_numeric_compatible(actual, clean_param) { + return 1 + } + if actual is types.Primitive && clean_param is types.Primitive + && g.types_numeric_compatible(actual, clean_param) { + return 1 + } + return 0 +} + +fn (g &FlatGen) generic_method_arg_types_match(actual types.Type, expected types.Type) bool { + if g.type_names_match(actual, expected) { + return true + } + if actual is types.Alias && g.generic_method_arg_types_match(actual.base_type, expected) { + return true + } + if expected is types.Alias && g.generic_method_arg_types_match(actual, expected.base_type) { + return true + } + return false +} + +fn generic_method_type_arg_from_return(ret types.Type) ?string { + if ret is types.Pointer { + base_name := ret.base_type.name() + if base_name.len > 0 && base_name != 'void' { + return '&${base_name}' + } + } + if ret is types.ResultType { + name := ret.base_type.name() + if name.len > 0 && name != 'void' { + return name + } + } + if ret is types.OptionType { + name := ret.base_type.name() + if name.len > 0 && name != 'void' { + return name + } + } + return none +} + +// static_method_fn_name resolves `Type.method(...)` static calls where `Type` is a +// named type, struct, enum, sum type or type alias (e.g. `SimdFloat4.new` for +// `type SimdFloat4 = vec.Vec4[f32]`). Returns the fn key, or none if `type_ident` +// is not a type or has no such static method. +fn (g &FlatGen) static_method_fn_name(type_ident string, method string) ?string { + qtype := g.tc.qualify_name(type_ident) + is_type := type_ident in g.tc.type_aliases || qtype in g.tc.type_aliases + || type_ident in g.tc.structs || qtype in g.tc.structs || type_ident in g.tc.enum_names + || qtype in g.tc.enum_names || type_ident in g.tc.sum_types || qtype in g.tc.sum_types + if !is_type { + return none + } + // Prefer the module-qualified key: a static method defined in the current (or the + // type's) module is emitted under its qualified C name (`game__Animation__load`), + // so the call must resolve to the same qualified key even though an unqualified + // alias (`Animation.load`) may also be registered. + qdirect := '${qtype}.${method}' + if qtype != type_ident && (qdirect in g.tc.fn_ret_types || qdirect in g.tc.fn_param_types) { + return qdirect + } + direct := '${type_ident}.${method}' + if direct in g.tc.fn_ret_types || direct in g.tc.fn_param_types { + return direct + } + if qdirect in g.tc.fn_ret_types || qdirect in g.tc.fn_param_types { + return qdirect + } + return none +} + +fn (g &FlatGen) resolve_method_name(type_name string, method string) string { + direct := '${type_name}.${method}' + if direct in g.tc.fn_param_types || direct in g.tc.fn_ret_types { + return direct + } + if generic_method := g.resolve_concrete_generic_method_name(type_name, method) { + return generic_method + } + for receiver in cgen_flattened_generic_receiver_short_variants(type_name) { + candidate := '${receiver}.${method}' + if candidate in g.tc.fn_param_types || candidate in g.tc.fn_ret_types { + return candidate + } + } + if g.tc.cur_module.len > 0 && g.tc.cur_module != 'main' && g.tc.cur_module != 'builtin' + && !type_name.contains('.') { + qualified := '${g.tc.cur_module}.${type_name}.${method}' + if qualified in g.tc.fn_param_types || qualified in g.tc.fn_ret_types { + return qualified + } + lowered := g.cname(qualified) + if lowered in g.tc.fn_param_types || lowered in g.tc.fn_ret_types { + return lowered + } + } + // A receiver type parsed in an importing module may carry only the + // import-local qualifier (`pool.PoolProcessor`) rather than the full module + // path (`sync.pool.PoolProcessor`) that the method was registered under. + // Canonicalize via the unique short-name index so the C method name matches + // the emitted definition. + if type_name.contains('.') { + if full := g.tc.canonical_qualified_type_name(type_name) { + if full != type_name { + canonical := '${full}.${method}' + if canonical in g.tc.fn_param_types || canonical in g.tc.fn_ret_types { + return canonical + } + } + } + } + return '' +} + +fn (g &FlatGen) resolve_concrete_generic_method_name(type_name string, method string) ?string { + base, args, ok := g.shared_generic_app_parts(type_name) + if !ok || args.len == 0 { + return none + } + for suffix in generic_receiver_type_suffix_variants(args) { + mut candidates := ['${base}_${suffix}.${method}'] + if base.contains('.') { + base_mod := base.all_before_last('.') + base_short := base.all_after_last('.') + candidates << '${base_short}_${suffix}.${method}' + candidates << '${base_mod}.${base_short}_${suffix}.${method}' + } + for candidate in candidates { + if candidate in g.tc.fn_param_types || candidate in g.tc.fn_ret_types { + return candidate + } + lowered := g.cname(candidate) + if lowered in g.tc.fn_param_types || lowered in g.tc.fn_ret_types { + return lowered + } + } + } + return none +} + +fn generic_receiver_type_suffixes(args []string) string { + variants := generic_receiver_type_suffix_variants(args) + if variants.len > 0 { + return variants[0] + } + return '' +} + +fn generic_receiver_type_suffix_variants(args []string) []string { + mut raw_parts := []string{cap: args.len} + mut parts := []string{cap: args.len} + for arg in args { + raw := generic_receiver_type_arg_short(arg).replace('[]', 'Array_').replace('&', 'ptr_') + raw_parts << raw + parts << c_name(raw) + } + mut variants := []string{} + codegen_push_unique(mut variants, raw_parts.join('__')) + codegen_push_unique(mut variants, parts.join('__')) + codegen_push_unique(mut variants, raw_parts.join('_')) + codegen_push_unique(mut variants, parts.join('_')) + return variants +} + +fn generic_receiver_type_arg_short(type_arg string) string { + clean := trimmed_space(type_arg) + if clean.starts_with('[]') { + return 'Array_${generic_receiver_type_arg_short(clean[2..])}' + } + if clean.starts_with('&') { + return 'ptr_${generic_receiver_type_arg_short(clean[1..])}' + } + if clean.starts_with('map[') { + bracket_end := shared_generic_matching_bracket(clean, 3) + if bracket_end < clean.len - 1 { + key := generic_receiver_type_arg_short(clean[4..bracket_end]) + value := generic_receiver_type_arg_short(clean[bracket_end + 1..]) + return 'Map_${key}_${value}' + } + } + if clean.starts_with('?') { + return 'Option_${generic_receiver_type_arg_short(clean[1..])}' + } + if clean.starts_with('!') { + return 'Result_${generic_receiver_type_arg_short(clean[1..])}' + } + if fixed := generic_receiver_fixed_array_type_arg_short(clean) { + return fixed + } + if clean.contains('(') || clean.contains(' ') { + return sanitize_generic_receiver_type_fragment(clean) + } + base, args, ok := parse_shared_generic_app_parts(clean) + if ok { + mut parts := [generic_receiver_type_arg_short(base)] + for arg in args { + parts << generic_receiver_type_arg_short(arg) + } + return parts.join('_') + } + if clean.contains('.') { + return clean.all_after_last('.') + } + return clean +} + +fn generic_receiver_fixed_array_type_arg_short(type_arg string) ?string { + clean := trimmed_space(type_arg) + if !clean.starts_with('[') { + return none + } + close_idx := clean.index_u8(`]`) + if close_idx <= 1 || close_idx + 1 >= clean.len { + return none + } + len_text := trimmed_space(clean[1..close_idx]) + elem_text := clean[close_idx + 1..].trim_space() + if len_text.len == 0 || elem_text.len == 0 { + return none + } + elem := generic_receiver_type_arg_short(elem_text) + return '${elem}_${len_text}' +} + +fn sanitize_generic_receiver_type_fragment(typ string) string { + mut out := []u8{} + mut prev_us := false + mut i := 0 + for i < typ.len { + c := typ[i] + if (c >= `A` && c <= `Z`) || (c >= `a` && c <= `z`) || (c >= `0` && c <= `9`) { + out << c + prev_us = false + i++ + } else if c == `[` && i + 1 < typ.len && typ[i + 1] == `]` { + for ch in 'Array_'.bytes() { + out << ch + } + prev_us = false + i += 2 + } else if c == `&` { + for ch in 'ptr_'.bytes() { + out << ch + } + prev_us = false + i++ + } else if c == `.` { + for out.len > 0 { + last := out[out.len - 1] + if (last >= `A` && last <= `Z`) || (last >= `a` && last <= `z`) + || (last >= `0` && last <= `9`) { + out.delete_last() + } else { + break + } + } + i++ + } else { + if !prev_us { + out << `_` + prev_us = true + } + i++ + } + } + mut s := out.bytestr() + for s.starts_with('_') { + s = s[1..] + } + for s.ends_with('_') { + s = s[..s.len - 1] + } + return s +} + +fn cgen_flattened_generic_receiver_short_variants(receiver_type string) []string { + clean := trimmed_space(receiver_type) + if clean.len == 0 || !clean.contains('__') || !clean.contains('_') { + return []string{} + } + module_name := if clean.contains('.') { clean.all_before_last('.') } else { '' } + leaf := if clean.contains('.') { clean.all_after_last('.') } else { clean } + parts := cgen_flattened_generic_receiver_leaf_parts(leaf) + mut changed := false + mut short_parts := []string{cap: parts.len} + for part in parts { + if part.contains('__') { + short_parts << part.all_after_last('__') + changed = true + } else { + short_parts << part + } + } + if !changed { + return []string{} + } + short_leaf := short_parts.join('_') + mut variants := [short_leaf] + if module_name.len > 0 { + variants << '${module_name}.${short_leaf}' + } + return variants +} + +fn cgen_flattened_generic_receiver_leaf_parts(leaf string) []string { + mut parts := []string{} + mut start := 0 + mut i := 0 + for i < leaf.len { + if leaf[i] == `_` { + if i + 1 < leaf.len && leaf[i + 1] == `_` { + i += 2 + continue + } + parts << leaf[start..i] + i++ + start = i + continue + } + i++ + } + parts << leaf[start..] + return parts +} + +fn c_string_pointer_base_arg(base types.Type) bool { + clean := if base is types.Alias { base.base_type } else { base } + if clean is types.Char { + return true + } + return clean is types.Primitive && types.Type(clean).name() == 'u8' +} + +fn c_type_is_pointer_like(typ types.Type) bool { + mut clean := typ + for { + if clean is types.Alias { + clean = clean.base_type + continue + } + return clean is types.Pointer + } + return false +} + +// voidptr_value_arg_needs_address mirrors the checker rules that let a voidptr +// parameter borrow an addressable value. C calls restrict the implicit borrow +// to struct values; V calls also accept other addressable runtime values. +fn (g &FlatGen) voidptr_value_arg_needs_address(arg_id flat.NodeId, arg_node flat.Node, actual types.Type, expected types.Type, is_c_call bool) bool { + if !type_is_void_pointer(expected) || c_type_is_pointer_like(actual) + || g.arg_is_null_pointer_literal(arg_id, arg_node) + || g.fn_value_arg_passes_direct_to_voidptr(arg_id, arg_node, actual, expected) + || g.voidptr_method_value_arg(arg_id, expected) || !g.expr_is_addressable(arg_id) { + return false + } + if is_c_call && cgen_unalias_type(actual) !is types.Struct { + return false + } + if arg_node.kind == .ident { + if g.local_storage_is_pointer(arg_node.value) { + return false + } + if global_type := g.global_type_for_ident(arg_node.value) { + if c_type_is_pointer_like(global_type) { + return false + } + } + } + return true +} + +fn (g &FlatGen) addressed_const_arg_value_type(arg_id flat.NodeId, expected types.Type) types.Type { + if type_is_void_pointer(expected) { + actual := g.usable_expr_type(arg_id) + if actual !is types.Unknown && actual !is types.Void { + return actual + } + } + return types.unwrap_pointer(expected) +} + +fn (g &FlatGen) c_char_literal_arg(id flat.NodeId) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(id)] + if node.kind == .paren && node.children_count > 0 { + return g.c_char_literal_arg(g.a.child(&node, 0)) + } + return node.kind == .char_literal && node.value.starts_with('c:') +} + +fn (g &FlatGen) c_string_pointer_arg(arg_node flat.Node, expected types.Type) bool { + if expected !is types.Pointer { + return false + } + if !c_string_pointer_base_arg(types.unwrap_pointer(expected)) { + return false + } + if arg_node.kind == .char_literal { + return arg_node.value.starts_with('c:') + } + if arg_node.kind == .ident { + const_name := g.const_ref_name(arg_node.value) + if const_name.len == 0 { + return false + } + if const_id := g.const_vals[const_name] { + const_node := g.a.nodes[int(const_id)] + return const_node.kind == .char_literal && const_node.value.starts_with('c:') + } + } + return false +} + +fn (g &FlatGen) arg_is_null_pointer_literal(arg_id flat.NodeId, arg_node flat.Node) bool { + return g.expr_is_nil_value(arg_id) + || (arg_node.kind == .int_literal && (arg_node.value == '0' || arg_node.value.len == 0)) + || (arg_node.kind == .selector && arg_node.value == 'NULL' && arg_node.children_count > 0 + && g.a.child_node(&arg_node, 0).kind == .ident && g.a.child_node(&arg_node, 0).value == 'C') +} + +fn (mut g FlatGen) gen_pointer_builtin_method_call(node flat.Node, fn_node &flat.Node, base_type types.Type) bool { + receiver := pointer_builtin_receiver_name_for_c(base_type) + if receiver.len == 0 { + return false + } + method := fn_node.value + if receiver in ['charptr', 'byteptr'] && method in ['vstring', 'vstring_with_len'] { + g.write(g.cname('${receiver}.${method}')) + g.write('(') + g.gen_expr(g.a.child(fn_node, 0)) + for i in 1 .. node.children_count { + g.write(', ') + g.gen_expr(g.a.child(&node, i)) + } + g.write(')') + return true + } + if receiver in ['byteptr', 'voidptr'] && method == 'vbytes' { + g.write(g.cname('${receiver}.${method}')) + g.write('(') + g.gen_expr(g.a.child(fn_node, 0)) + for i in 1 .. node.children_count { + g.write(', ') + g.gen_expr(g.a.child(&node, i)) + } + g.write(')') + return true + } + return false +} + +fn pointer_builtin_receiver_name_for_c(typ types.Type) string { + if typ is types.Alias { + if typ.name in ['charptr', 'byteptr', 'voidptr'] { + return typ.name + } + return pointer_builtin_receiver_name_for_c(typ.base_type) + } + if typ is types.Pointer { + base := typ.base_type + if base is types.Alias { + if base.name == 'byte' { + return 'byteptr' + } + return pointer_builtin_receiver_name_for_c(base) + } + if base is types.Char { + return 'charptr' + } + if base is types.Void { + return 'voidptr' + } + if base is types.Primitive && types.Type(base).name() == 'u8' { + return 'byteptr' + } + } + if typ is types.Char { + return 'charptr' + } + if typ is types.Void { + return 'voidptr' + } + if typ is types.Primitive && types.Type(typ).name() == 'u8' { + return 'byteptr' + } + return '' +} + +fn (mut g FlatGen) gen_special_c_callback_arg(fn_name string, arg_idx int, arg_id flat.NodeId, expected_param types.Type) bool { + clean_name := fn_name.trim_string_left('C.').all_after_last('.') + if clean_name == 'mbedtls_ssl_conf_sni' && arg_idx == 1 { + g.write('(int (*)(void *, mbedtls_ssl_context *, const unsigned char *, size_t))') + g.gen_expr(arg_id) + return true + } + // Only convert to `(void*)` for an actual C-callback slot. A V `fn (...) ...` + // parameter is generated as a `_fn_ptr_*` typedef and must receive the function + // pointer directly: `(void*)foo` is an object-pointer-to-function-pointer cast that + // strict C rejects and that is not portable across ABIs. C functions (and loosely + // typed `voidptr` slots) still need it, because C uses the header prototype. + // `fn_type_from` is alias-aware, so a `type Cb = fn ()` parameter is recognised too. + if _ := fn_type_from(expected_param) { + return false + } + if expected_param !is types.Pointer { + return false + } + if _ := g.sum_type_for_expected_value(expected_param) { + return false + } + // A V function passed by name to a C function (a callback) must be cast: the V + // declaration's parameter type (often `voidptr`) is ignored by C in favour of the + // real header prototype, so the bare name trips -Wincompatible-function-pointer-types. + // `(void*)` converts cleanly to any function-pointer parameter. + if int(arg_id) >= 0 { + arg_node := g.a.nodes[int(arg_id)] + if arg_node.kind == .ident { + looked_up := g.tc.cur_scope.lookup(arg_node.value) or { types.Type(types.void_) } + if looked_up is types.Void { + call_name := g.call_key(arg_id, arg_node.value) + fn_key := if call_name in g.tc.fn_ret_types { + call_name + } else if arg_node.value in g.tc.fn_ret_types { + arg_node.value + } else { + '' + } + if fn_key.len > 0 && !fn_key.starts_with('C.') { + g.write('(void*)') + g.write(g.cname(fn_key)) + return true + } + } + } + } + return false +} + +fn (mut g FlatGen) spawn_wrapper_decls() { + mut seen := map[string]bool{} + for def in g.spawn_wrapper_defs { + if def in seen { + continue + } + seen[def] = true + g.writeln(def) + } + if g.spawn_wrapper_defs.len > 0 { + g.writeln('') + } +} + +fn (mut g FlatGen) add_spawn_wrapper_def(def string) { + if g.parallel_chunk_wrapper_capture >= 0 { + g.parallel_chunk_wrapper_defs[g.parallel_chunk_wrapper_capture].spawn << def.clone() + } + if g.spawn_wrapper_defs_seen[def] { + return + } + g.spawn_wrapper_defs_seen[def] = true + g.spawn_wrapper_defs << def +} + +fn (mut g FlatGen) add_callback_wrapper_def(def string) { + if g.parallel_chunk_wrapper_capture >= 0 { + g.parallel_chunk_wrapper_defs[g.parallel_chunk_wrapper_capture].callback << def.clone() + } + if g.callback_wrapper_defs_seen[def] { + return + } + g.callback_wrapper_defs_seen[def] = true + g.callback_wrapper_defs << def +} + +// expr_is_addressable reports whether an expression denotes a stable lvalue whose address +// outlives the enclosing statement expression — a variable, a field/index access reaching one, +// or a dereference. Rvalues (struct literals, calls, ...) only have temporary storage, so their +// address must not be captured in a method value's static receiver slot. +fn (g &FlatGen) expr_is_addressable(id flat.NodeId) bool { + if int(id) < 0 { + return false + } + node := g.a.nodes[int(id)] + return match node.kind { + .ident { + true + } + .index { + node.value != 'range' && node.children_count > 0 + && g.expr_is_addressable(g.a.child(&node, 0)) + } + .selector { + node.children_count > 0 && g.expr_is_addressable(g.a.child(&node, 0)) + } + .prefix { + node.op == .mul + } + .paren { + node.children_count > 0 && g.expr_is_addressable(g.a.child(&node, 0)) + } + else { + false + } + } +} + +// expr_is_stable_for_reuse reports whether evaluating an expression repeatedly is free of +// observable side effects. This is intentionally narrower than addressability: `xs[next()]` +// is an lvalue, but the index must still be evaluated exactly once. +fn (g &FlatGen) expr_is_stable_for_reuse(id flat.NodeId) bool { + if int(id) < 0 { + return false + } + node := g.a.nodes[int(id)] + return match node.kind { + .ident, .int_literal, .float_literal, .bool_literal, .char_literal, .string_literal, + .nil_literal, .none_expr, .enum_val, .sizeof_expr, .typeof_expr { + true + } + .selector, .paren, .cast_expr { + node.children_count > 0 && g.expr_is_stable_for_reuse(g.a.child(&node, 0)) + } + .prefix { + node.children_count > 0 && g.expr_is_stable_for_reuse(g.a.child(&node, 0)) + } + .index { + node.children_count >= 2 && g.expr_is_stable_for_reuse(g.a.child(&node, 0)) + && g.expr_is_stable_for_reuse(g.a.child(&node, 1)) + } + else { + false + } + } +} + +fn (mut g FlatGen) gen_mut_sum_lvalue_arg(arg_id flat.NodeId, expected types.Type) bool { + mut lvalue_id := arg_id + if int(arg_id) >= 0 && int(arg_id) < g.a.nodes.len { + arg_node := g.a.nodes[int(arg_id)] + if arg_node.kind == .prefix && arg_node.op == .amp && arg_node.children_count > 0 { + lvalue_id = g.a.child(&arg_node, 0) + } + } + base0 := if expected is types.Pointer { + expected.base_type + } else { + return false + } + base := if base0 is types.Alias { base0.base_type } else { base0 } + if base !is types.SumType { + return false + } + // A `mut value SumType` parameter is already lowered to `SumType* value`. + // Forward that pointer directly; taking its address would pass a `SumType**` + // and leave the caller's sum value unchanged. + lvalue_node := g.a.nodes[int(lvalue_id)] + if lvalue_node.kind == .ident && g.current_param_is_mut(lvalue_node.value) { + g.write(g.cname(lvalue_node.value)) + return true + } + if !g.expr_is_addressable(lvalue_id) { + return false + } + actual0 := g.tc.resolve_type(lvalue_id) + if actual0 is types.Pointer { + return false + } + storage0 := if declared := g.selector_declared_type(lvalue_id) { declared } else { actual0 } + storage := if storage0 is types.Alias { storage0.base_type } else { storage0 } + if storage !is types.SumType || !g.type_names_match(storage, base) { + return false + } + g.write('&') + if !g.gen_sum_storage_lvalue_arg(lvalue_id) { + g.gen_expr(lvalue_id) + } + return true +} + +fn (mut g FlatGen) gen_sum_storage_lvalue_arg(arg_id flat.NodeId) bool { + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(arg_id)] + if node.kind != .selector || node.children_count == 0 { + return false + } + if _ := g.selector_declared_type(arg_id) { + // handled below + } else { + return false + } + base_id := g.a.child(&node, 0) + base := g.a.nodes[int(base_id)] + needs_paren := base.kind !in [.ident, .selector] + if needs_paren { + g.write('(') + } + g.gen_expr(base_id) + if needs_paren { + g.write(')') + } + mut is_ptr := false + if base.kind == .ident { + if typ := g.tc.cur_scope.lookup(base.value) { + is_ptr = typ is types.Pointer + } + } else if base.kind == .selector { + if declared := g.selector_declared_type(base_id) { + is_ptr = declared is types.Pointer + } else { + resolved := g.tc.resolve_type(base_id) + is_ptr = resolved is types.Pointer + } + } else { + resolved := g.tc.resolve_type(base_id) + is_ptr = resolved is types.Pointer + } + if node.op == .arrow || is_ptr { + g.write('->') + } else { + g.write('.') + } + g.write(g.cname(node.value)) + return true +} + +// gen_method_value_closure handles a method used as a *value* (e.g. `game.draw` +// passed where a `fn ()` callback is expected) rather than called. A plain struct +// field access can't represent the bound receiver, so it stores the receiver in a +// per-instance closure context and yields a wrapper function that invokes the method. +// Returns false when the selector is an ordinary field access (handled normally). +fn (mut g FlatGen) gen_method_value_closure(selector_id flat.NodeId, base_id flat.NodeId, base_type types.Type, method string, borrow_receiver bool, clone_receiver_fn string) bool { + clean := types.unwrap_all_pointers(base_type) + mut receiver_name := '' + mut is_interface_receiver := false + if clean is types.Struct { + receiver_name = clean.name + } else if clean is types.Interface { + receiver_name = clean.name + is_interface_receiver = true + } else if clean is types.Alias { + receiver_name = clean.name + base_receiver_name := clean.base_type.name() + if g.resolve_method_name(receiver_name, method).len == 0 + && g.resolve_method_name(base_receiver_name, method).len > 0 { + // A true alias (`type A = S`) inherits methods declared on its + // underlying type. Use that declaration's receiver/signature for + // the method-value wrapper when the alias has no method of its own. + receiver_name = base_receiver_name + } else if alias_method := g.find_alias_method(base_receiver_name, method) { + receiver_name = alias_method.all_before_last('.') + } + } else if clean is types.String || clean is types.Primitive || clean is types.Char + || clean is types.Rune { + receiver_name = clean.name() + } else { + alias_method := g.find_alias_method(clean.name(), method) or { return false } + receiver_name = alias_method.all_before_last('.') + } + // A real field shadows any same-named method: that's a field access, not a value. + if _ := g.field_type(base_type, method) { + return false + } + method_key := if is_interface_receiver { + '${receiver_name}.${method}' + } else { + g.resolve_method_name(receiver_name, method) + } + mut params := []types.Type{} + mut ret := types.Type(types.void_) + mut cname := '' + if is_interface_receiver { + if actual_params := g.fn_decl_param_types[method_key] { + params = actual_params.clone() + ret = g.fn_decl_ret_types[method_key] or { + g.tc.fn_ret_types[method_key] or { types.Type(types.void_) } + } + } else { + params = g.interface_method_param_types(method_key) or { return false } + decl_key := g.interface_method_signature_key(receiver_name, method) or { method_key } + ret = g.tc.fn_ret_types[decl_key] or { types.Type(types.void_) } + g.add_spawn_wrapper_def('${g.interface_dispatch_signature(receiver_name, + g.cname(receiver_name), method)};') + if !g.should_emit_interface_dispatch(receiver_name, method) { + g.add_spawn_wrapper_def(g.interface_dispatch_def_string(receiver_name, + g.cname(receiver_name), method)) + } + } + cname = g.cname(method_key) + } else if method_key.len > 0 { + params = g.tc.fn_param_types[method_key] or { return false } + ret = g.tc.fn_ret_types[method_key] or { types.Type(types.void_) } + cname = g.cname(method_key) + } else if ci := g.tc.generic_method_value_info['${receiver_name}.${method}'] { + // Generic receiver (`Box[int]`): the open `Box[T].get` registration is gone by + // cgen, so use the substituted params/return the checker stashed, plus the + // monomorphised C name `g.cname('Box[int].get')` == `Box_int__get`. + params = ci.params.clone() + // The receiver param is the un-substituted open `Box[T]`; replace it with the + // concrete receiver type (keeping the method's pointer-ness) so its C name + // resolves to `Box_int` rather than the template `Box`. + if params.len > 0 { + recv_concrete := types.unwrap_pointer(base_type) + params[0] = if params[0] is types.Pointer { + types.Type(types.Pointer{ + base_type: recv_concrete + }) + } else { + recv_concrete + } + } + ret = ci.return_type + cname = g.cname('${receiver_name}.${method}') + } else { + return false + } + if params.len == 0 { + return false + } + recv_ct := g.tc.c_type(params[0]) + // Use the method's ABI return type (matching `cname`'s signature and the callback + // fn-pointer typedef): an option/result is `Optional_T`, a fixed array its + // `_v_ret_*` wrapper — not the bare `c_type` (`Optional`/`Array_fixed_*`). + ret_ct := g.fn_return_type_name(ret) + base_pointer_depth := cgen_type_pointer_depth(base_type) + receiver_pointer_depth := cgen_type_pointer_depth(params[0]) + base_node := g.a.node(base_id) + pointer_alias_to_local := base_node.kind == .ident && base_pointer_depth > 0 + && g.local_pointer_alias_source(base_node.value) != none + // Pointer-receiver method values still backed by addressable stack values need + // durable context storage. The transform heap-promotes callback-argument locals, + // so those arrive here as pointers and preserve receiver identity. A method value + // proven local uses the borrow marker for the same identity-preserving behavior. + receiver_value_copy := (receiver_pointer_depth > base_pointer_depth + && (!g.expr_is_addressable(base_id) || !borrow_receiver)) + || (pointer_alias_to_local && !borrow_receiver) + ctx_receiver_ct := if receiver_value_copy { + g.tc.c_type(types.unwrap_pointer(params[0])) + } else { + recv_ct + } + ctx_receiver_type := if receiver_value_copy { + types.unwrap_pointer(params[0]) + } else { + params[0] + } + ctx_receiver_needs_drop := g.tc.ownership_type_requires_destruction(ctx_receiver_type) + // The wrapper has translation-unit scope, so name it from the stable selector + // site and signature instead of the function-local temporary counter. Comptime + // expansion can reuse one selector node for methods with different concrete + // signatures, so the node id alone is not unique. + wrapper_key := '${method_key}|${ctx_receiver_ct}|${ret_ct}|${params.map(it.name()).join(',')}' + idx := '${int(selector_id)}_${callback_stable_key_hash(wrapper_key)}' + ctx_name := '_mvctx_${idx}' + wrap_name := '_mvwrap_${idx}' + drop_name := '_mvdrop_${idx}' + mut wparams := []string{} + mut receiver_arg := if receiver_value_copy { '&ctx->receiver' } else { 'ctx->receiver' } + if clone_receiver_fn.len > 0 { + receiver_arg = '${g.cname(clone_receiver_fn)}(&ctx->receiver)' + } + mut call_args := [receiver_arg] + for i in 1 .. params.len { + pt := g.tc.c_type(params[i]) + wparams << '${pt} a${i}' + call_args << 'a${i}' + } + wparam_str := if wparams.len == 0 { 'void' } else { wparams.join(', ') } + if !is_interface_receiver { + mut method_param_types := []string{cap: params.len} + for param in params { + method_param_types << g.tc.c_type(param) + } + g.add_spawn_wrapper_def('${ret_ct} ${cname}(${method_param_types.join(', ')});') + } + g.add_spawn_wrapper_def('typedef struct { ${ctx_receiver_ct} receiver; } ${ctx_name};') + if ctx_receiver_needs_drop { + drop_body := g.ownership_drop_value_to_string(ctx_receiver_type, 'ctx->receiver') + g.add_spawn_wrapper_def('static void ${drop_name}(void* data) { ${ctx_name}* ctx = (${ctx_name}*)data;\n${drop_body}}') + } + ret_prefix := if ret_ct == 'void' { '' } else { 'return ' } + g.add_spawn_wrapper_def('static ${ret_ct} ${wrap_name}(${wparam_str}) { ${ctx_name}* ctx = (${ctx_name}*)closure__g_closure.closure_get_data(); ${ret_prefix}${cname}(${call_args.join(', ')}); }') + fnptr_ct := if fnt := fn_type_from(g.expected_expr_type) { + g.value_c_type(fnt) + } else { + 'void*' + } + create_fn := if ctx_receiver_needs_drop { + 'closure__closure_create_with_data_and_drop' + } else { + 'closure__closure_create_with_data' + } + g.write('(${fnptr_ct})${create_fn}((void*)${wrap_name}, (void*)memdup(&(${ctx_name}){.receiver = ') + if clone_receiver_fn.len > 0 { + clone_fn_cname := g.cname(clone_receiver_fn) + if g.expr_is_addressable(base_id) { + g.write('${clone_fn_cname}((void*)&(') + g.gen_expr(base_id) + g.write('))') + } else { + tmp := g.tmp_count + g.tmp_count++ + receiver_tmp := '__method_receiver_${tmp}' + clone_tmp := '__method_receiver_clone_${tmp}' + g.write('({${ctx_receiver_ct} ${receiver_tmp} = ') + g.gen_expr(base_id) + g.write('; ${ctx_receiver_ct} ${clone_tmp} = ${clone_fn_cname}((void*)&${receiver_tmp}); ') + g.write(g.ownership_drop_value_to_string(ctx_receiver_type, receiver_tmp)) + g.write('${clone_tmp};})') + } + } else if receiver_value_copy { + // Store the receiver value directly in the context; the wrapper passes its + // durable field address instead of retaining `&local`. + if pointer_alias_to_local { + g.write('*') + } + g.gen_expr(base_id) + } else { + if receiver_pointer_depth > base_pointer_depth { + for _ in base_pointer_depth .. receiver_pointer_depth { + g.write('&(') + } + g.gen_expr(base_id) + for _ in base_pointer_depth .. receiver_pointer_depth { + g.write(')') + } + } else if base_pointer_depth > receiver_pointer_depth { + for _ in receiver_pointer_depth .. base_pointer_depth { + g.write('*(') + } + g.gen_expr(base_id) + for _ in receiver_pointer_depth .. base_pointer_depth { + g.write(')') + } + } else { + g.gen_expr(base_id) + } + } + g.write('}, sizeof(${ctx_name})), true') + if ctx_receiver_needs_drop { + g.write(', (void*)${drop_name}') + } + g.write(')') + return true +} + +fn (mut g FlatGen) callback_wrapper_decls() { + for def in g.callback_wrapper_defs { + g.writeln(def) + } + if g.callback_wrapper_defs.len > 0 { + g.writeln('') + } +} + +fn (mut g FlatGen) gen_spawn_expr(node flat.Node) { + if node.children_count == 0 { + g.write('(__v_thread){0}') + return + } + call_id := g.a.child(&node, 0) + call_node := g.a.nodes[int(call_id)] + if call_node.kind != .call || call_node.children_count == 0 { + g.write('(__v_thread){0}') + return + } + fn_node := g.a.child_node(&call_node, 0) + // The spawned call's return type: heap-captured by the wrapper so a later + // `[]thread T .wait()` can recover the value (void callees just return NULL). + // Use the callee's ABI return type, not the bare value type: an option/result + // return is `Optional_T` (not the generic `Optional`, whose payload is `int`) and a + // fixed-array return is its `_v_ret_*` wrapper struct. The wrapper mallocs and + // assigns this type, and `[]thread T.wait()` must read back the same layout. + ret_ct := g.fn_return_type_name(g.tc.resolve_type(call_id)) + mut wrapper := '' + mut arg_expr := 'NULL' + if fn_node.kind == .ident { + call_key := g.call_key(call_id, fn_node.value) + looked_up := g.tc.cur_scope.lookup(fn_node.value) or { types.Type(types.void_) } + if fn_type := fn_type_from(looked_up) { + if fn_type.params.len == int(call_node.children_count) - 1 { + mut packed_args := []SpawnPackedArg{} + for i, pt in fn_type.params { + arg_id := g.a.child(&call_node, i + 1) + packed_args << g.spawn_packed_arg_for_call_param(call_key, arg_id, pt, i) + } + g.emit_fn_value_spawn_expr(call_id, fn_node, fn_type, packed_args, ret_ct) + return + } + } + mut cfn := if looked_up !is types.Void && fn_type_from(looked_up) != none { + g.cname(fn_node.value) + } else if call_key in g.tc.fn_ret_types || call_key in g.tc.fn_param_types { + g.direct_call_name_for_call(call_id, call_key) + } else { + g.cname(fn_node.value) + } + if shadow_name := g.main_runtime_shadow_call_c_name(call_node, fn_node) { + cfn = shadow_name + } + if call_node.children_count == 1 { + if g.spawn_fn_literal_captures(cfn).len > 0 { + g.emit_args_spawn_expr(cfn, []SpawnPackedArg{}, ret_ct) + return + } + wrapper = g.ensure_noarg_spawn_wrapper(cfn, ret_ct) + } else { + // `spawn work(a, b)` packs the arguments into a heap struct so the + // spawned thread receives them, instead of silently dropping them. + param_types := g.param_types_for(call_key, fn_node.value) + if param_types.len > 0 && param_types.len == int(call_node.children_count) - 1 { + mut packed_args := []SpawnPackedArg{} + for i, pt in param_types { + arg_id := g.a.child(&call_node, i + 1) + packed_args << g.spawn_packed_arg_for_call_param(call_key, arg_id, pt, i) + } + g.emit_args_spawn_expr(cfn, packed_args, ret_ct) + return + } + } + } else if fn_type := g.spawn_selector_fn_value_type(g.a.child(&call_node, 0), fn_node) { + if fn_type.params.len == int(call_node.children_count) - 1 { + mut packed_args := []SpawnPackedArg{} + for i, pt in fn_type.params { + arg_id := g.a.child(&call_node, i + 1) + mut expected_ct := g.tc.c_type(pt) + if expected_ct.starts_with('fn_ptr:') { + expected_ct = g.resolve_fn_ptr_type(expected_ct) + } + packed_args << g.spawn_packed_arg_for_param(arg_id, pt, expected_ct, i) + } + g.emit_fn_value_spawn_expr(call_id, fn_node, fn_type, packed_args, ret_ct) + return + } + } else if module_call := g.selector_module_call_name(call_id, fn_node, call_node) { + // `spawn mod.fn(...)` is a module-qualified free function, not a method: its + // selector base is a module name. Pack and dispatch it like the `.ident` path + // above; the receiver-method branch below cannot resolve it and would + // otherwise fall through to a bogus `(void*)0` thread value. + cfn := g.direct_call_name_for_call(call_id, module_call) + if call_node.children_count == 1 { + wrapper = g.ensure_noarg_spawn_wrapper(cfn, ret_ct) + } else { + param_types := g.param_types_for(module_call, fn_node.value) + if param_types.len > 0 && param_types.len == int(call_node.children_count) - 1 { + mut packed_args := []SpawnPackedArg{} + for i, pt in param_types { + arg_id := g.a.child(&call_node, i + 1) + packed_args << g.spawn_packed_arg_for_call_param(module_call, arg_id, pt, i) + } + g.emit_args_spawn_expr(cfn, packed_args, ret_ct) + return + } + } + } else if fn_node.kind == .selector && fn_node.children_count > 0 { + base_id := g.a.child(fn_node, 0) + base_type := g.receiver_base_type(base_id) + clean_type := concrete_receiver_type(base_type) + method_name := g.resolved_method_name_for_spawn(clean_type, fn_node.value) + if method_name.len > 0 { + param_types := g.param_types_for(method_name, fn_node.value) + if param_types.len > 0 { + receiver_type := param_types[0] + receiver_ct := g.tc.c_type(receiver_type) + if call_node.children_count == 1 { + if receiver_type is types.Pointer { + if base_type is types.Pointer { + wrapper = g.ensure_receiver_spawn_wrapper(g.cname(method_name), + receiver_ct, ret_ct) + base_expr := g.expr_to_string(base_id) + arg_expr = '(${receiver_ct})(${base_expr})' + } else if g.expr_is_addressable(base_id) { + wrapper = g.ensure_receiver_spawn_wrapper(g.cname(method_name), + receiver_ct, ret_ct) + base_expr := g.expr_to_string(base_id) + arg_expr = '(${receiver_ct})(&(${base_expr}))' + } else { + receiver_value := g.spawn_packed_arg_for_call_param(method_name, + base_id, receiver_type, 0) + g.emit_args_spawn_expr(g.cname(method_name), [ + receiver_value, + ], ret_ct) + return + } + } else { + // Casting the void* thread argument straight to a struct type + // is invalid C, so copy the value receiver into the heap arg + // struct and dispatch through the argument-packing path. + receiver_value := g.spawn_packed_arg_for_call_param(method_name, base_id, + receiver_type, 0) + g.emit_args_spawn_expr(g.cname(method_name), [receiver_value], ret_ct) + return + } + } else if param_types.len == int(call_node.children_count) { + // `spawn recv.method(a, b)` packs the receiver and arguments + // into a heap struct rather than dropping the call. + receiver_arg := g.spawn_packed_arg_for_call_param(method_name, base_id, + receiver_type, 0) + mut packed_args := [receiver_arg] + for i in 1 .. param_types.len { + arg_id := g.a.child(&call_node, i) + packed_args << g.spawn_packed_arg_for_call_param(method_name, arg_id, + param_types[i], i) + } + g.emit_args_spawn_expr(g.cname(method_name), packed_args, ret_ct) + return + } + } + } + } else if fn_type := fn_type_from(g.tc.resolve_type(g.a.child(&call_node, 0))) { + if fn_type.params.len == int(call_node.children_count) - 1 { + mut packed_args := []SpawnPackedArg{} + for i, pt in fn_type.params { + arg_id := g.a.child(&call_node, i + 1) + mut expected_ct := g.tc.c_type(pt) + if expected_ct.starts_with('fn_ptr:') { + expected_ct = g.resolve_fn_ptr_type(expected_ct) + } + packed_args << g.spawn_packed_arg_for_param(arg_id, pt, expected_ct, i) + } + g.emit_fn_value_spawn_expr(call_id, g.a.child_node(&call_node, 0), fn_type, + packed_args, ret_ct) + return + } + } + if wrapper.len == 0 { + g.write('(__v_thread){0}') + return + } + g.write('__v_thread_spawn(${wrapper}, (void*)(${arg_expr}), NULL)') +} + +fn (g &FlatGen) spawn_selector_fn_value_type(callee_id flat.NodeId, fn_node flat.Node) ?types.FnType { + if fn_node.kind != .selector || fn_node.children_count == 0 { + return none + } + declared := g.selector_declared_type(callee_id) or { return none } + return fn_type_from(declared) +} + +// spawn_wrapper_body builds the thread-wrapper statement that invokes the spawned +// call and returns its result as a `void*`. When the callee returns a value, the +// result is heap-copied so `[]thread T .wait()` can recover it (the wait fn frees +// it); a void callee returns NULL. `post` runs after the call (e.g. `free(p);`). +fn spawn_wrapper_body(call_expr string, ret_ct string, post string) string { + return spawn_wrapper_body_with_pre(call_expr, ret_ct, '', post) +} + +fn spawn_wrapper_body_with_pre(call_expr string, ret_ct string, pre string, post string) string { + if ret_ct == 'void' || ret_ct.len == 0 { + return '${pre}${call_expr}; ${post}return NULL;' + } + return '${pre}${ret_ct}* __tr = (${ret_ct}*)__v_thread_alloc(sizeof(${ret_ct})); *__tr = ${call_expr}; ${post}return (void*)__tr;' +} + +fn (mut g FlatGen) ensure_noarg_spawn_wrapper(cfn string, ret_ct string) string { + key := 'noarg|${cfn}' + if name := g.spawn_wrapper_names[key] { + return name + } + name := g.cname('${cfn}_thread_wrapper') + g.spawn_wrapper_names[key] = name + body := spawn_wrapper_body('${cfn}()', ret_ct, '') + g.add_spawn_wrapper_def('static void* ${name}(void* arg) { (void)arg; ${body} }') + return name +} + +fn (mut g FlatGen) ensure_receiver_spawn_wrapper(cfn string, receiver_ct string, ret_ct string) string { + key := 'receiver|${cfn}|${receiver_ct}' + if name := g.spawn_wrapper_names[key] { + return name + } + name := g.cname('${cfn}_thread_wrapper') + g.spawn_wrapper_names[key] = name + body := spawn_wrapper_body('${cfn}((${receiver_ct})arg)', ret_ct, '') + g.add_spawn_wrapper_def('static void* ${name}(void* arg) { ${body} }') + return name +} + +// ensure_args_spawn_wrapper registers a heap-arg struct plus a thread wrapper +// that unpacks the struct, calls the function with all arguments, and frees the +// struct. Pointer rvalues are stored by value in the heap struct and passed as +// `&p->field`, so the wrapper shape is part of the cache key. +fn (mut g FlatGen) ensure_args_spawn_wrapper(cfn string, args []SpawnPackedArg, ret_ct string) (string, string) { + signature := spawn_packed_args_signature(args) + captures := g.spawn_fn_literal_captures(cfn) + capture_signature := spawn_closure_capture_signature(captures) + mut struct_name := g.cname('${cfn}_thread_args') + mut wrapper_name := g.cname('${cfn}_args_thread_wrapper') + if !spawn_packed_args_are_direct(args) { + suffix := spawn_packed_args_name_suffix(args) + struct_name = g.cname('${cfn}_thread_args_${suffix}') + wrapper_name = g.cname('${cfn}_args_thread_wrapper_${suffix}') + } + key := 'args|${cfn}|${signature}|captures:${capture_signature}' + if name := g.spawn_wrapper_names[key] { + return name, struct_name + } + g.spawn_wrapper_names[key] = wrapper_name + mut fields := '' + mut call_args := []string{} + for i, arg in args { + fields += '${arg.field_ct} a${i}; ' + call_args << arg.call_expr + } + for i, capture in captures { + fields += '${capture.field_ct} c${i}; ' + } + g.add_spawn_wrapper_def('typedef struct { ${fields}} ${struct_name};') + mut pre := '' + // Fn-literal capture globals are declared as per-thread lvalues by global_decls(), + // so restoring them here writes into this spawned thread's environment. + for i, capture in captures { + if capture.copy_array { + pre += 'memmove(${capture.global_cname}, p->c${i}, sizeof(${capture.global_cname})); ' + } else { + pre += '${capture.global_cname} = p->c${i}; ' + } + } + body := spawn_wrapper_body_with_pre('${cfn}(${call_args.join(', ')})', ret_ct, pre, 'free(p); ') + g.add_spawn_wrapper_def('static void* ${wrapper_name}(void* arg) { ${struct_name}* p = (${struct_name}*)arg; ${body} }') + return wrapper_name, struct_name +} + +// emit_args_spawn_expr writes a statement-expression that heap-allocates the arg +// struct, populates it, and starts the thread on the packing wrapper. +fn (mut g FlatGen) emit_args_spawn_expr(cfn string, args []SpawnPackedArg, ret_ct string) { + wrapper, struct_name := g.ensure_args_spawn_wrapper(cfn, args, ret_ct) + captures := g.spawn_fn_literal_captures(cfn) + tmp := g.tmp_count + g.tmp_count++ + g.write('({ ${struct_name}* _sa${tmp} = (${struct_name}*)__v_thread_alloc(sizeof(${struct_name})); ') + for i, arg in args { + g.write_spawn_packed_arg_init(tmp, i, arg) + } + for i, capture in captures { + g.write_spawn_capture_init(tmp, i, capture) + } + g.write('__v_thread_spawn(${wrapper}, (void*)_sa${tmp}, free); })') +} + +fn (mut g FlatGen) ensure_fn_value_spawn_wrapper(fn_ct string, args []SpawnPackedArg, ret_ct string, captures []SpawnClosureCapture, destroys_fn bool) (string, string) { + signature := spawn_packed_args_signature(args) + capture_signature := spawn_closure_capture_signature(captures) + mut suffix := '${fn_ct}_${spawn_packed_args_name_suffix(args)}' + capture_suffix := spawn_closure_capture_name_suffix(captures) + if capture_suffix.len > 0 { + suffix += '_captures_${capture_suffix}' + } + suffix = suffix.replace('*', 'ptr').replace(' ', '_') + struct_name := g.cname('fn_value_thread_args_${suffix}') + wrapper_name := g.cname('fn_value_args_thread_wrapper_${suffix}') + key := 'fnvalue|${fn_ct}|${ret_ct}|${signature}|captures:${capture_signature}|destroy:${destroys_fn}' + if name := g.spawn_wrapper_names[key] { + return name, struct_name + } + g.spawn_wrapper_names[key] = wrapper_name + mut fields := '${fn_ct} f; ' + mut call_args := []string{} + for i, arg in args { + fields += '${arg.field_ct} a${i}; ' + call_args << arg.call_expr + } + for i, capture in captures { + fields += '${capture.field_ct} c${i}; ' + } + g.add_spawn_wrapper_def('typedef struct { ${fields}} ${struct_name};') + mut pre := '' + for i, capture in captures { + if capture.copy_array { + pre += 'memmove(${capture.global_cname}, p->c${i}, sizeof(${capture.global_cname})); ' + } else { + pre += '${capture.global_cname} = p->c${i}; ' + } + } + destroy := if destroys_fn { + '${g.cname('closure.closure_try_destroy')}((void*)p->f); ' + } else { + '' + } + body := spawn_wrapper_body_with_pre('p->f(${call_args.join(', ')})', ret_ct, pre, + '${destroy}free(p); ') + g.add_spawn_wrapper_def('static void* ${wrapper_name}(void* arg) { ${struct_name}* p = (${struct_name}*)arg; ${body} }') + return wrapper_name, struct_name +} + +fn (mut g FlatGen) emit_fn_value_spawn_expr(call_id flat.NodeId, fn_node flat.Node, fn_type types.FnType, args []SpawnPackedArg, ret_ct string) { + fn_ct := g.value_c_type(fn_type) + captures := g.spawn_fn_value_captures(fn_node) + mut callable := fn_node + for callable.kind == .paren && callable.children_count > 0 { + callable = g.a.nodes[int(g.a.child(&callable, 0))] + } + // Only compiler-created immediate closures are consumed by spawn. A named local + // remains owned by the caller and may be invoked again after the worker joins. + destroys_fn := callable.kind == .ident && callable.value.starts_with('__immediate_closure_') + wrapper, struct_name := g.ensure_fn_value_spawn_wrapper(fn_ct, args, ret_ct, captures, + destroys_fn) + tmp := g.tmp_count + g.tmp_count++ + g.write('({ ${struct_name}* _sa${tmp} = (${struct_name}*)__v_thread_alloc(sizeof(${struct_name})); ') + g.write('_sa${tmp}->f = ') + g.gen_expr_with_expected_type(g.a.child(&g.a.nodes[int(call_id)], 0), fn_type) + g.write('; ') + for i, arg in args { + g.write_spawn_packed_arg_init(tmp, i, arg) + } + for i, capture in captures { + g.write_spawn_capture_init(tmp, i, capture) + } + g.write('__v_thread_spawn(${wrapper}, (void*)_sa${tmp}, free); })') +} + +fn (mut g FlatGen) write_spawn_packed_arg_init(tmp int, idx int, arg SpawnPackedArg) { + if arg.copy_array { + g.write('memmove(_sa${tmp}->a${idx}, ${arg.assign_expr}, sizeof(_sa${tmp}->a${idx})); ') + return + } + g.write('_sa${tmp}->a${idx} = ${arg.assign_expr}; ') +} + +fn (mut g FlatGen) write_spawn_capture_init(tmp int, idx int, capture SpawnClosureCapture) { + if capture.copy_array { + g.write('memmove(_sa${tmp}->c${idx}, ${capture.global_cname}, sizeof(_sa${tmp}->c${idx})); ') + return + } + g.write('_sa${tmp}->c${idx} = ${capture.global_cname}; ') +} + +fn (mut g FlatGen) spawn_fn_value_captures(fn_node flat.Node) []SpawnClosureCapture { + if fn_node.kind == .paren && fn_node.children_count > 0 { + return g.spawn_fn_value_captures(g.a.child_node(&fn_node, 0)) + } + if fn_node.kind != .ident { + return []SpawnClosureCapture{} + } + cfn := g.local_fn_value_c_name(fn_node.value) or { return []SpawnClosureCapture{} } + return g.spawn_fn_literal_captures(cfn) +} + +fn (mut g FlatGen) spawn_fn_literal_captures(cfn string) []SpawnClosureCapture { + mut names := []string{} + for name, _ in g.global_types { + // Capture slots form the closure environment available to the current + // thread. A spawned closure can itself hold another capturing callback, + // so copying only slots whose prefix matches the immediate function loses + // that nested callback's environment in the worker thread. Keep the + // environment module-local: an imported module's spawned literal must not + // capture program-owned slots merely because both are anonymous functions. + if g.cname(name).contains('__anon_fn_') + && g.spawn_capture_global_matches_fn_module(name, cfn) { + names << name + } + } + if names.len == 0 { + return []SpawnClosureCapture{} + } + names.sort() + mut captures := []SpawnClosureCapture{cap: names.len} + for name in names { + typ := g.global_types[name] or { continue } + decl_typ := g.global_storage_type(name, typ) + mut ct := g.value_c_type(decl_typ) + if ct == 'Optional' { + if concrete_ct := g.global_init_optional_c_type(name) { + ct = concrete_ct + } + } + if ct == 'void' { + continue + } + captures << SpawnClosureCapture{ + global_cname: g.cname(name) + field_ct: ct + copy_array: decl_typ is types.ArrayFixed + } + } + return captures +} + +fn (g &FlatGen) spawn_capture_global_matches_fn_module(name string, cfn string) bool { + capture_module := g.global_modules[name] or { '' } + if cfn.starts_with('__anon_fn_') { + return capture_module in ['', 'main'] + } + if capture_module in ['', 'main'] { + return false + } + return cfn.starts_with(g.cname('${capture_module}.__anon_fn_')) +} + +fn (g &FlatGen) shared_local_arg_c_expr(arg_id flat.NodeId) ?string { + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return none + } + arg := g.a.nodes[int(arg_id)] + if arg.kind == .paren && arg.children_count > 0 { + return g.shared_local_arg_c_expr(g.a.child(&arg, 0)) + } + if arg.kind == .prefix && (arg.value == 'shared' || arg.value.starts_with('shared:')) + && arg.children_count > 0 { + return g.shared_local_arg_c_expr(g.a.child(&arg, 0)) + } + if arg.kind == .prefix && arg.op == .mul && arg.children_count > 0 { + if expr := g.shared_payload_deref_storage_c_expr(g.a.child(&arg, 0)) { + return expr + } + } + if arg.kind == .ident && g.local_storage_is_shared(arg.value) { + return g.cname(arg.value) + } + return none +} + +fn (g &FlatGen) shared_payload_deref_storage_c_expr(id flat.NodeId) ?string { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return none + } + node := g.a.nodes[int(id)] + if node.kind == .paren && node.children_count > 0 { + return g.shared_payload_deref_storage_c_expr(g.a.child(&node, 0)) + } + if node.kind != .selector || node.value != 'val' || node.children_count == 0 { + return none + } + base_id := g.a.child(&node, 0) + base := g.a.nodes[int(base_id)] + if base.kind == .ident && g.local_storage_is_shared(base.value) { + return g.cname(base.value) + } + return none +} + +fn (mut g FlatGen) shared_arg_storage_c_expr(arg_id flat.NodeId) ?string { + if expr := g.shared_local_arg_c_expr(arg_id) { + return expr + } + orig := g.sb + orig_line_start := g.line_start + g.sb = strings.new_builder(64) + g.line_start = false + ok := g.gen_shared_storage_expr(arg_id) + result := g.sb.str() + g.sb = orig + g.line_start = orig_line_start + if !ok || result.len == 0 { + return none + } + return result +} + +fn (mut g FlatGen) spawn_packed_arg_for_call_param(fn_name string, arg_id flat.NodeId, expected types.Type, field_idx int) SpawnPackedArg { + if g.fn_param_is_shared_for_call(field_idx, fn_name, '', '', '') + || g.spawn_arg_has_shared_marker(arg_id) || g.spawn_arg_is_shared_local(arg_id) { + if expr := g.shared_arg_storage_c_expr(arg_id) { + wrapper_ct := g.shared_spawn_wrapper_c_type(expected) + return SpawnPackedArg{ + field_ct: wrapper_ct + assign_expr: expr + call_expr: 'p->a${field_idx}' + } + } + } + expected_ct := g.spawn_arg_c_type(expected) + return g.spawn_packed_arg_for_param(arg_id, expected, expected_ct, field_idx) +} + +fn (g &FlatGen) spawn_arg_has_shared_marker(arg_id flat.NodeId) bool { + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return false + } + arg := g.a.nodes[int(arg_id)] + if arg.kind == .paren && arg.children_count > 0 { + return g.spawn_arg_has_shared_marker(g.a.child(&arg, 0)) + } + return arg.kind == .prefix && (arg.value == 'shared' || arg.value.starts_with('shared:')) + && arg.children_count > 0 +} + +fn (g &FlatGen) spawn_arg_is_shared_local(arg_id flat.NodeId) bool { + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return false + } + arg := g.a.nodes[int(arg_id)] + if arg.kind == .paren && arg.children_count > 0 { + return g.spawn_arg_is_shared_local(g.a.child(&arg, 0)) + } + if arg.kind == .prefix && (arg.value == 'shared' || arg.value.starts_with('shared:')) + && arg.children_count > 0 { + return g.spawn_arg_is_shared_local(g.a.child(&arg, 0)) + } + return arg.kind == .ident && g.local_storage_is_shared(arg.value) +} + +fn (mut g FlatGen) spawn_arg_c_type(expected types.Type) string { + return g.value_c_type(expected) +} + +fn (mut g FlatGen) shared_spawn_wrapper_c_type(expected types.Type) string { + value_type := shared_local_value_type(expected) + inner := g.shared_qualify_type_text(value_type.name(), g.tc.cur_module) + return '${g.shared_wrapper_c_name(inner)}*' +} + +fn (mut g FlatGen) spawn_packed_arg_for_param(arg_id flat.NodeId, expected types.Type, expected_ct string, field_idx int) SpawnPackedArg { + if expr := g.shared_lowered_spawn_arg_storage_c_expr(arg_id) { + wrapper_ct := g.shared_spawn_wrapper_c_type(expected) + return SpawnPackedArg{ + field_ct: wrapper_ct + assign_expr: expr + call_expr: 'p->a${field_idx}' + } + } + if fixed := array_fixed_type(expected) { + return SpawnPackedArg{ + field_ct: expected_ct + assign_expr: g.fixed_array_copy_source_string(arg_id, types.Type(fixed)) + call_expr: 'p->a${field_idx}' + copy_array: true + } + } + if spawn_c_type_is_pointer(expected_ct) { + arg_node := g.a.nodes[int(arg_id)] + if storage := g.spawn_shared_value_arg_storage(arg_id) { + wrapper_ct := g.shared_spawn_wrapper_c_type(expected) + return SpawnPackedArg{ + field_ct: wrapper_ct + assign_expr: storage + call_expr: 'p->a${field_idx}' + } + } + if child_id := g.spawn_materialized_pointer_rvalue_arg(arg_node) { + value_type := types.unwrap_pointer(expected) + return SpawnPackedArg{ + field_ct: g.tc.c_type(value_type) + assign_expr: g.expr_to_string_with_expected_type(child_id, value_type) + call_expr: '&p->a${field_idx}' + } + } + if child_id := g.addressed_rvalue_arg(arg_node) { + value_type := types.unwrap_pointer(expected) + return SpawnPackedArg{ + field_ct: g.tc.c_type(value_type) + assign_expr: g.expr_to_string_with_expected_type(child_id, value_type) + call_expr: '&p->a${field_idx}' + } + } + if child_id := g.spawn_stack_address_value(arg_id) { + value_type := types.unwrap_pointer(expected) + return SpawnPackedArg{ + field_ct: g.tc.c_type(value_type) + assign_expr: g.expr_to_string_with_expected_type(child_id, value_type) + call_expr: '&p->a${field_idx}' + } + } + if g.spawn_arg_expr_is_pointer_value(arg_id) { + return SpawnPackedArg{ + field_ct: expected_ct + assign_expr: g.expr_to_string(arg_id) + call_expr: 'p->a${field_idx}' + } + } + if g.expr_is_addressable(arg_id) { + expr := g.expr_to_string(arg_id) + return SpawnPackedArg{ + field_ct: expected_ct + assign_expr: '&${expr}' + call_expr: 'p->a${field_idx}' + } + } + value_type := types.unwrap_pointer(expected) + return SpawnPackedArg{ + field_ct: g.tc.c_type(value_type) + assign_expr: g.expr_to_string_with_expected_type(arg_id, value_type) + call_expr: '&p->a${field_idx}' + } + } + assign_expr := g.expr_to_string_with_expected_type(arg_id, expected) + if storage_expr := shared_storage_from_payload_value_expr(assign_expr) { + wrapper_ct := g.shared_spawn_wrapper_c_type(expected) + return SpawnPackedArg{ + field_ct: wrapper_ct + assign_expr: storage_expr + call_expr: 'p->a${field_idx}' + } + } + return SpawnPackedArg{ + field_ct: expected_ct + assign_expr: assign_expr + call_expr: 'p->a${field_idx}' + } +} + +fn shared_storage_from_payload_value_expr(expr string) ?string { + clean := expr.trim_space() + if !clean.starts_with('*') || !clean.ends_with('->val') { + return none + } + storage := clean[1..clean.len - 5].trim_space() + if storage.len == 0 || storage.contains(' ') || storage.contains('(') || storage.contains(')') { + return none + } + return storage +} + +fn (mut g FlatGen) gen_shared_array_push_arg(marker string, arg_id flat.NodeId) bool { + if !marker.starts_with('shared_array_push:') { + return false + } + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return false + } + inner := marker['shared_array_push:'.len..].trim_space() + if inner.len == 0 { + return false + } + qualified := g.shared_qualify_type_text(inner, g.tc.cur_module) + wrapper := g.shared_wrapper_c_name(qualified) + mut value_id := arg_id + arg := g.a.nodes[int(arg_id)] + if arg.kind == .prefix && arg.op == .amp && arg.children_count > 0 { + value_id = g.a.child(&arg, 0) + } + value_type := g.tc.parse_type(qualified) + g.write('&(${wrapper}*[]){(${wrapper}*)__dup${wrapper}(&(${wrapper}){.mtx = {0}, .val = ') + g.gen_expr_with_expected_type(value_id, value_type) + g.write('}, sizeof(${wrapper}))}') + return true +} + +fn (g &FlatGen) shared_lowered_spawn_arg_storage_c_expr(arg_id flat.NodeId) ?string { + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return none + } + arg := g.a.nodes[int(arg_id)] + if arg.kind == .paren && arg.children_count > 0 { + return g.shared_lowered_spawn_arg_storage_c_expr(g.a.child(&arg, 0)) + } + if arg.kind == .prefix && arg.value == 'shared' && arg.children_count > 0 { + return g.shared_local_arg_c_expr(g.a.child(&arg, 0)) + } + if arg.kind == .prefix && arg.op == .mul && arg.children_count > 0 { + return g.shared_payload_deref_storage_c_expr(g.a.child(&arg, 0)) + } + return none +} + +fn (g &FlatGen) spawn_shared_value_arg_storage(arg_id flat.NodeId) ?string { + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return none + } + node := g.a.nodes[int(arg_id)] + if node.kind == .paren && node.children_count > 0 { + return g.spawn_shared_value_arg_storage(g.a.child(&node, 0)) + } + if node.kind == .selector { + return g.spawn_shared_value_selector_storage(node) + } + if node.kind != .prefix || node.op != .mul || node.children_count == 0 { + return none + } + child_id := g.a.child(&node, 0) + if int(child_id) < 0 || int(child_id) >= g.a.nodes.len { + return none + } + child := g.a.nodes[int(child_id)] + if child.kind == .prefix && (child.value == 'shared' || child.value.starts_with('shared:')) + && child.children_count > 0 { + return g.shared_local_arg_c_expr(child_id) + } + return g.spawn_shared_value_selector_storage(child) +} + +fn (g &FlatGen) spawn_shared_value_selector_storage(child flat.Node) ?string { + if child.kind != .selector || child.value != 'val' || child.children_count == 0 { + return none + } + base_id := g.a.child(&child, 0) + if int(base_id) < 0 || int(base_id) >= g.a.nodes.len { + return none + } + base := g.a.nodes[int(base_id)] + if base.kind != .ident || !g.local_ident_is_shared_wrapper(base.value) { + return none + } + return g.cname(base.value) +} + +fn (g &FlatGen) local_ident_is_shared_wrapper(name string) bool { + if g.local_storage_is_shared(name) { + return true + } + if ct := g.local_storage_c_type(name) { + return ct.starts_with('__shared__') && ct.ends_with('*') + } + return false +} + +// spawn_stack_address_value finds `&local` (also through parentheses) when local is a value +// binding. The spawned wrapper must own a copy in its heap argument block; storing the stack +// address itself lets loop iterations reuse the slot before the thread reads it. +fn (g &FlatGen) spawn_stack_address_value(id flat.NodeId) ?flat.NodeId { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return none + } + node := g.a.node(id) + if node.kind == .paren && node.children_count > 0 { + return g.spawn_stack_address_value(g.a.child(node, 0)) + } + if node.kind != .prefix || node.op != .amp || node.children_count == 0 { + return none + } + child_id := g.a.child(node, 0) + child := g.a.node(child_id) + if child.kind != .ident || node.is_mut || child.is_mut { + return none + } + local_type := g.local_ident_type(child.value) or { return none } + if local_type is types.Pointer { + return none + } + return child_id +} + +fn spawn_packed_args_signature(args []SpawnPackedArg) string { + mut parts := []string{} + for i, arg in args { + parts << '${i}:${arg.field_ct}:${arg.call_expr}' + } + return parts.join('|') +} + +fn spawn_closure_capture_signature(captures []SpawnClosureCapture) string { + mut parts := []string{} + for i, capture in captures { + copy_mode := if capture.copy_array { 'array' } else { 'value' } + parts << '${i}:${capture.global_cname}:${capture.field_ct}:${copy_mode}' + } + return parts.join('|') +} + +fn spawn_closure_capture_name_suffix(captures []SpawnClosureCapture) string { + mut parts := []string{} + for i, capture in captures { + copy_mode := if capture.copy_array { 'array' } else { 'value' } + parts << '${i}_${capture.global_cname}_${copy_mode}' + } + return parts.join('_') +} + +fn spawn_packed_args_are_direct(args []SpawnPackedArg) bool { + for i, arg in args { + if arg.call_expr != 'p->a${i}' { + return false + } + } + return true +} + +fn spawn_packed_args_name_suffix(args []SpawnPackedArg) string { + mut parts := []string{} + for i, arg in args { + field := arg.field_ct.replace('*', 'ptr').replace(' ', '_') + mode := if arg.call_expr == 'p->a${i}' { 'value' } else { 'addr' } + parts << '${field}_${mode}' + } + return parts.join('_') +} + +fn spawn_c_type_is_pointer(ct string) bool { + return trimmed_space(ct).ends_with('*') +} + +fn (mut g FlatGen) shared_param_c_type(raw_typ string) ?string { + inner := shared_inner_type_text(raw_typ) or { return none } + qualified := g.shared_qualify_type_text(inner, g.tc.cur_module) + return '${g.shared_wrapper_c_name(qualified)}*' +} + +fn (g &FlatGen) spawn_arg_expr_is_pointer_value(arg_id flat.NodeId) bool { + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(arg_id)] + if node.kind == .prefix && node.op == .amp { + if node.children_count == 0 { + return false + } + return g.expr_is_addressable(g.a.child(&node, 0)) + } + if node.kind == .ident { + if typ := g.current_param_type(node.value) { + return typ is types.Pointer + } + } + if node.kind == .call { + if fname := g.tc.resolved_call_name(arg_id) { + ret_type := g.tc.fn_ret_types[fname] or { return false } + return ret_type is types.Pointer + } + return false + } + return g.tc.resolve_type(arg_id) is types.Pointer +} + +fn (g &FlatGen) spawn_materialized_pointer_rvalue_arg(arg_node flat.Node) ?flat.NodeId { + if arg_node.kind != .prefix || arg_node.op != .amp || arg_node.children_count == 0 { + return none + } + child_id := g.a.child(&arg_node, 0) + if int(child_id) < 0 || int(child_id) >= g.a.nodes.len { + return none + } + child := g.a.nodes[int(child_id)] + if child.kind == .ident && child.value.starts_with('__ptr_arg_') { + return child_id + } + return none +} + +fn (mut g FlatGen) gen_thread_wait_call(fn_node &flat.Node) bool { + if fn_node.value != 'wait' || fn_node.children_count == 0 { + return false + } + base_id := g.a.child(fn_node, 0) + base_type0 := g.usable_expr_type(base_id) + base_type := if base_type0 is types.Unknown || base_type0 is types.Void { + g.tc.resolve_type(base_id) + } else { + base_type0 + } + clean_type := types.unwrap_pointer(base_type) + if clean_type !is types.Struct { + return false + } + thread_struct := clean_type as types.Struct + thread_name := trimmed_space(thread_struct.name) + mut ret_name := '' + if thread_name == 'thread' || thread_name.ends_with('.thread') { + ret_name = '' + } else if thread_name.starts_with('thread ') { + ret_name = trimmed_space(thread_name[7..]) + } else { + return false + } + tmp := g.tmp_count + g.tmp_count++ + res_name := '__twres${tmp}' + g.write('({ void* ${res_name} = __v_thread_join(') + g.gen_expr(base_id) + g.write('); ') + if ret_name.len == 0 { + g.write('if (${res_name}) free(${res_name}); })') + return true + } + ret_ct := g.fn_return_type_name(g.tc.parse_type(ret_name)) + val_name := '__twval${tmp}' + g.write('${ret_ct} ${val_name}; if (${res_name}) { ${val_name} = *((${ret_ct}*)${res_name}); free(${res_name}); } else { memset(&${val_name}, 0, sizeof(${val_name})); } ${val_name}; })') + return true +} + +fn (g &FlatGen) resolved_method_name_for_spawn(clean_type types.Type, method string) string { + mut type_name := clean_type.name() + if clean_type is types.Struct { + type_name = clean_type.name + } + method_name := '${type_name}.${method}' + if method_name in g.tc.fn_param_types { + return method_name + } + for alias, target in g.tc.type_aliases { + if target == type_name { + alias_method := '${alias}.${method}' + if alias_method in g.tc.fn_param_types { + return alias_method + } + } + } + return '' +} + +fn (g &FlatGen) print_fn_selector_matches(c_name string, module_name string, source_name string) bool { + if c_name in g.print_fn_names { + return true + } + if module_name in ['', 'main'] && 'main__${source_name}' in g.print_fn_names { + return true + } + return false +} + +// gen_fn_in_module emits fn in module output for c. +fn (mut g FlatGen) gen_fn_in_module(node_id flat.NodeId, node flat.Node, module_name string, skip_prelude_scan bool) { + g.tc.cur_module = module_name + g.cur_fn_name = node.value + g.cur_fn_assert_continues = g.tc.declaration_has_attribute(node_id, 'assert_continues') + g.begin_usable_expr_type_memo() + g.known_expr_type_id = -1 + g.ownership_return_index = 0 + g.ownership_seen_return_sources.clear() + g.ownership_propagation_index = 0 + g.ownership_loop_control_index = 0 + g.ownership_loop_iteration_index = 0 + g.ownership_scope_index = 0 + g.cur_return_drops.clear() + g.loop_depth = 0 + g.loop_label_depths.clear() + g.map_loop_copyback_guards.clear() + g.emitted_loop_break_labels.clear() + g.goto_label_c_names.clear() + g.goto_label_count = 0 + mut prelude_scan := if skip_prelude_scan { + FnPreludeScan{} + } else { + g.collect_fn_prelude_scan(node) + } + g.goto_label_lock_scopes = prelude_scan.goto_label_lock_scopes.move() + g.pending_loop_label = '' + g.ierror_stack_pointer_aliases.clear() + g.ierror_owned_pointer_by_owner.clear() + g.local_pointer_storage_by_owner.clear() + g.local_c_type_by_owner.clear() + g.local_pointer_alias_by_owner.clear() + g.local_pointer_alias_mut_param.clear() + g.local_shared_storage_by_owner.clear() + g.shadowed_global_locals.clear() + g.local_fn_value_c_name_by_owner.clear() + g.defers.clear() + g.scope_defer_starts.clear() + g.push_scope() + g.fn_defers.clear() + g.fn_defer_counts.clear() + g.defer_capture_names.clear() + g.defer_capture_types.clear() + g.set_cur_fn_ret(types.Type(types.void_)) + g.cur_param_names.clear() + g.cur_param_type_values.clear() + g.cur_param_types.clear() + g.cur_concrete_optional_params.clear() + g.cur_mut_params.clear() + g.cur_mut_pointer_params.clear() + g.cur_mut_param_owners.clear() + typed_params := g.fn_node_param_types(node, module_name) + concrete_optional_params := g.is_specialized_generic_fn_node(node) + mut param_idx := 0 + for i in 0 .. node.children_count { + param_id := g.a.child(&node, i) + p := g.a.node(param_id) + if p.kind == .param { + decl_param_type := g.tc.parse_resolution_type(p.typ) + param_type := if p.is_mut && p.op == .amp && param_idx < typed_params.len { + g.fn_node_effective_param_type(p, typed_params[param_idx]) + } else if shared_alias_ptr := g.shared_alias_pointer_type_from_text(p.typ) { + shared_alias_ptr + } else if !concrete_optional_params && p.typ.len > 0 + && !decl_annotation_is_unusable(decl_param_type, p.typ) { + decl_param_type + } else if param_idx < typed_params.len { + typed_params[param_idx] + } else { + decl_param_type + } + param_idx++ + if p.value.len > 0 { + g.cur_param_names << p.value + g.cur_param_type_values << param_type + g.cur_param_types[p.value] = param_type + owner := g.tc.cur_scope.insert_with_owner(p.value, param_type) + if shared_ct := g.shared_param_c_type(p.typ) { + g.declare_local_c_type(owner, shared_ct) + g.declare_local_pointer_storage(owner, true) + g.declare_local_shared_storage(owner, true) + } + if p.is_mut { + g.cur_mut_params[p.value] = true + if p.op == .amp { + g.cur_mut_pointer_params[p.value] = true + } + g.cur_mut_param_owners[p.value] = owner + } + if concrete_optional_params && type_is_optional_result(param_type) { + g.cur_concrete_optional_params[p.value] = true + } + } + } + } + g.insert_cur_implicit_veb_ctx_param(node) + g.prepare_function_defers(prelude_scan.defer_ids) + is_entry_main := is_main_fn_in_main_module(module_name, node.value) && g.test_files.len == 0 + && !g.suppress_main + generated_fn_name := g.fn_c_name_in_module(module_name, node.value) + should_print_fn := g.print_fn_selector_matches(generated_fn_name, module_name, node.value) + || (is_entry_main && 'main' in g.print_fn_names) + fn_start_pos := g.sb.len + mut is_direct_no_main_export := false + if is_entry_main { + g.writeln('int main(int argc, char** argv) {') + if g.has_builtins { + g.writeln('\tg_main_argc = argc;') + g.writeln('\tg_main_argv = argv;') + } + g.gen_compiler_vexe_env_setup() + g.gen_coverage_registration() + g.gen_profile_startup_enable() + if g.const_runtime_inits.len > 0 || g.runtime_inits.len > 0 || g.module_init_fns.len > 0 + || g.global_inits.len > 0 { + g.writeln('\t_vinit();') + } + g.gen_profile_registration() + g.gen_executable_cleanup_registration() + } else { + ret_type := g.fn_node_return_type(node, module_name) + g.set_cur_fn_ret(ret_type) + if export_name := g.export_fn_name_in_module(module_name, node.value) { + if export_name == generated_fn_name { + g.write(g.exported_symbol_attribute()) + // A natural-name export is called under its own symbol; no wrapper is + // generated, so this body itself must run the guarded initializer. + is_direct_no_main_export = g.needs_no_main_runtime_init_caller() + } + } + g.write(g.fn_return_type_name(ret_type)) + g.write(' ') + g.write(generated_fn_name) + g.write('(') + g.write_fn_node_params(node) + g.writeln(')${g.fn_decl_c_attribute(node_id)} {') + } + // All generated temporary identifiers are function-local. Reset immediately + // before emitting the body so lookup/preparation work cannot affect spelling. + g.tmp_count = 0 + g.indent++ + if is_direct_no_main_export { + g.writeln('_vno_main_init_caller();') + } + g.gen_function_defer_prelude() + g.gen_profile_fn_begin(generated_fn_name, module_name, node.value, g.tc.declaration_has_attribute(node_id, + 'inline')) + + for i in 0 .. node.children_count { + id := g.a.child(&node, i) + child := g.a.node(id) + if child.kind != .param { + g.tc.cur_module = module_name + g.gen_node(id) + } + } + g.gen_all_defers() + g.gen_profile_fn_exit() + g.gen_ownership_drops(g.tc.ownership_drop_entries_at_fn_exit(qualify_name_in_module(module_name, + node.value))) + if is_entry_main { + g.writeln('return 0;') + } else if g.cur_fn_ret_is_optional { + ct := g.current_fn_optional_type_name(g.cur_fn_ret) + g.writeln('return (${ct}){.ok = true};') + } + g.indent-- + g.writeln('}') + g.writeln('') + if should_print_fn { + println(g.sb.after(fn_start_pos)) + } + if !is_entry_main && !g.object_file_mode { + g.gen_export_wrapper_for_fn(node, module_name) + } + g.loop_depth = 0 + g.pending_loop_label = '' + g.pop_scope() + g.end_usable_expr_type_memo() +} + +fn (mut g FlatGen) gen_export_wrapper_for_fn(node flat.Node, module_name string) { + export_name := g.export_fn_name_in_module(module_name, node.value) or { return } + canonical_name := g.fn_c_name_in_module(module_name, node.value) + if export_name == canonical_name { + return + } + ret_type := g.fn_node_return_type(node, module_name) + ret_ct := g.fn_return_type_name(ret_type) + g.write(g.exported_symbol_attribute()) + g.write(ret_ct) + g.write(' ') + g.write(export_name) + g.write('(') + g.write_fn_node_params(node) + g.writeln(') {') + g.indent++ + args := g.export_wrapper_arg_names(node) + call := '${canonical_name}(${args.join(', ')})' + if g.needs_no_main_runtime_init_caller() { + g.writeln('_vno_main_init_caller();') + } + if ret_type is types.Void { + g.writeln('${call};') + } else { + g.writeln('return ${call};') + } + g.indent-- + g.writeln('}') + g.writeln('') +} + +fn (mut g FlatGen) emit_object_file_export_wrappers() { + mut emitted := map[string]bool{} + for item in g.ensure_fn_gen_items() { + node := g.a.nodes[int(item.node_id)] + if export_name := g.export_fn_name_in_module(item.module, node.value) { + canonical_name := g.fn_c_name_in_module(item.module, node.value) + if export_name != canonical_name && !emitted[export_name] { + emitted[export_name] = true + g.tc.cur_file = item.file + g.tc.cur_module = item.module + ret_type := g.fn_node_return_type(node, item.module) + g.write(g.exported_symbol_attribute()) + g.write(g.fn_return_type_name(ret_type)) + g.write(' ') + g.write(export_name) + g.write('(') + g.write_fn_node_params(node) + g.writeln(') {') + g.indent++ + if g.needs_no_main_runtime_init_caller() { + g.writeln('_vno_main_init_caller();') + } + call := '${canonical_name}(${g.export_wrapper_arg_names(node).join(', ')})' + if ret_type is types.Void { + g.writeln('${call};') + } else { + g.writeln('return ${call};') + } + g.indent-- + g.writeln('}') + g.writeln('') + } + } + if item.module !in ['', 'main'] || item.file !in g.cache_program_files || node.op != .arrow + || node.value == 'main' || node.value.contains('.') { + continue + } + export_name := g.cname('main.${node.value}') + if emitted[export_name] { + continue + } + emitted[export_name] = true + g.tc.cur_file = item.file + g.tc.cur_module = item.module + ret_type := g.fn_node_return_type(node, item.module) + concrete_optional := g.is_program_specialization_fn_node(node, int(item.node_id), + item.module) + g.write(g.fn_return_type_name_for_context(ret_type, concrete_optional)) + g.write(' ') + g.write(export_name) + g.write('(') + g.write_fn_node_params(node) + g.writeln(') {') + g.indent++ + call := '${item.c_name}(${g.export_wrapper_arg_names(node).join(', ')})' + if ret_type is types.Void { + g.writeln('${call};') + } else { + g.writeln('return ${call};') + } + g.indent-- + g.writeln('}') + g.writeln('') + } +} + +fn (g &FlatGen) exported_symbol_attribute() string { + if !g.is_shared { + return '' + } + if g.ccompiler == 'msvc' { + return '__declspec(dllexport) ' + } + return '__attribute__((visibility("default"))) ' +} + +fn (mut g FlatGen) export_wrapper_arg_names(node flat.Node) []string { + mut args := []string{} + needs_implicit_ctx := g.fn_needs_implicit_veb_ctx(node) + insert_implicit_ctx_after_first := needs_implicit_ctx && g.fn_has_receiver_param(node) + mut written := 0 + mut implicit_ctx_written := false + for i in 0 .. node.children_count { + param_id := g.a.child(&node, i) + p := g.a.node(param_id) + if p.kind != .param { + continue + } + param_name := if p.value == '_' { '_${written}' } else { g.cname(p.value) } + args << param_name + written++ + if insert_implicit_ctx_after_first && !implicit_ctx_written { + args << 'ctx' + written++ + implicit_ctx_written = true + } + } + if needs_implicit_ctx && !implicit_ctx_written { + args << 'ctx' + } + return args +} + +fn (mut g FlatGen) gen_top_level_main(stmts []TopLevelStmt) { + old_tc_file := g.tc.cur_file + old_tc_module := g.tc.cur_module + g.tc.cur_module = 'main' + old_fn_name := g.cur_fn_name + g.cur_fn_name = 'main' + g.loop_depth = 0 + g.loop_label_depths = map[string]int{} + g.map_loop_copyback_guards = []MapLoopCopybackGuard{} + g.emitted_loop_break_labels = map[string]bool{} + mut prelude_scan := g.collect_top_level_prelude_scan(stmts) + g.goto_label_lock_scopes = prelude_scan.goto_label_lock_scopes.move() + g.pending_loop_label = '' + old_ierror_stack_pointer_aliases := g.ierror_stack_pointer_aliases + g.ierror_stack_pointer_aliases = []map[string]bool{} + mut old_ierror_owned_pointer_by_owner := g.ierror_owned_pointer_by_owner.move() + g.ierror_owned_pointer_by_owner = map[string]bool{} + mut old_local_pointer_storage_by_owner := g.local_pointer_storage_by_owner.move() + g.local_pointer_storage_by_owner = map[string]bool{} + mut old_local_c_type_by_owner := g.local_c_type_by_owner.move() + g.local_c_type_by_owner = map[string]string{} + mut old_local_pointer_alias_by_owner := g.local_pointer_alias_by_owner.move() + g.local_pointer_alias_by_owner = map[string]string{} + mut old_local_pointer_alias_mut_param := g.local_pointer_alias_mut_param.move() + g.local_pointer_alias_mut_param = map[string]bool{} + mut old_local_shared_storage_by_owner := g.local_shared_storage_by_owner.move() + g.local_shared_storage_by_owner = map[string]bool{} + mut old_local_fn_value_c_name_by_owner := g.local_fn_value_c_name_by_owner.move() + g.local_fn_value_c_name_by_owner = map[string]string{} + g.defers = []flat.NodeId{} + g.scope_defer_starts = []int{} + g.push_scope() + g.fn_defers = []flat.NodeId{} + g.fn_defer_counts = map[int]string{} + g.defer_capture_names = []string{} + g.defer_capture_types = map[string]types.Type{} + g.set_cur_fn_ret(types.Type(types.void_)) + old_param_names := g.cur_param_names + old_param_type_values := g.cur_param_type_values + mut old_param_types := g.cur_param_types.move() + mut old_concrete_optional_params := g.cur_concrete_optional_params.move() + mut old_mut_params := g.cur_mut_params.move() + mut old_mut_pointer_params := g.cur_mut_pointer_params.move() + mut old_mut_param_owners := g.cur_mut_param_owners.move() + g.cur_param_names = []string{} + g.cur_param_type_values = []types.Type{} + g.cur_param_types = map[string]types.Type{} + g.cur_concrete_optional_params = map[string]bool{} + g.cur_mut_params = map[string]bool{} + g.cur_mut_pointer_params = map[string]bool{} + g.cur_mut_param_owners = map[string]types.ScopeBindingOwner{} + g.prepare_function_defers(prelude_scan.defer_ids) + g.goto_label_c_names.clear() + g.goto_label_count = 0 + fn_start_pos := g.sb.len + g.writeln('int main(int argc, char** argv) {') + if g.has_builtins { + g.writeln('\tg_main_argc = argc;') + g.writeln('\tg_main_argv = argv;') + } + g.gen_compiler_vexe_env_setup() + g.gen_coverage_registration() + g.gen_profile_startup_enable() + needs_no_main_runtime_init_caller := g.needs_no_main_runtime_init_caller() + if needs_no_main_runtime_init_caller { + // Exported callbacks can be invoked without main. Share their guarded + // initializer so main and callback startup cannot initialize twice. + g.writeln('\t_vno_main_init_caller();') + } else { + if g.runtime_init_is_needed() { + g.writeln('\t_vinit();') + } + g.gen_executable_cleanup_registration() + } + if !needs_no_main_runtime_init_caller { + g.gen_profile_registration() + } + g.indent++ + g.gen_function_defer_prelude() + g.gen_profile_fn_begin('main', 'main', 'main', false) + for stmt in stmts { + g.tc.cur_file = stmt.file + g.tc.cur_module = stmt.module + g.gen_top_level_main_stmt(stmt.id) + } + g.gen_all_defers() + g.gen_profile_fn_exit() + g.writeln('return 0;') + g.indent-- + g.writeln('}') + g.writeln('') + if 'main' in g.print_fn_names || 'main__main' in g.print_fn_names { + println(g.sb.after(fn_start_pos)) + } + g.cur_param_names = old_param_names + g.cur_param_type_values = old_param_type_values + g.cur_param_types = old_param_types.move() + g.cur_concrete_optional_params = old_concrete_optional_params.move() + g.cur_mut_params = old_mut_params.move() + g.cur_mut_pointer_params = old_mut_pointer_params.move() + g.cur_mut_param_owners = old_mut_param_owners.move() + g.cur_fn_name = old_fn_name + g.loop_depth = 0 + g.loop_label_depths = map[string]int{} + g.map_loop_copyback_guards = []MapLoopCopybackGuard{} + g.goto_label_lock_scopes = map[string][]int{} + g.pending_loop_label = '' + g.tc.cur_file = old_tc_file + g.tc.cur_module = old_tc_module + g.pop_scope() + g.ierror_stack_pointer_aliases = old_ierror_stack_pointer_aliases + g.ierror_owned_pointer_by_owner = old_ierror_owned_pointer_by_owner.move() + g.local_pointer_storage_by_owner = old_local_pointer_storage_by_owner.move() + g.local_c_type_by_owner = old_local_c_type_by_owner.move() + g.local_pointer_alias_by_owner = old_local_pointer_alias_by_owner.move() + g.local_pointer_alias_mut_param = old_local_pointer_alias_mut_param.move() + g.local_shared_storage_by_owner = old_local_shared_storage_by_owner.move() + g.local_fn_value_c_name_by_owner = old_local_fn_value_c_name_by_owner.move() +} + +fn (mut g FlatGen) gen_top_level_main_stmt(id flat.NodeId) { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return + } + node := g.a.nodes[int(id)] + if node.kind in [.block, .comptime_if] { + for i in 0 .. node.children_count { + child_id := g.a.child(&node, i) + if g.cgen_is_top_level_stmt(child_id) { + g.gen_top_level_main_stmt(child_id) + } + } + return + } + g.gen_node(id) +} + +fn (mut g FlatGen) gen_test_main() { + tests, hooks := g.test_harness_fns() + g.tc.cur_module = 'main' + fn_start_pos := g.sb.len + if g.show_test_stats && tests.len > 0 { + g.writeln('static double __v3_test_now_ms(void) {') + g.writeln('#if defined(_WIN32)') + g.writeln('\treturn (double)GetTickCount64();') + g.writeln('#else') + g.writeln('\tstruct timespec ts;') + g.writeln('\tclock_gettime(CLOCK_MONOTONIC, &ts);') + g.writeln('\treturn ((double)ts.tv_sec * 1000.0) + ((double)ts.tv_nsec / 1000000.0);') + g.writeln('#endif') + g.writeln('}') + g.writeln('') + } + g.writeln('int main(int argc, char** argv) {') + if g.has_builtins { + g.writeln('\tg_main_argc = argc;') + g.writeln('\tg_main_argv = argv;') + } + g.gen_compiler_vexe_env_setup() + g.gen_coverage_registration() + g.gen_profile_startup_enable() + if g.const_runtime_inits.len > 0 || g.runtime_inits.len > 0 || g.module_init_fns.len > 0 + || g.global_inits.len > 0 { + g.writeln('\t_vinit();') + } + g.gen_profile_registration() + g.gen_executable_cleanup_registration() + g.indent++ + if g.show_test_stats && tests.len > 0 { + g.writeln('double __v3_test_suite_start_ms = __v3_test_now_ms();') + } + if hooks.testsuite_begin.len > 0 { + g.writeln('${hooks.testsuite_begin}();') + } + if g.show_test_stats && tests.len > 0 { + g.writeln('printf("running tests in: %s\\n", "${c_escape(tests[0].file)}");') + } + track_test_results := g.show_test_stats || g.show_test_summary + if track_test_results { + g.writeln('int __v3_test_passes = 0;') + } + for idx, test_fn in tests { + if g.show_test_stats { + g.writeln('double __v3_test_start_ms_${idx} = __v3_test_now_ms();') + g.writeln('int __v3_test_assertions_before_${idx} = __v3_test_assertions;') + } + g.writeln('int __v3_test_failures_before_${idx} = __v3_test_failures;') + g.writeln('__v3_test_jump_active = 1;') + g.writeln('if (setjmp(__v3_test_jump_buffer) == 0) {') + g.indent++ + if hooks.before_each.len > 0 { + g.writeln('${hooks.before_each}();') + } + g.writeln('if (__v3_test_failures == __v3_test_failures_before_${idx}) {') + g.indent++ + g.gen_test_fn_call(test_fn, idx) + g.indent-- + g.writeln('}') + g.indent-- + g.writeln('}') + g.writeln('__v3_test_jump_active = 0;') + if hooks.after_each.len > 0 { + g.writeln('__v3_test_jump_active = 1;') + g.writeln('if (setjmp(__v3_test_jump_buffer) == 0) {') + g.indent++ + g.writeln('${hooks.after_each}();') + g.indent-- + g.writeln('}') + g.writeln('__v3_test_jump_active = 0;') + } + if g.show_test_stats { + g.writeln('double __v3_test_elapsed_ms_${idx} = __v3_test_now_ms() - __v3_test_start_ms_${idx};') + g.writeln('int __v3_test_assertions_run_${idx} = __v3_test_assertions - __v3_test_assertions_before_${idx};') + } + if track_test_results { + g.writeln('if (__v3_test_failures == __v3_test_failures_before_${idx}) {') + g.indent++ + g.writeln('__v3_test_passes++;') + } + if g.show_test_stats { + g.writeln('printf(" OK [${idx + 1}/${tests.len}] %9.3f ms %d assert%s | main.${c_escape(test_fn.name)}()\\n", __v3_test_elapsed_ms_${idx}, __v3_test_assertions_run_${idx}, __v3_test_assertions_run_${idx} == 1 ? "" : "s");') + g.indent-- + g.writeln('} else {') + g.indent++ + g.writeln('printf(" FAIL [${idx + 1}/${tests.len}] %9.3f ms %d assert%s | main.${c_escape(test_fn.name)}()\\n", __v3_test_elapsed_ms_${idx}, __v3_test_assertions_run_${idx}, __v3_test_assertions_run_${idx} == 1 ? "" : "s");') + } + if track_test_results { + g.indent-- + g.writeln('}') + } + } + if hooks.testsuite_end.len > 0 { + g.writeln('${hooks.testsuite_end}();') + } + if g.show_test_stats && tests.len > 0 { + file_name := os.file_name(tests[0].file) + g.writeln('double __v3_test_suite_elapsed_ms = __v3_test_now_ms() - __v3_test_suite_start_ms;') + g.writeln('if (__v3_test_failures > 0) {') + g.indent++ + g.writeln("printf(\" Summary for running V tests in \\\"%s\\\": %d failed, %d passed, ${tests.len} total. Elapsed time: %.3f ms.\\n\", \"${c_escape(file_name)}\", ${tests.len} - __v3_test_passes, __v3_test_passes, __v3_test_suite_elapsed_ms);") + g.indent-- + g.writeln('} else {') + g.indent++ + g.writeln("printf(\" Summary for running V tests in \\\"%s\\\": %d passed, ${tests.len} total. Elapsed time: %.3f ms.\\n\", \"${c_escape(file_name)}\", __v3_test_passes, __v3_test_suite_elapsed_ms);") + g.indent-- + g.writeln('}') + } + if g.show_test_summary { + g.writeln('if (__v3_test_failures > 0) {') + g.indent++ + g.writeln('printf("Summary for all V _test.v files: %d failed, %d passed, ${tests.len} total.\\n", ${tests.len} - __v3_test_passes, __v3_test_passes);') + g.indent-- + g.writeln('} else {') + g.indent++ + g.writeln('printf("Summary for all V _test.v files: %d passed, ${tests.len} total.\\n", __v3_test_passes);') + g.indent-- + g.writeln('}') + } + g.writeln('return __v3_test_failures > 0;') + g.indent-- + g.writeln('}') + g.writeln('') + if 'main' in g.print_fn_names { + println(g.sb.after(fn_start_pos)) + } +} + +fn (mut g FlatGen) gen_test_fn_call(test_fn TestHarnessFn, idx int) { + if test_fn.ret is types.OptionType || test_fn.ret is types.ResultType { + ct := g.optional_type_name(test_fn.ret) + tmp_name := '__test_opt_${idx}' + g.writeln('${ct} ${tmp_name} = ${test_fn.c_name}();') + g.writeln('if (!${tmp_name}.ok) {') + g.indent++ + g.writeln('string __test_err_msg_${idx} = IError__msg(&${tmp_name}.err);') + g.writeln('fprintf(stderr, "%s:%d: fn %s failed propagation with error: %.*s\\n", "${c_escape(test_fn.file)}", ${test_fn.failure_line}, "${c_escape(test_fn.name)}", __test_err_msg_${idx}.len, __test_err_msg_${idx}.str);') + g.writeln('__v3_test_failures++;') + g.indent-- + g.writeln('}') + return + } + g.writeln('${test_fn.c_name}();') +} + +fn (g &FlatGen) test_harness_fns() ([]TestHarnessFn, TestHarnessHooks) { + mut tests := []TestHarnessFn{} + mut hooks := TestHarnessHooks{} + for file_idx in g.top_level_nodes() { + file_node := g.a.nodes[file_idx] + if !g.is_user_test_file_node(file_idx, file_node) { + continue + } + module_name := g.test_file_module_name(file_node) + mut decl_ids := []flat.NodeId{} + g.collect_test_harness_decl_ids(file_node, mut decl_ids) + for child_id in decl_ids { + child := g.a.nodes[int(child_id)] + cname := g.qualified_fn_name_in_module_c(module_name, child.value) + match child.value { + 'testsuite_begin' { + if hooks.testsuite_begin.len == 0 && g.is_supported_test_hook_decl(child) { + hooks.testsuite_begin = cname + } + } + 'testsuite_end' { + if hooks.testsuite_end.len == 0 && g.is_supported_test_hook_decl(child) { + hooks.testsuite_end = cname + } + } + 'before_each' { + if hooks.before_each.len == 0 && g.is_supported_test_hook_decl(child) { + hooks.before_each = cname + } + } + 'after_each' { + if hooks.after_each.len == 0 && g.is_supported_test_hook_decl(child) { + hooks.after_each = cname + } + } + else { + if child.value.starts_with('test_') && g.is_supported_test_fn_decl(child) { + if !g.test_fn_matches_run_only(module_name, child.value) { + continue + } + tests << TestHarnessFn{ + node_id: child_id + name: child.value + c_name: cname + ret: g.parse_node_type(&child) + file: file_node.value + failure_line: g.test_fn_failure_line(child_id) + } + } + } + } + } + } + return tests, hooks +} + +fn (g &FlatGen) test_fn_matches_run_only(module_name string, name string) bool { + if g.test_run_only.len == 0 { + return true + } + qualified_name := '${module_name}.${name}' + for pattern in g.test_run_only { + if name.match_glob(pattern) || qualified_name.match_glob(pattern) { + return true + } + } + return false +} + +fn (g &FlatGen) test_fn_failure_line(id flat.NodeId) int { + line := g.test_fn_propagation_line(id) + if line > 0 { + return line + } + source_line := g.test_fn_source_failure_line(id) + if source_line > 0 { + return source_line + } + if int(id) >= 0 && int(id) < g.a.nodes.len { + if position := g.a.source_position(g.a.nodes[int(id)].pos) { + return position.line + } + } + return 1 +} + +fn (g &FlatGen) test_fn_source_failure_line(id flat.NodeId) int { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return 0 + } + node := g.a.nodes[int(id)] + file := g.a.source_files[node.pos.id] or { return 0 } + lines := os.read_lines(file.name) or { return 0 } + start_line := file.position(node.pos).line + for line_index in start_line .. lines.len { + trimmed := lines[line_index].trim_space() + if trimmed.starts_with('fn ') { + break + } + if trimmed.contains(' or {') || trimmed.contains(')!') || trimmed.contains(']!') + || trimmed.ends_with('!') || trimmed.starts_with('return error(') { + return line_index + 1 + } + } + return 0 +} + +fn (g &FlatGen) test_fn_propagation_line(id flat.NodeId) int { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return 0 + } + node := g.a.nodes[int(id)] + if node.kind in [.or_expr, .return_stmt] { + if position := g.a.source_position(node.pos) { + return position.line + } + } + for i in 0 .. node.children_count { + child_id := g.a.child(&node, i) + if int(child_id) < 0 || int(child_id) >= g.a.nodes.len { + continue + } + child := g.a.nodes[int(child_id)] + if child.kind in [.fn_decl, .c_fn_decl, .fn_literal] { + continue + } + line := g.test_fn_propagation_line(child_id) + if line > 0 { + return line + } + } + return 0 +} + +fn (g &FlatGen) collect_test_harness_decl_ids(node flat.Node, mut ids []flat.NodeId) { + if node.kind != .file && node.kind != .block { + return + } + for i in 0 .. node.children_count { + child_id := g.a.child(&node, i) + if int(child_id) < g.a.user_code_start { + continue + } + child := g.a.nodes[int(child_id)] + if child.kind == .fn_decl { + ids << child_id + } else if child.kind == .block { + g.collect_test_harness_decl_ids(child, mut ids) + } + } +} + +fn (g &FlatGen) is_supported_test_fn_decl(node flat.Node) bool { + if node.generic_params().len > 0 { + return false + } + if g.test_fn_param_count(node) != 0 { + return false + } + return test_harness_fn_return_supported(g.parse_node_type(&node)) +} + +fn (g &FlatGen) is_supported_test_hook_decl(node flat.Node) bool { + if node.generic_params().len > 0 { + return false + } + return g.test_fn_param_count(node) == 0 && g.parse_node_type(&node) is types.Void +} + +fn (g &FlatGen) test_fn_param_count(node flat.Node) int { + mut count := 0 + for i in 0 .. node.children_count { + child := g.a.child_node(&node, i) + if child.kind != .param { + if g.prefix_param_scan { + break + } + continue + } + count++ + } + return count +} + +fn test_harness_fn_return_supported(ret types.Type) bool { + return ret is types.Void || ret is types.OptionType || ret is types.ResultType +} + +fn (g &FlatGen) is_user_test_file_node(file_idx int, file_node flat.Node) bool { + if file_idx < g.a.user_code_start || file_node.kind != .file || file_node.children_count == 0 { + return false + } + return g.test_files[file_node.value] +} + +fn (g &FlatGen) test_file_module_name(file_node flat.Node) string { + for i in 0 .. file_node.children_count { + child := g.a.child_node(&file_node, i) + if child.kind == .module_decl { + return child.value + } + } + return '' +} + +// collect_function_defer_ids updates collect function defer ids state for c. +fn (mut g FlatGen) collect_function_defer_ids(node flat.Node) []flat.NodeId { + mut ids := []flat.NodeId{} + for i in 0 .. node.children_count { + g.collect_function_defer_ids_from(g.a.child(&node, i), mut ids) + } + return ids +} + +// collect_function_defer_ids_from updates collect function defer ids from state for c. +fn (mut g FlatGen) collect_function_defer_ids_from(id flat.NodeId, mut ids []flat.NodeId) { + if !g.valid_node_id(id) { + return + } + node := g.a.nodes[int(id)] + if node.kind == .fn_decl || node.kind == .c_fn_decl || node.kind == .fn_literal { + return + } + if node.kind == .defer_stmt && node.value == 'function' { + ids << id + return + } + for i in 0 .. node.children_count { + g.collect_function_defer_ids_from(g.a.child(&node, i), mut ids) + } +} + +// prepare_function_defers supports prepare function defers handling for FlatGen. +fn (mut g FlatGen) prepare_function_defers(fn_defer_ids []flat.NodeId) { + for idx, defer_id in fn_defer_ids { + g.fn_defer_counts[int(defer_id)] = '${g.cname(g.cur_fn_name)}_defer_${idx}_count' + defer_node := g.a.nodes[int(defer_id)] + if defer_node.children_count > 0 { + g.collect_function_defer_captures(g.a.child(&defer_node, 0)) + } + } +} + +// collect_function_defer_captures updates collect function defer captures state for c. +fn (mut g FlatGen) collect_function_defer_captures(id flat.NodeId) { + if !g.valid_node_id(id) { + return + } + node := g.a.nodes[int(id)] + if node.kind == .fn_decl || node.kind == .c_fn_decl || node.kind == .fn_literal { + return + } + if node.kind == .ident { + g.add_function_defer_capture(id, node.value) + } + for i in 0 .. node.children_count { + g.collect_function_defer_captures(g.a.child(&node, i)) + } +} + +// add_function_defer_capture updates add function defer capture state for FlatGen. +fn (mut g FlatGen) add_function_defer_capture(id flat.NodeId, name string) { + if name.len == 0 || name == '_' || name in g.cur_param_names || g.has_import_alias(name) + || name in g.global_modules || name in g.defer_capture_types { + return + } + typ := g.usable_expr_type(id) + if typ is types.Void || typ is types.Unknown || typ is types.FnType { + return + } + ct := g.value_c_type(typ) + if ct.len == 0 || ct == 'void' || ct.starts_with('fn_ptr:') { + return + } + g.defer_capture_names << name + g.defer_capture_types[name] = typ +} + +// gen_function_defer_prelude emits function defer prelude output for c. +fn (mut g FlatGen) gen_function_defer_prelude() { + for _, count_name in g.fn_defer_counts { + g.writeln('int ${count_name} = 0;') + } + for name in g.defer_capture_names { + typ := g.defer_capture_types[name] or { continue } + ct := g.value_c_type(typ) + g.write('${ct} ${g.cname(name)} = ') + g.gen_default_value_for_type(typ) + g.writeln(';') + g.tc.cur_scope.insert(name, typ) + } +} + +// set_cur_fn_ret updates set cur fn ret state for c. +fn (mut g FlatGen) set_cur_fn_ret(ret_type types.Type) { + ret_abi_type := optional_result_unalias_type(ret_type) + g.cur_fn_ret = ret_abi_type + g.cur_fn_ret_is_optional = false + g.cur_fn_ret_base = types.Type(types.void_) + if ret_abi_type is types.OptionType { + g.cur_fn_ret_is_optional = true + g.cur_fn_ret_base = ret_abi_type.base_type + } else if ret_abi_type is types.ResultType { + g.cur_fn_ret_is_optional = true + g.cur_fn_ret_base = ret_abi_type.base_type + } +} + +// gen_compiler_vexe_env_setup emits compiler vexe env setup output for c. +fn (mut g FlatGen) gen_compiler_vexe_env_setup() { + if !g.compiler_vexe_env_setup || (g.compiler_vexe.len == 0 && g.compiler_vroot.len == 0) { + return + } + root := c_escape(g.compiler_vroot) + mut runtime_vexe := g.compiler_vexe + if runtime_vexe.len > 0 && g.compiler_vroot.len > 0 { + clean_root := g.compiler_vroot.replace('\\', '/').trim_right('/') + clean_vexe := runtime_vexe.replace('\\', '/') + if clean_root.len > 0 && !(clean_vexe == '${clean_root}/v' + || clean_vexe.starts_with('${clean_root}/')) { + runtime_vexe = '${g.compiler_vroot}/v' + } + } + g.writeln('\tif (getenv("VEXE") == NULL || getenv("VEXE")[0] == 0) {') + if runtime_vexe.len > 0 { + vexe := c_escape(runtime_vexe) + g.writeln('\t\tconst char* v3_vexe = "${vexe}";') + } else { + g.writeln('\t\tconst char* v3_arg0 = argc > 0 ? argv[0] : "v";') + g.writeln("\t\tconst char* v3_base = strrchr(v3_arg0, '/');") + g.writeln('\t\tv3_base = v3_base == NULL ? v3_arg0 : v3_base + 1;') + g.writeln('\t\tif (v3_base[0] == 0) v3_base = "v";') + g.writeln('\t\tconst char* v3_checkout_root = "${root}";') + g.writeln('\t\tchar v3_checkout_vexe[4096];') + g.writeln('\t\tsnprintf(v3_checkout_vexe, sizeof(v3_checkout_vexe), "%s/%s", v3_checkout_root, v3_base);') + g.writeln('\t\tif (access(v3_checkout_vexe, F_OK) != 0) snprintf(v3_checkout_vexe, sizeof(v3_checkout_vexe), "%s/v", v3_checkout_root);') + g.writeln('\t\tchar v3_src_real[4096];') + g.writeln('\t\tchar* v3_src_real_result = realpath(v3_arg0, v3_src_real);') + g.writeln('\t\tconst char* v3_vexe = v3_src_real_result != NULL ? v3_src_real : v3_arg0;') + g.writeln('\t\tif (access(v3_checkout_vexe, F_OK) == 0) v3_vexe = v3_checkout_vexe;') + } + g.writeln('\t\tif (v3_vexe[0] != 0) {') + g.writeln('#ifdef _WIN32') + g.writeln('\t\t\t_putenv_s("VEXE", v3_vexe);') + g.writeln('#else') + g.writeln('\t\t\tsetenv("VEXE", v3_vexe, 1);') + g.writeln('#endif') + g.writeln('\t\t}') + g.writeln('\t}') +} + +// gen_defers emits defers output for c. +fn (mut g FlatGen) gen_defers() { + g.gen_defers_from(0) +} + +// gen_all_defers emits all defers output for c. +fn (mut g FlatGen) gen_all_defers() { + g.gen_all_defers_range(0, g.defers.len) +} + +fn (mut g FlatGen) gen_all_defers_range(start int, end int) { + mut defer_start := start + if defer_start < 0 { + defer_start = 0 + } + mut defer_index := if end < g.defers.len { end } else { g.defers.len } + mut fn_defer_index := g.fn_defers.len + for defer_index > defer_start || fn_defer_index > 0 { + if defer_index <= defer_start { + fn_defer_index-- + g.gen_fn_defer_at(fn_defer_index) + continue + } + if fn_defer_index <= 0 { + defer_index-- + g.gen_defer_at(defer_index) + continue + } + defer_node := g.a.nodes[int(g.defers[defer_index - 1])] + fn_defer_node := g.a.nodes[int(g.fn_defers[fn_defer_index - 1])] + if defer_node.pos.id == fn_defer_node.pos.id + && defer_node.pos.offset > fn_defer_node.pos.offset { + defer_index-- + g.gen_defer_at(defer_index) + } else { + fn_defer_index-- + g.gen_fn_defer_at(fn_defer_index) + } + } +} + +// gen_defers_from emits defers from output for c. +fn (mut g FlatGen) gen_defers_from(start int) { + g.gen_defers_range(start, g.defers.len) +} + +fn (mut g FlatGen) gen_defers_range(start int, end int) { + if g.defers.len == 0 { + return + } + mut defer_start := start + if defer_start < 0 { + defer_start = 0 + } + mut defer_end := end + if defer_end > g.defers.len { + defer_end = g.defers.len + } + if defer_start >= defer_end { + return + } + mut i := defer_end + for i > defer_start { + i-- + g.gen_defer_at(i) + } +} + +fn (mut g FlatGen) gen_defer_at(index int) { + defer_body := g.a.nodes[int(g.defers[index])] + g.writeln('{') + g.indent++ + for j in 0 .. defer_body.children_count { + g.gen_node(g.a.child(&defer_body, j)) + } + g.indent-- + g.writeln('}') +} + +// gen_fn_defers emits fn defers output for c. +fn (mut g FlatGen) gen_fn_defers() { + if g.fn_defers.len == 0 { + return + } + mut i := g.fn_defers.len + for i > 0 { + i-- + g.gen_fn_defer_at(i) + } +} + +fn (mut g FlatGen) gen_fn_defer_at(index int) { + defer_id := g.fn_defers[index] + defer_node := g.a.nodes[int(defer_id)] + defer_body := g.a.nodes[int(g.a.child(&defer_node, 0))] + count_name := g.fn_defer_counts[int(defer_id)] or { '0' } + iter_name := '${count_name}_i' + g.writeln('for (int ${iter_name} = 0; ${iter_name} < ${count_name}; ${iter_name}++) {') + g.indent++ + for j in 0 .. defer_body.children_count { + g.gen_node(g.a.child(&defer_body, j)) + } + g.indent-- + g.writeln('}') +} + +// trim_defers transforms trim defers data for c. +fn (mut g FlatGen) trim_defers(start int) { + if start >= g.defers.len { + return + } + g.defers = g.defers[..start].clone() + for i, scope_start in g.scope_defer_starts { + if scope_start > start { + g.scope_defer_starts[i] = start + } + } +} + +// gen_ierror_from_error_call converts gen ierror from error call data for c. +fn (mut g FlatGen) gen_ierror_from_error_call(node flat.Node) { + fn_node := g.a.child_node(&node, 0) + type_id := g.ierror_type_id_for_pattern('MessageError') + empty_sid := g.intern_string('') + g.write('(IError){._typ = ${type_id}, ._object = (MessageError*)memdup(&(MessageError){.msg = ') + if node.children_count > 1 { + g.gen_expr(g.a.child(&node, 1)) + } else { + g.write('_S("")') + } + g.write(', .code = ') + if fn_node.value == 'error_with_code' && node.children_count > 2 { + g.gen_expr(g.a.child(&node, 2)) + } else { + g.write('0') + } + // The boxed MessageError owns the payload; semantic consumers use dynamic dispatch. + g.write('}, sizeof(MessageError)), ._object_is_boxed = true') + g.write(', .message = _str_${empty_sid}, .code = 0}') +} + +// gen_optional_error_from_call converts gen optional error from call data for c. +fn (mut g FlatGen) gen_optional_error_from_call(ct string, node flat.Node) { + g.write('(${ct}){.ok = false, .err = ') + g.gen_ierror_from_error_call(node) + g.write('}') +} + +fn (g &FlatGen) is_runtime_new_map_call(node flat.Node) bool { + if node.children_count < 3 { + return false + } + key_size := g.a.child_node(&node, 1) + value_size := g.a.child_node(&node, 2) + return key_size.kind == .sizeof_expr && value_size.kind == .sizeof_expr +} + +fn (g &FlatGen) main_runtime_shadow_call_c_name(node flat.Node, fn_node flat.Node) ?string { + if fn_node.kind != .ident || g.is_runtime_new_map_call(node) { + return none + } + shadow_name := g.main_runtime_shadow_fn_c_name(g.tc.cur_module, fn_node.value) or { + return none + } + supplied_args := int(node.children_count) - 1 + if g.tc.cur_module.len == 0 || g.tc.cur_module == 'main' { + if g.main_fn_decl_arg_count_matches(fn_node.value, supplied_args) { + return shadow_name + } + for module_name in ['main', ''] { + if params := g.fn_decl_param_types[fn_decl_module_key(module_name, fn_node.value)] { + if params.len == supplied_args { + return shadow_name + } + } + } + } + return none +} + +fn (g &FlatGen) main_fn_decl_arg_count_matches(name string, supplied_args int) bool { + mut cur_module := '' + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + match node.kind { + .file { + cur_module = '' + } + .module_decl { + cur_module = node.value + } + .fn_decl { + if cur_module !in ['', 'main'] || node.value != name { + continue + } + mut param_count := 0 + for child_idx in 0 .. node.children_count { + if g.a.child_node(&node, child_idx).kind == .param { + param_count++ + } + } + if param_count == supplied_args { + return true + } + } + else {} + } + } + return false +} + +fn (mut g FlatGen) gen_map_mutation_call_with_loop_copyback_guard(node flat.Node, fn_name string, target_name string, resolved_target_name string) bool { + if g.map_loop_copyback_guards.len == 0 { + return false + } + is_map_delete := fn_name == 'map__delete' || target_name == 'map__delete' + || resolved_target_name == 'map__delete' + is_map_set := fn_name == 'map__set' || target_name == 'map__set' + || resolved_target_name == 'map__set' + if (!is_map_delete && !is_map_set) || (is_map_delete && node.children_count != 3) + || (is_map_set && node.children_count != 4) { + return false + } + map_tmp := '__map_mut_target_${g.tmp_count}' + g.tmp_count++ + key_tmp := '__map_mut_key_${g.tmp_count}' + g.tmp_count++ + g.write('({ map* ${map_tmp} = ') + g.gen_expr(g.a.child(&node, 1)) + g.write('; void* ${key_tmp} = ') + g.gen_expr(g.a.child(&node, 2)) + mut val_tmp := '' + if is_map_set { + val_tmp = '__map_mut_val_${g.tmp_count}' + g.tmp_count++ + g.write('; void* ${val_tmp} = ') + g.gen_expr(g.a.child(&node, 3)) + } + g.writeln(';') + g.gen_map_loop_copyback_dirty_checks(map_tmp, key_tmp) + if is_map_set { + g.write('map__set(${map_tmp}, ${key_tmp}, ${val_tmp}); })') + } else { + g.write('map__delete(${map_tmp}, ${key_tmp}); })') + } + return true +} + +fn (g &FlatGen) owned_capture_context_type(data_id flat.NodeId) ?types.Type { + mut current := data_id + for _ in 0 .. 8 { + if int(current) < 0 || int(current) >= g.a.nodes.len { + return none + } + node := g.a.nodes[int(current)] + if node.kind in [.cast_expr, .paren] && node.children_count == 1 { + current = g.a.child(&node, 0) + continue + } + if node.kind == .prefix && node.op == .amp && node.children_count == 1 { + context_type := types.unwrap_pointer(g.tc.resolve_type(g.a.child(&node, 0))) + if context_type is types.Struct && context_type.name.ends_with('_Ctx') + && g.tc.ownership_type_requires_destruction(context_type) { + return context_type + } + } + return none + } + return none +} + +fn (mut g FlatGen) gen_owned_capture_closure_create(id flat.NodeId, node flat.Node, fn_name string, target_name string, resolved_target_name string) bool { + if node.children_count != 4 + || !(fn_name in ['closure.closure_create_with_data', 'closure__closure_create_with_data'] + || target_name in ['closure.closure_create_with_data', 'closure__closure_create_with_data'] + || resolved_target_name in ['closure.closure_create_with_data', 'closure__closure_create_with_data']) { + return false + } + context_type := g.owned_capture_context_type(g.a.child(&node, 2)) or { return false } + context_ct := g.tc.c_type(context_type) + drop_name := '_flctxdrop_${int(id)}' + drop_body := g.ownership_drop_value_to_string(context_type, '*ctx') + g.add_spawn_wrapper_def('static void ${drop_name}(void* data) { ${context_ct}* ctx = (${context_ct}*)data;\n${drop_body}}') + g.write('closure__closure_create_with_data_and_drop(') + for i in 1 .. node.children_count { + if i > 1 { + g.write(', ') + } + g.gen_expr(g.a.child(&node, i)) + } + g.write(', (void*)${drop_name})') + return true +} + +fn (g &FlatGen) trace_call_name(fn_node flat.Node, fn_name string, target_name string, resolved_target_name string) ?string { + if 'trace' !in g.compile_values || g.inside_trace_call { + return none + } + if g.tc.cur_module in ['builtin', 'debug'] { + return none + } + if target_name.starts_with('C.') || resolved_target_name.starts_with('C.') { + return none + } + mut name := if resolved_target_name.len > 0 { resolved_target_name } else { target_name } + if name.len == 0 && fn_node.kind == .ident + && g.non_generic_fn_decl_exists_in_module(fn_name, g.tc.cur_module) { + name = qualify_name_in_module(g.tc.cur_module, fn_name) + } + if name.len == 0 || name.starts_with('builtin.') || name.starts_with('builtin__') + || name.starts_with('debug.') || name.starts_with('debug__') || name.starts_with('v.debug.') + || name.starts_with('v__debug__') || name.starts_with('closure.') + || name.starts_with('closure__') { + return none + } + for candidate in [name, target_name, fn_name] { + clean := if candidate.starts_with('builtin.') { + candidate['builtin.'.len..] + } else { + candidate + } + if fn_decl_module_key('builtin', clean) in g.fn_decl_ret_types { + return none + } + } + if !name.contains('.') && fn_decl_module_key(g.tc.cur_module, name) !in g.fn_decl_ret_types { + return none + } + // Compiler intrinsics and unresolved function values do not have the stable + // V function declaration needed by the tracing ABI. + if name.starts_with('__v3_') + || (resolved_target_name.len == 0 && fn_node.kind !in [.ident, .selector]) { + return none + } + return name +} + +fn (mut g FlatGen) gen_traced_call(id flat.NodeId, trace_name string) bool { + mut ret_type := g.declared_call_return_type(id) + if ret_type is types.Unknown { + ret_type = g.usable_expr_type(id) + } + if ret_type is types.Unknown { + return false + } + if _ := array_fixed_type(ret_type) { + // Fixed-array calls use a generated ABI wrapper that the surrounding + // expression unwraps. Leave those calls direct until that wrapper is + // represented in the semantic type. + return false + } + g.inside_trace_call = true + call_expr := g.expr_to_string(id) + g.inside_trace_call = false + if call_expr.len == 0 { + return false + } + trace_sid := g.intern_string(trace_name) + trace_global := g.cname('debug.g_trace') + before_hook := g.cname('debug.before_call_hook') + after_hook := g.cname('debug.after_call_hook') + g.write('({ if (!${trace_global}.in_hook) { ${before_hook}(_str_${trace_sid}); } ') + if ret_type is types.Void { + g.write('${call_expr}; ') + g.write('if (!${trace_global}.in_hook) { ${after_hook}(_str_${trace_sid}); } })') + return true + } + tmp := g.tmp_count + g.tmp_count++ + g.write('${g.value_c_type(ret_type)} _trace_ret_${tmp} = ${call_expr}; ') + g.write('if (!${trace_global}.in_hook) { ${after_hook}(_str_${trace_sid}); } ') + g.write('_trace_ret_${tmp}; })') + return true +} + +// gen_call emits call output for c. +@[direct_array_access] +fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) { + mut fn_node := g.a.child_node(&node, 0) + target_name := g.call_target_name(g.a.child(&node, 0)) + fn_name := if fn_node.kind == .selector && fn_node.value in ['error', 'error_with_code'] { + target_name + } else { + fn_node.value + } + resolved_target_name := g.tc.resolved_call_name(id) or { '' } + if trace_name := g.trace_call_name(fn_node, fn_name, target_name, resolved_target_name) { + if g.gen_traced_call(id, trace_name) { + return + } + } + if fn_node.kind == .ident && fn_name == 'v3_heap_array' && node.children_count == 2 { + arg_id := g.a.child(&node, 1) + arg_type := types.unwrap_pointer(g.usable_expr_type(arg_id)) + if arg_type is types.Array { + g.write('v3_heap_array(') + g.gen_expr_with_expected_type(arg_id, arg_type) + g.write(')') + return + } + } + if fn_node.kind == .ident && fn_name == '__v3_closure_current_data' { + g.write('closure__g_closure.closure_get_data()') + return + } + if fn_node.kind == .ident && fn_name.starts_with('fn_ptr:') && node.children_count == 2 { + g.write('(${g.resolve_fn_ptr_type(fn_name)})(') + g.gen_expr(g.a.child(&node, 1)) + g.write(')') + return + } + if fn_node.kind == .selector && fn_node.value == 'value' + && !g.call_callee_is_module_selector(node) { + if fn_type := fn_type_from(g.usable_expr_type(g.a.child(&node, 0))) { + g.gen_expr(g.a.child(&node, 0)) + g.write('(') + for i in 1 .. node.children_count { + if i > 1 { + g.write(', ') + } + arg_id := g.a.child(&node, i) + if i - 1 < fn_type.params.len { + g.gen_expr_with_expected_type(arg_id, fn_type.params[i - 1]) + } else { + g.gen_expr(arg_id) + } + } + g.write(')') + return + } + } + if node.children_count == 3 && (target_name == 'string__eq' || fn_name == 'string__eq') { + lhs_id := g.a.child(&node, 1) + rhs_id := g.a.child(&node, 2) + if g.expr_is_non_string_scalar_value(lhs_id) || g.expr_is_non_string_scalar_value(rhs_id) { + g.write('(') + g.gen_expr(lhs_id) + g.write(' == ') + g.gen_expr(rhs_id) + g.write(')') + return + } + } + if target_name == 'array.pointers' || fn_name == 'array.pointers' { + if node.children_count > 1 { + arg_id := g.a.child(&node, 1) + g.gen_array_pointers_expr(arg_id, g.tc.resolve_type(arg_id) is types.Pointer) + } else { + g.write('array_new(sizeof(voidptr), 0, 0)') + } + return + } + if g.gen_fixed_array_get_call(node, fn_name, target_name) { + return + } + if fn_name == '__v3_isreftype' || target_name == '__v3_isreftype' { + g.write(if g.isreftype_call(node) { 'true' } else { 'false' }) + return + } + if target_name.starts_with('C.') || resolved_target_name.starts_with('C.') { + if shadow_name := g.main_runtime_shadow_call_c_name(node, fn_node) { + g.write(shadow_name) + g.write('(') + g.gen_call_args('main.${fn_name}', node, 1) + g.write(')') + return + } + } + if g.gen_owned_capture_closure_create(id, node, fn_name, target_name, resolved_target_name) { + return + } + // Generic templates are transformed before their concrete type arguments are + // known. A builtin print call can therefore reach cgen with an unconverted `T` + // argument that monomorphization has since made concrete. Stringify that final + // value here, using the same recursive conversion used by implicit interface + // stringification, instead of passing a scalar directly to `println(string)`. + if node.children_count == 2 && fn_name in ['println', 'eprintln', 'print', 'eprint'] + && (resolved_target_name.len == 0 || resolved_target_name == fn_name + || resolved_target_name == 'builtin.${fn_name}') { + arg_id := g.a.child(&node, 1) + arg_type := g.usable_expr_type(arg_id) + if g.interface_unaliased_type(arg_type) !is types.String { + arg_expr := g.expr_to_string(arg_id) + mut stringify_stack := []string{} + if str_expr := g.interface_implicit_str_expr(arg_type, arg_expr, false, mut + stringify_stack) + { + g.write(fn_name) + g.write('(') + g.write(str_expr) + g.write(')') + return + } + } + } + // Ownership lowering appends calls after checking, while a generic builtin + // drop specialized inside another module can retain its original source + // position. Recognize both forms by their resolved/synthetic names. + is_ownership_drop := (!node.pos.is_valid() && ownership_synthetic_drop_name(fn_name)) + || g.ownership_drop_intrinsic_name(fn_name) + || (target_name.len > 0 && g.ownership_drop_intrinsic_name(target_name)) + || (g.tc.cur_module == 'builtin' && ownership_synthetic_drop_name(fn_name)) + || (resolved_target_name.len > 0 && g.ownership_drop_intrinsic_name(resolved_target_name)) + if node.children_count == 2 && is_ownership_drop { + arg_id := g.a.child(&node, 1) + arg_type := g.usable_expr_type(arg_id) + g.writeln('({') + g.indent++ + mut expr := '' + if g.expr_is_addressable(arg_id) { + if g.expr_is_stable_for_reuse(arg_id) { + expr = g.expr_to_string(arg_id) + } else { + tmp := g.tmp_count + g.tmp_count++ + ct := g.value_c_type(arg_type) + g.write('${ct}* _drop_owned_ref${tmp} = &(') + g.gen_expr(arg_id) + g.writeln(');') + expr = '*_drop_owned_ref${tmp}' + } + } else { + tmp := g.tmp_count + g.tmp_count++ + ct := g.value_c_type(arg_type) + g.write('${ct} _drop_owned_value${tmp} = ') + g.gen_expr(arg_id) + g.writeln(';') + expr = '_drop_owned_value${tmp}' + } + g.gen_ownership_drop_value(arg_type, expr, 0) + g.indent-- + g.write('})') + return + } + if node.children_count == 2 && resolved_target_name.len == 0 + && target_name == '__v3_clone_owned_ierror' { + g.gen_ownership_clone_ierror(g.a.child(&node, 1)) + return + } + if g.gen_map_mutation_call_with_loop_copyback_guard(node, fn_name, target_name, + resolved_target_name) + { + return + } + if fn_node.kind == .index && fn_node.value != 'range' { + runtime_start := 1 + arg_count := int(node.children_count) - runtime_start + mut specialized_name := '' + if target_name.contains('.') { + if specialized := g.specialized_generic_method_name_for_call_with_arg_count(id, + target_name, arg_count) + { + specialized_name = specialized + } + } + if specialized_name.len == 0 { + if specialized := g.specialized_generic_plain_fn_name_for_explicit_call(id, fn_node, + target_name) + { + specialized_name = specialized + } + } + if specialized_name.len == 0 { + if specialized := g.specialized_generic_plain_fn_name_for_call(id, node, target_name) { + specialized_name = specialized + } + } + if specialized_name.len > 0 { + g.write(g.direct_call_name(specialized_name)) + g.write('(') + g.gen_call_args(specialized_name, node, runtime_start) + g.write(')') + return + } + } + if node.children_count == 2 && (call_name_is_isnil(fn_name, target_name) + || call_name_is_isnil(resolved_target_name, '')) { + g.write('isnil(') + arg_id := g.a.child(&node, 1) + arg_node := g.a.nodes[int(arg_id)] + if !g.gen_isnil_fn_value_arg(arg_id, arg_node) { + if arg_node.kind == .ident || cgen_type_is_pointer_like(g.tc.resolve_type(arg_id)) { + g.gen_expr(arg_id) + } else { + g.gen_call_args('isnil', node, 1) + } + } + g.write(')') + return + } + if node.children_count >= 2 && (fn_name == 'sync__Channel__close' + || target_name == 'sync__Channel__close' + || target_name == 'C.sync__Channel__close' + || resolved_target_name == 'sync__Channel__close') { + g.write('sync__Channel__close(') + g.gen_expr(g.a.child(&node, 1)) + if node.children_count > 2 { + g.write(', ') + g.gen_expr(g.a.child(&node, 2)) + } else { + g.write(', array_new(sizeof(IError), 0, 0)') + } + g.write(')') + return + } + if resolved_target_name == 'chan.close' && fn_node.kind == .selector { + g.gen_channel_close_call(g.a.child(fn_node, 0), node) + return + } + if fn_node.kind == .selector && g.gen_channel_try_call(node, fn_node) { + return + } + if fn_node.kind == .selector && g.gen_compiler_default_free_call(fn_node, resolved_target_name) { + return + } + if resolved_target_name in ['free', 'builtin.free'] && node.children_count == 2 { + arg_id := g.a.child(&node, 1) + arg_type := g.usable_expr_type(arg_id) + clean_type := types.unwrap_pointer(arg_type) + if _ := array_like_type(clean_type) { + g.write('array__free(') + if arg_type !is types.Pointer { + g.write('&') + } + g.gen_expr(arg_id) + g.write(')') + return + } + if g.pointer_free_needs_aligned_free(arg_type) { + g.write('v3_aligned_free(') + g.gen_expr(arg_id) + g.write(')') + return + } + } + if g.is_json_decode_call(id, target_name) { + if !g.is_json_decode_self_call(target_name, resolved_target_name) + && g.gen_json_decode_call(node) { + return + } + g.gen_default_value_for_type(g.json_decode_result_type_for_call(node) or { + g.call_default_return_type(id) + }) + return + } + if target_name in ['json.encode', 'json__encode'] + || resolved_target_name in ['json.encode', 'json__encode'] + || (g.tc.cur_module == 'json' && target_name == 'encode') { + // Old `json` module only; `json2`/`x.json2` are pure V (see + // is_json_decode_target_name). + if g.gen_json_encode_call(node) { + return + } + } + if g.is_veb_json_result_call(fn_node) { + g.gen_default_value_for_type(g.call_default_return_type(id)) + return + } + if g.is_missing_middleware_use_call(fn_node) { + g.write('0') + return + } + if fn_node.kind == .selector && fn_node.value == 'str' { + base_id := g.a.child(fn_node, 0) + base_node := g.a.node(base_id) + raw_base_type := g.usable_expr_type(base_id) + base_type := if base_node.kind == .ident && raw_base_type !is types.Pointer + && g.local_pointer_alias_source(base_node.value) != none { + types.Type(types.Pointer{ + base_type: raw_base_type + }) + } else { + raw_base_type + } + clean_type := concrete_receiver_type(base_type) + if base_type is types.Pointer && clean_type is types.Struct { + method_name := g.resolve_method_name(clean_type.name, fn_node.value) + if method_name.len > 0 + && !g.method_decl_receiver_wants_ptr(method_name, method_name, method_name) { + mut stack := []string{} + base_expr := g.expr_to_string(base_id) + if pointer_str := g.interface_pointer_str_expr(base_type.base_type, base_expr, + true, mut stack) + { + g.write(pointer_str) + return + } + } + } + if clean_type is types.Enum { + if _ := g.enum_receiver_method_name(clean_type, fn_node.value) { + // Let normal method call generation handle custom enum str methods. + } else { + g.gen_enum_str_call(fn_node, clean_type) + return + } + } + } + if fn_node.kind == .selector && fn_node.value == 'close' { + base_id := g.a.child(fn_node, 0) + base_type := g.tc.resolve_type(base_id) + if cgen_is_channel_close_receiver_type(base_type) { + g.gen_channel_close_call(base_id, node) + return + } + } + if g.gen_flag_enum_zero_call(id, fn_node, node) { + return + } + if g.gen_flag_enum_from_call(id, fn_node, node) { + return + } + if target_name.starts_with('C.') { + if shadow_name := g.main_runtime_shadow_call_c_name(node, fn_node) { + g.write(shadow_name) + g.write('(') + g.gen_call_args('main.${fn_name}', node, 1) + g.write(')') + return + } + g.write(g.direct_call_name_for_call(id, target_name)) + g.write('(') + g.gen_call_args(target_name, node, 1) + g.write(')') + return + } + if g.gen_transformed_method_ident_call(id, node, fn_node) { + return + } + if const_target := g.const_fn_call_target_name(fn_node) { + emitted_target := g.direct_call_name_for_call(id, const_target) + g.write(emitted_target) + g.write('(') + g.gen_call_args(emitted_target, node, 1) + g.write(')') + return + } + if resolved_module_call := g.selector_module_call_name(id, fn_node, node) { + call_args_name := if specialized := g.specialized_generic_plain_fn_name_for_call(id, node, + resolved_module_call) + { + specialized + } else { + resolved_module_call + } + emitted_name := g.direct_call_name_for_call_node(id, node, call_args_name) + g.write(emitted_name) + g.write('(') + g.gen_call_args(call_args_name, node, g.selector_module_call_arg_start(fn_node, node)) + g.write(')') + return + } + if resolved_module_call := g.target_module_call_name(target_name, node) { + call_args_name := if specialized := g.specialized_generic_plain_fn_name_for_call(id, node, + resolved_module_call) + { + specialized + } else { + resolved_module_call + } + emitted_name := g.direct_call_name_for_call_node(id, node, call_args_name) + g.write(emitted_name) + g.write('(') + g.gen_call_args(call_args_name, node, g.target_module_call_arg_start(target_name, node)) + g.write(')') + return + } + callee_is_fn_value := g.fn_value_call_param_types(g.a.child(&node, 0)) != none + // Monomorphization's concrete callee is authoritative. A transformed clone can + // retain the template's resolved-call entry, whose alias-erased specialization + // (`Bar`) must not replace an explicit alias specialization (`BarAlias`). + if fn_node.kind == .ident && fn_name in g.tc.specialized_generic_fns && !callee_is_fn_value { + emitted_name := g.direct_call_name_for_call_node(id, node, fn_name) + g.write(emitted_name) + g.write('(') + g.gen_call_args(fn_name, node, 1) + g.write(')') + return + } + if resolved := g.tc.resolved_call_name(id) { + if arg_start := g.resolved_selector_module_arg_start(resolved, node) { + emitted_resolved := g.direct_call_name_for_call_node(id, node, resolved) + g.write(emitted_resolved) + g.write('(') + g.gen_call_args(resolved, node, arg_start) + g.write(')') + return + } + if !(fn_node.kind == .ident && g.resolved_name_is_generic_plain(resolved) + && g.plain_concrete_fn_name_shadows_generic(fn_node.value) && resolved != fn_node.value) + && !callee_is_fn_value && g.selector_call_can_emit_direct(resolved, node) { + // Comptime expansion can clone one generic call site for fields of different + // concrete types while retaining the first clone's resolved name. Re-infer a + // plain generic from the current clone's arguments before trusting that name. + resolved_for_call := if fn_node.kind == .ident { + g.specialized_generic_plain_fn_name_for_call(id, node, fn_node.value) or { + resolved + } + } else { + resolved + } + emitted_resolved := g.direct_call_name_for_call_node(id, node, resolved_for_call) + g.write(emitted_resolved) + g.write('(') + g.gen_call_args(resolved_for_call, node, 1) + g.write(')') + return + } + } + dispatch_name := if callee_is_fn_value + || (fn_name in ['error', 'error_with_code'] && !g.expr_is_error_call(id)) { + // User modules can declare ordinary functions with the builtin error + // helper names. Only a call resolved to builtin constructs an IError. + '' + } else { + fn_name + } + match dispatch_name { + 'array_new' { + g.write('array_new(') + for i in 1 .. node.children_count { + if i > 1 { + g.write(', ') + } + arg_id := g.a.child(&node, i) + arg_node := g.a.nodes[int(arg_id)] + if i == 1 { + arg_expr := g.expr_to_string(arg_id) + if raw_sizeof := raw_sizeof_arg_value(arg_expr) { + if raw_sizeof_needs_normalization(raw_sizeof) { + g.write('sizeof(${g.sizeof_target(raw_sizeof)})') + } else { + g.write(arg_expr) + } + } else { + g.write(arg_expr) + } + } else if arg_node.kind == .sizeof_expr { + g.write('sizeof(${g.sizeof_target(arg_node.value)})') + } else if raw_sizeof := raw_sizeof_arg_value(arg_node.value) { + if raw_sizeof_needs_normalization(raw_sizeof) { + g.write('sizeof(${g.sizeof_target(raw_sizeof)})') + } else { + g.gen_expr(arg_id) + } + } else { + g.gen_expr(arg_id) + } + } + g.write(')') + return + } + 'new_map' { + if g.is_runtime_new_map_call(node) { + if node.typ.starts_with('map[') { + map_type := g.parse_node_type(&node) + if map_type is types.Map { + g.write_new_map(map_type.key_type, map_type.value_type) + return + } + } + // Alias map types do not expose their underlying map type here. Keep the + // synthesized runtime call instead of resolving it as a user `new_map`. + g.write('new_map(') + for i in 1 .. node.children_count { + if i > 1 { + g.write(', ') + } + g.gen_expr(g.a.child(&node, i)) + } + g.write(')') + return + } + mut call_args_name := fn_name + call_name := if user_name := g.main_runtime_shadow_call_c_name(node, fn_node) { + call_args_name = 'main.${fn_name}' + user_name + } else if resolved_target_name.len > 0 && resolved_target_name != fn_name { + call_args_name = resolved_target_name + g.direct_call_name_for_call_node(id, node, resolved_target_name) + } else { + g.direct_call_name_for_call_node(id, node, fn_name) + } + g.write(call_name) + g.write('(') + g.gen_call_args(call_args_name, node, 1) + g.write(')') + return + } + 'panic' { + g.write('v_panic(') + if node.children_count > 1 { + arg_id := g.a.child(&node, 1) + arg_type := g.tc.resolve_type(arg_id) + clean_arg_type := types.unwrap_pointer(arg_type) + if g.is_ierror_type_name(clean_arg_type.name()) { + g.write('IError__str(') + arg_expr := g.expr_to_string(arg_id) + if arg_expr.starts_with('&') { + g.write(arg_expr[1..]) + } else { + g.write(arg_expr) + } + g.write(')') + } else { + g.gen_expr(arg_id) + } + } + g.write(')') + return + } + 'error' { + if g.is_ierror_type_name(g.expected_expr_type.name()) { + g.gen_ierror_from_error_call(node) + } else if g.cur_fn_ret_is_optional { + ct := g.current_fn_optional_type_name(g.cur_fn_ret) + g.gen_optional_error_from_call(ct, node) + } else { + g.gen_ierror_from_error_call(node) + } + return + } + 'error_with_code' { + if g.is_ierror_type_name(g.expected_expr_type.name()) { + g.gen_ierror_from_error_call(node) + } else if g.cur_fn_ret_is_optional { + ct := g.current_fn_optional_type_name(g.cur_fn_ret) + g.gen_optional_error_from_call(ct, node) + } else { + g.gen_ierror_from_error_call(node) + } + return + } + else { + mut is_method := false + mut is_c_call := false + mut method_name := '' + mut base_id := flat.NodeId(0) + mut emitted_callee_name := '' + mut emitted_param_name := '' + if g.is_explicit_generic_method_call_selector(fn_node, resolved_target_name, + target_name) + { + fn_node = g.a.child_node(fn_node, 0) + } + if fn_node.kind == .selector { + base := g.a.child_node(fn_node, 0) + base_is_local := if base.kind == .ident { + g.selector_base_is_value(base.value) + } else { + false + } + if base.kind == .ident && base.value == 'C' { + g.write(g.direct_call_name('C.${fn_node.value}')) + is_c_call = true + } else if g.is_flag_enum_method(fn_node) { + g.gen_flag_enum_call(node) + return + } else if base.kind == .ident && !base_is_local && g.has_import_alias(base.value) { + mod := g.import_alias_module(base.value) or { '' } + full_name := '${mod}.${fn_node.value}' + if full_name in g.tc.type_aliases || full_name in g.tc.structs + || full_name in g.tc.enum_names || full_name in g.tc.sum_types + || full_name in g.tc.interface_names { + target_type := g.tc.parse_type(full_name) + if target_type is types.Interface && node.children_count > 1 { + g.gen_expr_with_expected_type(g.a.child(&node, 1), target_type) + } else if target_type is types.SumType && node.children_count > 1 { + g.gen_sum_cast_expr(target_type, g.a.child(&node, 1)) + } else { + mut ct := g.tc.c_type(target_type) + if ct.starts_with('fn_ptr:') { + ct = g.resolve_fn_ptr_type(ct) + } + g.write('(${ct})(') + for i in 1 .. node.children_count { + if i > 1 { + g.write(', ') + } + g.gen_expr(g.a.child(&node, i)) + } + g.write(')') + } + return + } + g.write(g.cname(full_name)) + g.write('(') + g.gen_call_args(full_name, node, 1) + g.write(')') + return + } else if base.kind == .ident && !base_is_local + && g.static_method_fn_name(base.value, fn_node.value) != none { + // `Type.method(...)` where the base ident names a (possibly + // imported) type, not a value — e.g. `Animation.load(path)` inside + // the type's own module. Resolve to the module-qualified static fn. + static_fn := g.static_method_fn_name(base.value, fn_node.value) or { '' } + g.write(g.direct_call_name(static_fn)) + g.write('(') + // Lower the arguments through the ordinary call path so a static method + // parameter that needs coercion — option/result wrapping, fn-pointer alias + // callbacks, sum variants, fixed-array decay, variadic/`@[params]` handling — + // is emitted against its expected type, not as the raw expression. + g.gen_call_args(static_fn, node, 1) + g.write(')') + return + } else if base.kind == .selector { + inner := g.a.child_node(base, 0) + inner_is_local := if inner.kind == .ident { + (g.tc.cur_scope.lookup(inner.value) or { types.Type(types.void_) }) !is types.Void + } else { + false + } + if inner.kind == .ident && !inner_is_local { + if mod := g.import_alias_module(inner.value) { + full_name := '${mod}.${base.value}.${fn_node.value}' + g.write(g.cname(full_name)) + g.write('(') + g.gen_call_args(full_name, node, 1) + g.write(')') + return + } + } + { + base_type := g.usable_expr_type(g.a.child(fn_node, 0)) + clean_type := concrete_receiver_type(base_type) + if g.gen_thread_wait_call(fn_node) { + return + } + if g.gen_interface_method_call(node, fn_node, base_type) { + return + } + if g.gen_fn_field_call(node, fn_node, base_type) { + return + } + if arr := array_like_type(clean_type) { + g.gen_array_method_call(node, fn_node, arr) + return + } + if clean_type is types.ArrayFixed && fn_node.value == 'bytestr' { + g.write('u8__vstring_with_len((u8*)') + g.gen_expr(g.a.child(fn_node, 0)) + len_expr := g.fixed_array_len_value(clean_type) + g.write(', ${len_expr})') + return + } + if g.gen_pointer_builtin_method_call(node, fn_node, base_type) { + return + } + if clean_type is types.Map { + if fn_node.value == 'str' { + g.gen_map_str_expr(g.a.child(fn_node, 0), base_type) + return + } else if fn_node.value == 'clone' { + g.write('map__clone(') + g.gen_map_ref_arg(g.a.child(fn_node, 0), base_type) + g.write(')') + return + } else if fn_node.value in ['keys', 'values', 'delete', 'clear', 'free', + 'move', 'reserve'] { + panic('map method `${fn_node.value}` should be lowered by v3 transform') + } + } + if clean_type is types.String { + if fn_node.value == 'to_owned' { + g.write('string__clone(') + g.gen_expr(g.a.child(fn_node, 0)) + g.write(')') + return + } + method_name = 'string.${fn_node.value}' + if method_name in g.tc.fn_param_types { + is_method = true + base_id = g.a.child(fn_node, 0) + g.write_method_c_name(id, node, method_name) + } else { + g.write('string__${fn_node.value}(') + g.gen_expr(g.a.child(fn_node, 0)) + for i in 1 .. node.children_count { + g.write(', ') + g.gen_expr(g.a.child(&node, i)) + } + g.write(')') + return + } + } + if !is_method && (clean_type is types.Primitive + || clean_type is types.ISize || clean_type is types.USize + || clean_type is types.Rune) { + tname := clean_type.name() + prim_method := '${tname}.${fn_node.value}' + if prim_method in g.tc.fn_param_types { + is_method = true + base_id = g.a.child(fn_node, 0) + g.write(g.cname(prim_method)) + } else { + mut prim_found := false + if alias_method := g.find_alias_method(tname, fn_node.value) { + is_method = true + prim_found = true + base_id = g.a.child(fn_node, 0) + g.write(g.cname(alias_method)) + } + if !prim_found { + alt_name := g.find_prim_method(fn_node.value) + if alt_name.len > 0 { + is_method = true + base_id = g.a.child(fn_node, 0) + g.write(alt_name) + } + } + } + } + if !is_method { + mut struct_name := clean_type.name() + if clean_type is types.Struct { + struct_name = clean_type.name + } + method_name = g.resolved_receiver_method_for_call(resolved_target_name, + node, fn_node.value) or { + g.resolve_method_name(struct_name, fn_node.value) + } + if method_name.len == 0 { + for alias, target in g.tc.type_aliases { + if target == struct_name { + alias_method := '${alias}.${fn_node.value}' + if alias_method in g.tc.fn_param_types { + method_name = alias_method + break + } + } + } + } + if method_name.len > 0 && method_name in g.tc.fn_param_types { + is_method = true + base_id = g.a.child(fn_node, 0) + g.write_method_c_name(id, node, method_name) + } else { + str_method := 'string.${fn_node.value}' + if str_method in g.tc.fn_param_types { + is_method = true + method_name = str_method + base_id = g.a.child(fn_node, 0) + g.write(g.cname(str_method)) + } else if embedded_method := g.embedded_method_name_for_type(clean_type, + fn_node.value) + { + is_method = true + method_name = embedded_method + base_id = g.a.child(fn_node, 0) + g.write(g.cname(embedded_method)) + } else if struct_name.len > 0 { + is_method = true + base_id = g.a.child(fn_node, 0) + fallback_method := '${struct_name}.${fn_node.value}' + method_name = fallback_method + g.write_method_c_name(id, node, fallback_method) + } else { + g.gen_expr(g.a.child(&node, 0)) + } + } + } + } + } else if base.kind == .ident + && (base.value in g.tc.structs || base.value in g.tc.enum_names || g.tc.qualify_name(base.value) in g.tc.structs + || g.tc.qualify_name(base.value) in g.tc.enum_names) { + qname := if base.value in g.tc.structs || base.value in g.tc.enum_names { + base.value + } else { + g.tc.qualify_name(base.value) + } + static_name := '${qname}.${fn_node.value}' + g.write(g.cname(static_name)) + g.write('(') + for i in 1 .. node.children_count { + if i > 1 { + g.write(', ') + } + g.gen_expr(g.a.child(&node, i)) + } + g.write(')') + return + } else { + base_type := g.usable_expr_type(g.a.child(fn_node, 0)) + clean_type := concrete_receiver_type(base_type) + if cgen_is_channel_close_receiver_type(clean_type) && fn_node.value == 'close' { + g.gen_channel_close_call(g.a.child(fn_node, 0), node) + return + } + if g.gen_thread_wait_call(fn_node) { + return + } + if g.gen_interface_method_call(node, fn_node, base_type) { + return + } + if g.gen_fn_field_call(node, fn_node, base_type) { + return + } + if arr := array_like_type(clean_type) { + g.gen_array_method_call(node, fn_node, arr) + return + } + if clean_type is types.ArrayFixed && fn_node.value == 'bytestr' { + g.write('u8__vstring_with_len((u8*)') + g.gen_expr(g.a.child(fn_node, 0)) + len_expr := g.fixed_array_len_value(clean_type) + g.write(', ${len_expr})') + return + } + if g.gen_pointer_builtin_method_call(node, fn_node, base_type) { + return + } + if clean_type is types.Map { + if fn_node.value == 'str' { + g.gen_map_str_expr(g.a.child(fn_node, 0), base_type) + return + } else if fn_node.value == 'clone' { + g.write('map__clone(') + g.gen_map_ref_arg(g.a.child(fn_node, 0), base_type) + g.write(')') + return + } else if fn_node.value in ['keys', 'values', 'delete', 'clear', 'free', + 'move', 'reserve'] { + panic('map method `${fn_node.value}` should be lowered by v3 transform') + } + } + if clean_type is types.String { + if fn_node.value == 'to_owned' { + g.write('string__clone(') + g.gen_expr(g.a.child(fn_node, 0)) + g.write(')') + return + } + method_name = 'string.${fn_node.value}' + if method_name in g.tc.fn_param_types { + is_method = true + base_id = g.a.child(fn_node, 0) + g.write_method_c_name(id, node, method_name) + } else { + g.write('string__${fn_node.value}(') + g.gen_expr(g.a.child(fn_node, 0)) + for i in 1 .. node.children_count { + g.write(', ') + g.gen_expr(g.a.child(&node, i)) + } + g.write(')') + return + } + } + if !is_method && (clean_type is types.Void || clean_type is types.Primitive) + && fn_node.value in ['vstring', 'vstring_with_len'] { + g.write('u8__${fn_node.value}((u8*)') + g.gen_expr(g.a.child(fn_node, 0)) + for i in 1 .. node.children_count { + g.write(', ') + g.gen_expr(g.a.child(&node, i)) + } + g.write(')') + return + } + if !is_method && clean_type is types.Struct && clean_type.name == 'IError' { + if fn_node.value == 'msg' { + g.gen_ierror_dynamic_method_expr(g.a.child(fn_node, 0), base_type, + 'msg') + return + } else if fn_node.value == 'code' { + g.gen_ierror_dynamic_method_expr(g.a.child(fn_node, 0), base_type, + 'code') + return + } + } + if !is_method && clean_type is types.Struct && clean_type.name == 'array' + && fn_node.value == 'free' { + g.write('array__free(') + if base_type is types.Pointer { + g.gen_expr(g.a.child(fn_node, 0)) + } else { + g.write('&') + g.gen_expr(g.a.child(fn_node, 0)) + } + g.write(')') + return + } + if !is_method && (clean_type is types.Primitive + || clean_type is types.ISize || clean_type is types.USize + || clean_type is types.Rune) { + tname := clean_type.name() + prim_method := '${tname}.${fn_node.value}' + if prim_method in g.tc.fn_param_types { + is_method = true + base_id = g.a.child(fn_node, 0) + g.write(g.cname(prim_method)) + } else { + mut prim_found := false + if alias_method := g.find_alias_method(tname, fn_node.value) { + is_method = true + prim_found = true + base_id = g.a.child(fn_node, 0) + g.write(g.cname(alias_method)) + } + if !prim_found { + alt_name := g.find_prim_method(fn_node.value) + if alt_name.len > 0 { + is_method = true + base_id = g.a.child(fn_node, 0) + g.write(alt_name) + } + } + } + } + if !is_method { + // Static method on a named type / type alias (e.g. `SimdFloat4.new(...)`, + // where `SimdFloat4` names a type, not a value). The base ident resolves to + // no value type, so handle it before the instance-method fallback. + base_node_s := g.a.child_node(fn_node, 0) + if base_node_s.kind == .ident { + if static_fn := g.static_method_fn_name(base_node_s.value, + fn_node.value) + { + g.write(g.direct_call_name(static_fn)) + g.write('(') + for i in 1 .. node.children_count { + if i > 1 { + g.write(', ') + } + g.gen_expr(g.a.child(&node, i)) + } + g.write(')') + return + } + } + } + if !is_method { + mut struct_name := clean_type.name() + if clean_type is types.Struct { + struct_name = clean_type.name + } + method_name = g.resolved_receiver_method_for_call(resolved_target_name, + node, fn_node.value) or { + g.resolve_method_name(struct_name, fn_node.value) + } + if method_name.len == 0 { + for alias, target in g.tc.type_aliases { + if target == struct_name { + alias_method := '${alias}.${fn_node.value}' + if alias_method in g.tc.fn_param_types { + method_name = alias_method + break + } + } + } + } + if method_name.len > 0 && method_name in g.tc.fn_param_types { + is_method = true + base_id = g.a.child(fn_node, 0) + g.write_method_c_name(id, node, method_name) + } else { + str_method := 'string.${fn_node.value}' + if str_method in g.tc.fn_param_types { + is_method = true + method_name = str_method + base_id = g.a.child(fn_node, 0) + g.write(g.cname(str_method)) + } else if embedded_method := g.embedded_method_name_for_type(clean_type, + fn_node.value) + { + is_method = true + method_name = embedded_method + base_id = g.a.child(fn_node, 0) + g.write(g.cname(embedded_method)) + } else if struct_name.len > 0 { + is_method = true + base_id = g.a.child(fn_node, 0) + fallback_method := '${struct_name}.${fn_node.value}' + method_name = fallback_method + g.write_method_c_name(id, node, fallback_method) + } else { + g.gen_expr(g.a.child(&node, 0)) + } + } + } + // !is_method + } + } else { + fn_id := g.a.child(&node, 0) + fn_ident := g.a.nodes[int(fn_id)] + if fn_ident.kind == .ident { + qname := g.tc.qualify_name(fn_ident.value) + if fn_ident.value in g.tc.type_aliases || qname in g.tc.type_aliases + || fn_ident.value in g.tc.structs || qname in g.tc.structs + || fn_ident.value in g.tc.enum_names || qname in g.tc.enum_names + || fn_ident.value in g.tc.sum_types || qname in g.tc.sum_types + || fn_ident.value in g.tc.interface_names || qname in g.tc.interface_names { + type_name := if fn_ident.value in g.tc.type_aliases + || fn_ident.value in g.tc.structs || fn_ident.value in g.tc.enum_names + || fn_ident.value in g.tc.sum_types + || fn_ident.value in g.tc.interface_names { + fn_ident.value + } else { + qname + } + target_type := g.tc.parse_type(type_name) + if target_type is types.Interface && node.children_count > 1 { + g.gen_expr_with_expected_type(g.a.child(&node, 1), target_type) + } else if target_type is types.SumType && node.children_count > 1 { + g.gen_sum_cast_expr(target_type, g.a.child(&node, 1)) + } else { + mut ct := g.tc.c_type(target_type) + if ct.starts_with('fn_ptr:') { + ct = g.resolve_fn_ptr_type(ct) + } + g.write('(${ct})(') + for i in 1 .. node.children_count { + if i > 1 { + g.write(', ') + } + g.gen_expr(g.a.child(&node, i)) + } + g.write(')') + } + return + } + call_key := g.call_key(id, fn_ident.value) + looked_up := g.tc.cur_scope.lookup(fn_ident.value) or { + types.Type(types.void_) + } + if looked_up !is types.Void && fn_type_from(looked_up) != none { + emitted_callee_name = g.local_decl_cname(fn_ident.value) + g.write(emitted_callee_name) + } else if specialized := g.specialized_generic_plain_fn_name_for_call(id, node, + fn_ident.value) + { + emitted_callee_name = g.direct_call_name_for_call_node(id, node, + specialized) + g.write(emitted_callee_name) + } else if g.resolved_name_is_generic_plain(call_key) + && g.plain_concrete_fn_name_shadows_generic(fn_ident.value) { + emitted_callee_name = g.cname(fn_ident.value) + g.write(emitted_callee_name) + } else if call_key in g.tc.fn_ret_types || call_key in g.tc.fn_param_types { + emitted_callee_name = g.direct_call_name_for_call_node(id, node, call_key) + g.write(emitted_callee_name) + } else if specialized := g.specialized_generic_method_name_for_call_with_arg_count(id, + fn_ident.value, -1) + { + emitted_param_name = specialized + emitted_callee_name = g.cname(specialized) + g.write(emitted_callee_name) + } else { + local_name := if g.tc.cur_module !in ['', 'main', 'builtin'] { + '${g.tc.cur_module}.${fn_ident.value}' + } else { + fn_ident.value + } + emitted_callee_name = if local_name in g.tc.fn_ret_types + || local_name in g.tc.fn_param_types { + g.direct_call_name(local_name) + } else { + g.direct_call_name_for_call(id, fn_ident.value) + } + g.write(emitted_callee_name) + } + } else { + if explicit, receiver_id, explicit_method := g.explicit_generic_method_callee_from_index(fn_ident) { + is_method = true + base_id = receiver_id + method_name = explicit_method + emitted_callee_name = g.cname(explicit) + g.write(emitted_callee_name) + } else { + needs_callee_parens := fn_ident.kind !in [.ident, .selector] + if needs_callee_parens { + g.write('(') + } + g.gen_expr(fn_id) + if needs_callee_parens { + g.write(')') + } + } + } + } + g.write('(') + actual_fn := if is_method { + g.method_call_name_for_call(id, node, method_name) + } else if target_name.contains('.') { + g.call_key(id, target_name) + } else { + g.call_key(id, fn_name) + } + mut param_types := g.param_types_for(actual_fn, fn_name) + if emitted_param_name.len > 0 && (emitted_param_name in g.tc.fn_param_types + || emitted_param_name in g.tc.fn_ret_types) { + param_types = g.param_types_for(emitted_param_name, emitted_param_name) + } + if emitted_callee_name.len > 0 { + emitted_param_types := g.param_types_for(emitted_callee_name, emitted_callee_name) + if emitted_param_types.len > 0 { + param_types = emitted_param_types.clone() + } + } + mut uses_fn_value_param_types := false + if !is_method { + if fn_value_params := g.fn_value_call_param_types(g.a.child(&node, 0)) { + param_types = fn_value_params.clone() + uses_fn_value_param_types = true + } + } + callee_uses_specialized_generic_abi := + g.call_callee_uses_specialized_generic_abi(g.a.child(&node, 0)) + concrete_optional_args := g.call_uses_concrete_optional_params(actual_fn) + || g.call_uses_concrete_optional_params(fn_name) + || g.call_uses_concrete_optional_params(target_name) + || g.call_uses_concrete_optional_params(emitted_callee_name) + || (callee_uses_specialized_generic_abi && params_have_optional_result(param_types)) + if !uses_fn_value_param_types && param_types.len == 0 && !is_method { + // Calling through a function-typed value (a generic `f F` parameter or a + // local fn variable): the callee is not a named function, so recover the + // parameter types from the value's own function type. This lets `mut` / + // pointer arguments receive their `&` exactly as a direct call would. + if ft := fn_type_from(g.tc.resolve_type(g.a.child(&node, 0))) { + param_types = ft.params.clone() + } + } + mut arg_start := 1 + if is_method { + base_type := g.receiver_base_type(base_id) + wants_ptr := (param_types.len > 0 && param_types[0] is types.Pointer) + || g.method_decl_receiver_wants_ptr(actual_fn, method_name, fn_name) + || g.method_receiver_is_mut(actual_fn) + || g.method_receiver_is_mut(g.cname(actual_fn)) + || g.method_receiver_is_mut(method_name) + || g.method_receiver_is_mut(g.cname(method_name)) + || g.mut_receiver_arg_wants_addr(actual_fn, base_id) + receiver_type_name := g.type_lookup_name(base_type) + method_short := method_name.all_after_last('.').all_after_last('__') + base_method_name := '${receiver_type_name}.${method_short}' + base_declares_method := base_method_name in g.tc.fn_param_types + || base_method_name in g.tc.fn_ret_types + || (emitted_callee_name.len > 0 + && g.cname(base_method_name) == emitted_callee_name) + atomic_receiver_wants_ptr := method_name.starts_with('stdatomic.AtomicVal_') + || method_name.starts_with('stdatomic.AtomicVal.') + || method_name.starts_with('AtomicVal_') + || method_name.starts_with('AtomicVal.') + || (receiver_type_name.contains('AtomicVal') + && method_short in ['load', 'store', 'add', 'sub', 'swap', 'compare_and_swap']) + receiver_wants_ptr := wants_ptr || atomic_receiver_wants_ptr + || g.method_receiver_is_mut(method_name) + || g.fn_first_param_is_mut_receiver(base_method_name) + receiver_wants_shared := + g.fn_param_is_shared_for_call(0, actual_fn, emitted_callee_name, method_name, fn_name) + || g.fn_param_is_shared_for_call(0, base_method_name, g.cname(base_method_name), '', '') + if receiver_wants_shared && (g.gen_shared_local_receiver_arg(base_id) + || g.gen_shared_storage_expr(base_id)) { + arg_start = 1 + } else if param_types.len > 0 + && g.gen_embedded_interface_receiver(base_id, base_type, param_types[0], receiver_wants_ptr) { + arg_start = 1 + } else if !base_declares_method + && g.gen_embedded_named_method_receiver(base_id, base_type, method_short, method_name, emitted_callee_name, receiver_wants_ptr) { + arg_start = 1 + } else if !base_declares_method && param_types.len > 0 + && g.gen_embedded_method_receiver(base_id, base_type, param_types[0], receiver_wants_ptr) { + arg_start = 1 + } else if g.gen_current_mut_param_method_receiver(base_id, receiver_wants_ptr) { + arg_start = 1 + } else { + base_node := g.a.nodes[int(base_id)] + if receiver_wants_ptr && param_types.len > 0 + && g.gen_deref_method_receiver(base_id, param_types[0]) { + // handled + } else if receiver_wants_ptr && base_node.kind == .prefix + && base_node.op == .mul && base_node.children_count > 0 { + g.gen_expr(g.a.child(&base_node, 0)) + } else { + mut is_ptr_base := base_type is types.Pointer + || g.usable_expr_type(base_id) is types.Pointer + || g.receiver_ident_storage_is_pointer(base_id) + if base_node.kind == .ident + && g.local_ident_is_shared_wrapper(base_node.value) + && !receiver_wants_shared { + is_ptr_base = false + } + if receiver_wants_ptr && base_node.kind == .ident + && base_type !is types.Pointer + && !g.receiver_ident_storage_is_pointer(base_id) { + is_ptr_base = false + } + // A `string.*` method always takes its receiver by value. If the + // receiver mis-resolves to a char pointer (e.g. a `const x = + // os.getenv(..)` whose type was inferred as `&char`), the actual + // storage is still a `string`, so do not dereference it. + if is_ptr_base && method_name.starts_with('string.') + && base_type is types.Pointer && base_type.base_type is types.Char { + is_ptr_base = false + } + materialize_receiver := receiver_wants_ptr && !is_ptr_base + && base_node.kind == .call + if materialize_receiver { + receiver_ct := g.tc.c_type(types.unwrap_pointer(base_type)) + g.write('&((${receiver_ct}[]){') + } else if receiver_wants_ptr && !is_ptr_base { + g.write('&') + } else if !receiver_wants_ptr && is_ptr_base { + g.write('*') + } + g.gen_expr(base_id) + if materialize_receiver { + g.write('})[0]') + } + } + arg_start = 1 + } + } else if node.children_count == param_types.len + 2 { + resolved_call := g.tc.resolved_call_name(id) or { '' } + if resolved_call.contains('.') { + resolved_base := resolved_call.all_before_last('.') + first_arg := g.a.child_node(&node, 1) + if first_arg.kind == .ident && (first_arg.value == resolved_base + || resolved_base.ends_with('.${first_arg.value}')) { + arg_start = 2 + } + } + } + num_call_args := node.children_count - arg_start + is_c_variadic_fn := is_c_call && (g.tc.c_variadic_fns[actual_fn] or { false }) + is_variadic_fn := !is_method && !is_c_variadic_fn && ((g.tc.fn_variadic[actual_fn] or { + false + }) || g.fn_decl_is_variadic(actual_fn, fn_name)) + is_untyped_variadic_fn := is_variadic_fn && param_types.len > 0 + && variadic_array_is_native(param_types[param_types.len - 1]) + is_native_variadic_fn := is_c_variadic_fn || is_untyped_variadic_fn + variadic_idx := if is_variadic_fn && !is_untyped_variadic_fn && param_types.len > 0 + && param_types[param_types.len - 1] is types.Array { + param_types.len - 1 + } else { + -1 + } + typed_param_count := if is_native_variadic_fn && param_types.len > 0 + && param_types[param_types.len - 1] is types.Array { + param_types.len - 1 + } else { + param_types.len + } + callee_has_implicit_ctx := g.call_has_implicit_veb_ctx([actual_fn, emitted_param_name, + emitted_callee_name, fn_name, target_name, method_name]) + // A veb handler whose hidden `Context` parameter (param index 1, right + // after the receiver) was omitted by the caller forwards the enclosing + // handler's context in that slot so the remaining explicit arguments + // still line up with their parameters. + expected_non_ctx := if is_method { param_types.len - 1 } else { param_types.len } + may_forward_ctx := callee_has_implicit_ctx && param_types.len > 1 + && num_call_args < expected_non_ctx && g.is_implicit_veb_ctx_param(param_types[1]) + mut current_ctx_name := if may_forward_ctx { g.cur_veb_ctx_name() or { '' } } else { '' } + forward_ctx := may_forward_ctx && current_ctx_name.len > 0 + if forward_ctx && is_method { + g.write(', ${g.cname(current_ctx_name)}') + } + mut emitted_arg_count := 0 + for i in arg_start .. node.children_count { + mut arg_idx := if is_method { i } else { i - 1 } + if forward_ctx && (is_method || i - arg_start >= 1) { + arg_idx++ + } + arg_id := g.a.child(&node, i) + if int(arg_id) < 0 { + continue + } + arg_node := g.a.nodes[int(arg_id)] + if (param_types.len > 0 || uses_fn_value_param_types) && !is_native_variadic_fn + && variadic_idx < 0 && arg_idx >= typed_param_count { + continue + } + if is_method || emitted_arg_count > 0 { + g.write(', ') + } + emitted_arg_count++ + if arg_node.kind == .field_init && variadic_idx >= 0 && arg_idx == variadic_idx { + variadic_type := param_types[variadic_idx] + if variadic_type is types.Array { + if variadic_type.elem_type is types.Struct { + c_elem := g.tc.c_type(variadic_type.elem_type) + g.write('new_array_from_c_array(1, 1, sizeof(${c_elem}), (${c_elem}[]){') + g.gen_params_struct_arg(variadic_type.elem_type, node, i) + g.write('})') + break + } + } + } + if arg_node.kind == .field_init { + // `@[params]` struct argument: trailing `key: value` args form a struct literal + ptyp := if arg_idx < typed_param_count { + param_types[arg_idx] + } else { + types.Type(types.void_) + } + g.gen_params_struct_arg(ptyp, node, i) + break + } + if arg_node.kind == .sizeof_expr { + g.write('sizeof(${g.sizeof_target(arg_node.value)})') + continue + } + if g.gen_array_equality_literal_arg([emitted_callee_name, actual_fn, fn_name], + arg_idx, arg_id, arg_node) + { + continue + } + if !is_c_call && arg_idx < typed_param_count { + arg_param_is_shared := g.fn_param_is_shared_for_call(arg_idx, actual_fn, + target_name, emitted_callee_name, fn_name) + if arg_param_is_shared && (g.gen_shared_local_receiver_arg(arg_id) + || g.gen_shared_storage_expr(arg_id)) { + continue + } + } + if arg_idx == 0 && emitted_callee_name in ['array_push', 'array__push'] { + if target := g.shared_array_payload_lvalue(arg_id) { + g.write('&${target}') + continue + } + arg_expr := g.expr_to_string(arg_id).trim_space() + if arg_expr.ends_with('->val') { + if arg_expr.starts_with('&') { + g.write(arg_expr) + continue + } + g.write('&${arg_expr}') + continue + } + } + if arg_idx == 0 && emitted_callee_name in ['array_get', 'array__get'] { + arg_expr := g.expr_to_string(arg_id).trim_space() + if storage_expr := shared_storage_from_payload_value_expr(arg_expr) { + g.write('${storage_expr}->val') + continue + } + } + if arg_idx == 1 && emitted_callee_name in ['array_push', 'array__push'] + && g.gen_shared_array_push_arg(node.value, arg_id) { + continue + } + if !is_c_call && arg_idx < typed_param_count + && param_types[arg_idx] !is types.Pointer + && g.gen_addressed_byvalue_arg(arg_node, param_types[arg_idx]) { + continue + } + if !is_c_call && arg_idx < param_types.len + && g.gen_fixed_array_pointer_lvalue_arg(arg_id, param_types[arg_idx]) { + continue + } + if g.gen_new_array_fixed_data_arg(node, arg_start, arg_idx, arg_id, [ + emitted_callee_name, + actual_fn, + ]) + { + continue + } + if fixed := array_fixed_type(g.tc.resolve_type(arg_id)) { + g.gen_fixed_array_data_arg(arg_id, fixed) + continue + } + if !is_c_call && arg_idx < typed_param_count { + if fixed := array_fixed_type(param_types[arg_idx]) { + g.gen_fixed_array_data_arg(arg_id, fixed) + continue + } + } + if !is_c_call && arg_idx < typed_param_count + && g.gen_pointer_arg_from_array_literal(arg_node, param_types[arg_idx]) { + continue + } + cb_param := if arg_idx >= 0 && arg_idx < typed_param_count { + param_types[arg_idx] + } else { + types.Type(types.void_) + } + is_storage_pointer_arg := g.call_arg_is_storage_pointer_arg(arg_idx, [ + actual_fn, + fn_name, + emitted_callee_name, + ]) + if g.gen_special_c_callback_arg(target_name, arg_idx, arg_id, cb_param) { + continue + } + if is_c_call + && g.gen_c_va_list_macro_arg_direct(arg_idx, arg_id, target_name, actual_fn, fn_name, emitted_callee_name) { + continue + } + if arg_idx < typed_param_count + && g.gen_callback_fn_value_for_expected_type(arg_id, param_types[arg_idx]) { + continue + } + if is_storage_pointer_arg + && g.gen_generated_storage_pointer_arg(arg_idx, arg_id, arg_node) { + continue + } + if !is_storage_pointer_arg && type_is_void_pointer(cb_param) + && arg_node.kind == .ident && g.node_is_fn_value_for_voidptr(arg_id, arg_node) { + g.gen_expr(arg_id) + continue + } + if !is_storage_pointer_arg && type_is_void_pointer(cb_param) + && g.gen_voidptr_fn_value_arg(arg_id, arg_node) { + continue + } + if variadic_idx >= 0 && arg_idx == variadic_idx { + variadic_type := param_types[variadic_idx] + if variadic_type is types.Array { + if spread_id := g.spread_arg_child(arg_node) { + g.gen_expr_with_expected_type(spread_id, variadic_type) + continue + } + if num_call_args > param_types.len { + g.gen_variadic_array_args(node, i, variadic_type.elem_type) + break + } + arg_type := g.tc.resolve_type(arg_id) + if arg_type !is types.Array || arg_node.kind == .struct_init { + c_elem := g.tc.c_type(variadic_type.elem_type) + g.write('new_array_from_c_array(1, 1, sizeof(${c_elem}), (${c_elem}[]){') + if variadic_elem_is_voidptr(variadic_type.elem_type) { + g.gen_voidptr_variadic_arg(arg_id) + } else { + g.gen_expr_with_expected_type(arg_id, variadic_type.elem_type) + } + g.write('})') + continue + } + } + } + arg_type := if arg_idx >= 0 && arg_idx < typed_param_count { + g.usable_expr_type(arg_id) + } else { + types.Type(types.void_) + } + mut needs_addr := false + if !is_c_call && arg_idx < typed_param_count + && param_types[arg_idx] is types.Pointer && !(arg_node.kind == .prefix + && arg_node.op == .amp) && !g.arg_is_null_pointer_literal(arg_id, arg_node) { + arg_is_pointer_param := arg_node.kind == .ident && c_type_is_pointer_like(g.current_param_type(arg_node.value) or { + types.Type(types.void_) + }) + arg_is_pointer_global := arg_node.kind == .ident && c_type_is_pointer_like(g.global_type_for_ident(arg_node.value) or { + types.Type(types.void_) + }) + value_local_mut_receiver := arg_idx == 0 && (g.method_receiver_is_mut(fn_name) + || g.method_receiver_is_mut(g.direct_call_name(fn_name))) + && arg_node.kind == .ident && !g.local_storage_is_pointer(arg_node.value) + && !arg_is_pointer_param && !arg_is_pointer_global + && !c_type_is_pointer_like(arg_type) + if !g.fn_value_arg_passes_direct_to_voidptr(arg_id, arg_node, arg_type, param_types[arg_idx]) + && (!c_type_is_pointer_like(arg_type) || value_local_mut_receiver) + && !g.c_string_pointer_arg(arg_node, param_types[arg_idx]) { + needs_addr = !(arg_node.kind == .ident + && (g.local_storage_is_pointer(arg_node.value) || arg_is_pointer_global)) + } + if needs_addr && g.voidptr_method_value_arg(arg_id, param_types[arg_idx]) { + needs_addr = false + } + } + if arg_idx < typed_param_count + && g.voidptr_value_arg_needs_address(arg_id, arg_node, arg_type, param_types[arg_idx], is_c_call) { + needs_addr = true + } + if !is_c_call && !needs_addr && arg_idx == 0 + && (g.mut_receiver_arg_wants_addr(actual_fn, arg_id) + || g.mut_receiver_arg_wants_addr(emitted_callee_name, arg_id) + || g.mut_receiver_arg_wants_addr(fn_name, arg_id)) { + needs_addr = true + } + if !is_c_call && arg_idx < typed_param_count { + pt := param_types[arg_idx] + if pt is types.Enum { + g.expected_enum = pt.name + } + } + if !is_c_call && arg_idx < typed_param_count + && g.gen_mut_sum_lvalue_arg(arg_id, param_types[arg_idx]) { + g.expected_enum = '' + continue + } + if !is_c_call && arg_idx < typed_param_count + && g.gen_mut_pointer_slot_arg(arg_id, arg_node, param_types[arg_idx]) { + g.expected_enum = '' + continue + } + if !is_c_call && arg_idx < typed_param_count { + if child_id := g.addressed_rvalue_arg(arg_node) { + pt := param_types[arg_idx] + if g.gen_addressed_rvalue_arg(child_id, pt) { + g.expected_enum = '' + continue + } + } + } + is_rvalue := arg_node.kind == .call + || (arg_node.kind == .index && arg_node.value == 'range') + || g.arg_is_const_ident(arg_node) + if needs_addr && g.arg_is_const_ident(arg_node) { + value_type := g.addressed_const_arg_value_type(arg_id, param_types[arg_idx]) + ct := g.tc.c_type(value_type) + g.write('(${ct}[]){') + g.gen_expr_with_expected_type(arg_id, value_type) + g.write('}') + } else if needs_addr && is_rvalue { + pt := param_types[arg_idx] + ct := g.tc.c_type(types.unwrap_pointer(pt)) + if g.c_typedef_nil_call(arg_id) { + g.write('NULL') + } else { + g.write('&((${ct}[]){') + g.gen_expr_with_expected_type(arg_id, types.unwrap_pointer(pt)) + g.write('})[0]') + } + } else if needs_addr && g.gen_mut_sum_lvalue_arg(arg_id, param_types[arg_idx]) { + // handled + } else { + if needs_addr && arg_node.kind == .prefix && arg_node.op == .mul + && arg_node.children_count > 0 { + g.gen_expr(g.a.child(&arg_node, 0)) + g.expected_enum = '' + continue + } + if needs_addr { + g.write('&') + } + emitted_variant := !needs_addr && !is_c_call && arg_idx < typed_param_count + && g.gen_sum_variant_arg(arg_id, param_types[arg_idx]) + if !emitted_variant { + if !is_c_call && arg_idx < typed_param_count + && g.gen_optional_arg_with_abi(arg_id, param_types[arg_idx], concrete_optional_args) { + // handled + } else if !is_c_call && arg_idx < typed_param_count + && g.gen_pointer_backed_param_arg(arg_id, param_types[arg_idx]) { + // handled + } else if !is_c_call && arg_idx < typed_param_count + && g.gen_embedded_interface_receiver(arg_id, arg_type, param_types[arg_idx], param_types[arg_idx] is types.Pointer) { + // handled + } else if !is_c_call && arg_idx < typed_param_count + && g.gen_embedded_method_receiver(arg_id, g.receiver_base_type(arg_id), param_types[arg_idx], param_types[arg_idx] is types.Pointer) { + // handled + } else if !is_c_call && arg_idx < typed_param_count { + g.known_expr_type_id = int(arg_id) + g.known_expr_type = arg_type + g.gen_expr_with_expected_type(arg_id, param_types[arg_idx]) + } else { + g.gen_expr(arg_id) + } + } + } + g.expected_enum = '' + // A no-arg delegation leaves the forwarded ctx as the final argument; + // emit it here, right after the receiver, for the lowered free call. + if forward_ctx && !is_method && i - arg_start == 0 { + g.write(', ${g.cname(current_ctx_name)}') + } + } + // Count the forwarded ctx (if any) as already supplied. + actual_args := emitted_arg_count + (if forward_ctx { 1 } else { 0 }) + expected_args := if is_method { + param_types.len - 1 + } else { + param_types.len + } + if !is_c_call && expected_args > 0 && actual_args < expected_args { + mut emitted_defaults := 0 + for pi in actual_args .. expected_args { + pidx := if is_method { pi + 1 } else { pi } + pt := param_types[pidx] + if g.type_contains_generic_placeholder(pt) { + continue + } + if is_method || actual_args > 0 || emitted_defaults > 0 { + g.write(', ') + } + // The implicit veb `Context` parameter is supplied from the + // enclosing handler's context, not a zero/default value. + implicit_ctx := callee_has_implicit_ctx && g.is_implicit_veb_ctx_param(pt) + if implicit_ctx && current_ctx_name.len == 0 { + current_ctx_name = g.cur_veb_ctx_name() or { '' } + } + if implicit_ctx && current_ctx_name.len > 0 { + g.write(g.cname(current_ctx_name)) + } else { + g.gen_default_value_for_type(pt) + } + emitted_defaults++ + } + } + g.write(')') + } + } +} + +fn (g &FlatGen) expr_is_non_string_scalar_value(id flat.NodeId) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(id)] + if node.kind in [.paren, .expr_stmt] && node.children_count > 0 { + return g.expr_is_non_string_scalar_value(g.a.child(&node, 0)) + } + if node.kind in [.string_literal, .string_interp] { + return false + } + usable := cgen_unalias_type(g.usable_expr_type(id)) + if usable is types.String || usable.name().all_after_last('.') == 'string' { + return false + } + if node.kind in [.char_literal, .int_literal, .float_literal, .bool_literal] { + return true + } + if node.kind == .ident { + if c_type := g.local_storage_c_type(node.value) { + return c_type in ['bool', 'char', 'i8', 'i16', 'int', 'i64', 'isize', 'u8', 'u16', + 'u32', 'u64', 'usize', 'f32', 'f64', 'rune'] + } + if raw_type := g.local_storage_raw_type(node.value) { + clean := cgen_unalias_type(g.tc.parse_type(raw_type)) + if clean is types.String || clean.name() == 'string' { + return false + } + if clean is types.Primitive || clean is types.Char || clean is types.Rune + || clean is types.ISize || clean is types.USize || clean is types.Enum { + return true + } + } + } + const_name := g.const_ref_name_from_node(node) + if const_name.len > 0 { + if const_id := g.const_vals[const_name] { + if const_id != id { + return g.expr_is_non_string_scalar_value(const_id) + } + } + } + clean := cgen_unalias_type(g.usable_expr_type(id)) + if clean is types.String || clean.name() == 'string' { + return false + } + return clean is types.Primitive || clean is types.Char || clean is types.Rune + || clean is types.ISize || clean is types.USize || clean is types.Enum +} + +fn ownership_synthetic_drop_name(name string) bool { + return name == 'drop_owned' || name.starts_with('drop_owned_T_') + || name == 'drop_owned_v3_interface' || name.starts_with('drop_owned_v3_interface_T_') +} + +fn (g &FlatGen) ownership_drop_intrinsic_name(name string) bool { + if name in ['builtin.drop_owned', 'builtin__drop_owned'] + || name.starts_with('builtin.drop_owned_T_') || name.starts_with('builtin__drop_owned_T_') + || name in ['builtin.drop_owned_v3_interface', 'builtin__drop_owned_v3_interface'] + || name.starts_with('builtin.drop_owned_v3_interface_T_') + || name.starts_with('builtin__drop_owned_v3_interface_T_') { + return true + } + if !ownership_synthetic_drop_name(name) { + return false + } + if module_name := g.tc.fn_type_modules[name] { + return module_name == 'builtin' + } + // A generic builtin drop can be specialized while transforming another + // module (for example `sync.arc.Arc[[]string].drop`). Its concrete name is + // synthesized after signature collection, so only the generic declaration + // is present in `fn_type_modules`. + base_name := if name.starts_with('drop_owned_v3_interface_T_') { + 'drop_owned_v3_interface' + } else if name.starts_with('drop_owned_T_') { + 'drop_owned' + } else { + name + } + for candidate in [base_name, 'builtin.${base_name}', 'builtin__${base_name}'] { + if module_name := g.tc.fn_type_modules[candidate] { + if module_name == 'builtin' { + return true + } + } + } + // Unqualified `drop_owned_T_*` names are reserved for these generated + // specializations. User functions resolve with their module prefix. + return name.starts_with('drop_owned_T_') || name.starts_with('drop_owned_v3_interface_T_') +} + +fn (mut g FlatGen) gen_fixed_array_get_call(node flat.Node, fn_name string, target_name string) bool { + if node.children_count != 3 + || (fn_name !in ['array_get', 'array__get'] && target_name !in ['array_get', 'array__get']) { + return false + } + base_id := g.a.child(&node, 1) + if _ := g.fixed_array_type_for_expr(base_id) { + // `array_get` returns a pointer to the element. A fixed array already has + // addressable C storage, so take the address of the indexed slot directly. + is_ptr := g.usable_expr_type(base_id) is types.Pointer + g.write('(void*)(&') + if is_ptr { + g.write('(*') + g.gen_expr(base_id) + g.write(')') + } else { + g.gen_expr(base_id) + } + g.write('[') + g.gen_expr(g.a.child(&node, 2)) + g.write('])') + return true + } + return false +} + +fn (mut g FlatGen) fixed_array_type_for_expr(id flat.NodeId) ?types.ArrayFixed { + typ := g.usable_expr_type(id) + if fixed := array_fixed_type(types.unwrap_pointer(typ)) { + return fixed + } + if int(id) < 0 || int(id) >= g.a.nodes.len { + return none + } + node := g.a.nodes[int(id)] + if node.kind != .ident || node.value.len == 0 { + return none + } + if g.current_param_type(node.value) != none || g.cur_scope_has_local_name(node.value) { + return none + } + mut candidates := []string{cap: 4} + if g.tc.cur_module.len > 0 && g.tc.cur_module != 'main' && g.tc.cur_module != 'builtin' { + candidates << '${g.tc.cur_module}.${node.value}' + } + candidates << node.value + qname := g.tc.qualify_name(node.value) + if qname != node.value { + candidates << qname + } + cname := g.cname(node.value) + if cname != node.value { + candidates << cname + } + for candidate in candidates { + if global_type := g.global_types[candidate] { + if fixed := array_fixed_type(types.unwrap_pointer(global_type)) { + return fixed + } + } + if const_type := g.tc.const_types[candidate] { + if fixed := array_fixed_type(types.unwrap_pointer(const_type)) { + return fixed + } + } + } + return none +} + +fn (g &FlatGen) voidptr_method_value_arg(arg_id flat.NodeId, expected types.Type) bool { + expected_ptr := expected as types.Pointer + if expected_ptr.base_type !is types.Void { + return false + } + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(arg_id)] + if node.kind != .selector || node.children_count == 0 { + return false + } + base_id := g.a.child(&node, 0) + base_type := g.usable_expr_type(base_id) + clean := types.unwrap_pointer(base_type) + mut receiver_name := '' + if clean is types.Struct { + receiver_name = clean.name + } else if clean is types.Interface { + receiver_name = clean.name + } else { + return false + } + if _ := g.field_type(base_type, node.value) { + return false + } + if clean is types.Interface { + method_key := '${receiver_name}.${node.value}' + if method_key in g.fn_decl_param_types || method_key in g.tc.fn_param_types { + return true + } + if _ := g.interface_method_param_types(method_key) { + return true + } + return false + } + return g.resolve_method_name(receiver_name, node.value).len > 0 +} + +// receiver_base_type supports receiver base type handling for FlatGen. +fn (g &FlatGen) receiver_base_type(base_id flat.NodeId) types.Type { + if int(base_id) < 0 { + return types.Type(types.void_) + } + base := g.a.nodes[int(base_id)] + if base.kind == .ident { + if typ := g.current_param_type(base.value) { + return typ + } + if typ := g.current_param_map_type(base.value) { + return typ + } + if typ := g.tc.expr_type(base_id) { + if typ !is types.Unknown && typ !is types.Void + && !g.type_contains_generic_placeholder(typ) { + // C generation does not walk the checker scope for each function. Use + // the semantic type recorded for this receiver node before consulting + // a possibly unrelated same-named local in the checker's final scope. + return typ + } + } + if typ := g.tc.cur_scope.lookup(base.value) { + return typ + } + if typ := g.global_type_for_ident(base.value) { + return typ + } + } + return g.tc.resolve_type(base_id) +} + +fn (g &FlatGen) global_type_for_ident(name string) ?types.Type { + qname := qualify_name_in_module(g.tc.cur_module, name) + if qname != name { + if typ := g.global_types[qname] { + return typ + } + } + if typ := g.global_types[name] { + return typ + } + if mod := g.global_modules[name] { + module_qname := qualify_name_in_module(mod, name) + if typ := g.global_types[module_qname] { + return typ + } + } + return none +} + +fn (g &FlatGen) const_ident_type(name string) ?types.Type { + if typ := g.tc.const_types[name] { + return typ + } + qname := qualify_name_in_module(g.tc.cur_module, name) + if qname != name { + if typ := g.tc.const_types[qname] { + return typ + } + } + if key := g.tc.const_suffixes[name] { + if key.len > 0 { + if typ := g.tc.const_types[key] { + return typ + } + } + } + return none +} + +fn (g &FlatGen) const_type_for_arg_node(node flat.Node) ?types.Type { + if node.kind == .ident { + return g.const_ident_type(node.value) + } + if node.kind == .selector && node.children_count > 0 { + base := g.a.child_node(&node, 0) + if base.kind != .ident { + return none + } + mod := g.import_alias_module(base.value) or { base.value } + for key in ['${mod}.${node.value}', '${base.value}.${node.value}'] { + if typ := g.tc.const_types[key] { + return typ + } + } + } + return none +} + +fn (mut g FlatGen) gen_current_mut_param_method_receiver(base_id flat.NodeId, wants_ptr bool) bool { + if !wants_ptr { + return false + } + if int(base_id) < 0 || int(base_id) >= g.a.nodes.len { + return false + } + base := g.a.nodes[int(base_id)] + if base.kind != .ident || !g.current_param_is_mut(base.value) { + return false + } + if g.current_param_is_mut_pointer(base.value) { + g.gen_expr(base_id) + } else { + g.write(g.cname(base.value)) + } + return true +} + +fn (mut g FlatGen) gen_deref_method_receiver(receiver_id flat.NodeId, expected types.Type) bool { + if int(receiver_id) < 0 || int(receiver_id) >= g.a.nodes.len { + return false + } + receiver := g.a.nodes[int(receiver_id)] + if receiver.kind != .prefix || receiver.op != .mul || receiver.children_count == 0 { + return false + } + child_id := g.a.child(&receiver, 0) + child_type := g.usable_expr_type(child_id) + if cgen_type_pointer_depth(child_type) > cgen_type_pointer_depth(expected) { + g.gen_expr(receiver_id) + } else { + g.gen_expr(child_id) + } + return true +} + +fn (mut g FlatGen) gen_pointer_backed_param_arg(arg_id flat.NodeId, expected types.Type) bool { + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return false + } + arg := g.a.nodes[int(arg_id)] + if arg.kind != .ident { + return false + } + param_type := g.current_param_type(arg.value) or { return false } + if expected is types.Pointer { + expected_ptr := expected as types.Pointer + if param_type is types.Pointer { + if !g.type_names_match(param_type.base_type, expected_ptr.base_type) { + return false + } + g.write(g.local_decl_cname(arg.value)) + return true + } + } + return false +} + +fn (mut g FlatGen) method_decl_receiver_wants_ptr(actual_fn string, method_name string, fallback string) bool { + for name in [actual_fn, g.cname(actual_fn), method_name, g.cname(method_name), fallback, + g.cname(fallback)] { + params := g.param_types_from_decl(name, fallback) + if params.len > 0 { + return params[0] is types.Pointer + } + } + return false +} + +fn (g &FlatGen) method_receiver_is_mut(method_name string) bool { + return g.fn_decl_mut_receivers[method_name] or { false } +} + +fn (mut g FlatGen) mut_receiver_arg_wants_addr(fn_name string, arg_id flat.NodeId) bool { + if int(arg_id) < 0 || int(arg_id) >= g.a.nodes.len { + return false + } + arg_node := g.a.nodes[int(arg_id)] + if arg_node.kind != .ident { + return false + } + if g.local_storage_is_shared(arg_node.value) { + return !g.fn_param_is_shared_for_call(0, fn_name, '', '', '') + && g.fn_first_param_is_mut_receiver(fn_name) + } + if g.local_storage_is_pointer(arg_node.value) { + return false + } + arg_is_pointer_param := (g.current_param_type(arg_node.value) or { types.Type(types.void_) }) is types.Pointer + if arg_is_pointer_param { + return false + } + arg_type := g.usable_expr_type(arg_id) + if arg_type is types.Pointer { + return false + } + return g.fn_first_param_is_mut_receiver(fn_name) +} + +// fn_first_param_is_mut_receiver reports whether a call target's first +// parameter is a mut receiver/pointer. Pure per fn name given the fixed +// signature tables, and asked once per call site — memoized. +fn (mut g FlatGen) fn_first_param_is_mut_receiver(fn_name string) bool { + if !isnil(g.mut_recv_facts) { + mut cache := g.mut_recv_facts + cached := cache.get(fn_name) + if cached != 0 { + return cached > 0 + } + } + result := g.fn_first_param_is_mut_receiver_uncached(fn_name) + if !isnil(g.mut_recv_facts) { + mut cache := g.mut_recv_facts + cache.put(fn_name, if result { i8(1) } else { i8(-1) }) + } + return result +} + +fn (mut g FlatGen) fn_first_param_is_mut_receiver_uncached(fn_name string) bool { + mut names := []string{} + if fn_name.contains('.') || fn_name.contains('__') { + names << fn_name + names << g.cname(fn_name) + } + direct_name := g.direct_call_name(fn_name) + if direct_name != fn_name && direct_name != g.cname(fn_name) { + names << direct_name + names << g.cname(direct_name) + } + for name in names { + if params := g.fn_decl_param_types[name] { + if params.len > 0 { + return params[0] is types.Pointer + } + } + } + for name in names { + if g.method_receiver_is_mut(name) { + return true + } + } + return false +} + +fn (g &FlatGen) c_style_mut_receiver_arg_wants_addr(fn_name string, arg_id flat.NodeId) bool { + if !fn_name.contains('__') { + return false + } + arg_node := g.a.nodes[int(arg_id)] + if arg_node.kind != .ident || g.local_storage_is_pointer(arg_node.value) { + return false + } + arg_is_pointer_param := (g.current_param_type(arg_node.value) or { types.Type(types.void_) }) is types.Pointer + if arg_is_pointer_param { + return false + } + arg_type := g.usable_expr_type(arg_id) + if arg_type is types.Pointer { + return false + } + receiver_ct := g.tc.c_type(types.unwrap_pointer(arg_type)) + short_receiver := g.flattened_generic_struct_c_type_short_name(receiver_ct) + if short_receiver.len == 0 || !fn_name.contains(short_receiver) { + return false + } + method := fn_name.all_after_last('__') + if method.len == 0 { + return false + } + for name, is_mut in g.fn_decl_mut_receivers { + if !is_mut { + continue + } + if !name.contains(short_receiver) { + continue + } + if name == method || name.ends_with('.${method}') || name.ends_with('__${method}') { + return true + } + } + return false +} + +fn concrete_receiver_type(base_type types.Type) types.Type { + clean_type := types.unwrap_pointer(base_type) + if clean_type is types.Alias { + return clean_type.base_type + } + return clean_type +} + +fn cgen_is_channel_close_receiver_type(base_type types.Type) bool { + clean_type := concrete_receiver_type(base_type) + if clean_type is types.Channel { + return true + } + mut name := clean_type.name().trim_space() + for name.starts_with('&') { + name = name[1..].trim_space() + } + return name == 'chan' || name.starts_with('chan ') +} + +fn (g &FlatGen) receiver_storage_type(base_id flat.NodeId) ?types.Type { + if int(base_id) < 0 || int(base_id) >= g.a.nodes.len { + return none + } + node := g.a.nodes[int(base_id)] + if node.kind == .selector && node.children_count > 0 { + parent_type := g.tc.resolve_type(g.a.child(&node, 0)) + return g.field_type(parent_type, node.value) + } + return none +} + +fn (g &FlatGen) receiver_ident_storage_is_pointer(base_id flat.NodeId) bool { + if int(base_id) < 0 || int(base_id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(base_id)] + if node.kind != .ident { + return false + } + if g.local_storage_is_pointer(node.value) { + return true + } + if typ := g.tc.cur_scope.lookup(node.value) { + return typ is types.Pointer + } + return false +} + +fn (g &FlatGen) receiver_needs_address(base_id flat.NodeId, base_type types.Type) bool { + if int(base_id) >= 0 && int(base_id) < g.a.nodes.len { + node := g.a.nodes[int(base_id)] + if node.kind == .ident && g.local_storage_is_pointer(node.value) { + return false + } + if node.kind == .selector { + if storage_type := g.receiver_storage_type(base_id) { + return storage_type !is types.Pointer + } + } + } + return base_type !is types.Pointer +} + +fn (g &FlatGen) interface_receiver_needs_address(base_id flat.NodeId, base_type types.Type) bool { + if int(base_id) >= 0 && int(base_id) < g.a.nodes.len { + node := g.a.nodes[int(base_id)] + if node.kind == .ident { + if typ := g.tc.cur_scope.lookup(node.value) { + if typ is types.Pointer { + clean := types.unwrap_pointer(typ) + if clean is types.Interface { + return false + } + } + } + } + } + actual := g.usable_expr_type(base_id) + if actual is types.Pointer { + clean := types.unwrap_pointer(actual) + if clean is types.Interface { + return false + } + } + return g.receiver_needs_address(base_id, base_type) +} + +fn (mut g FlatGen) gen_interface_method_call(node flat.Node, fn_node flat.Node, base_type types.Type) bool { + clean0 := types.unwrap_pointer(base_type) + mut clean := clean0 + if clean0 is types.Alias { + clean = clean0.base_type + } + if clean !is types.Interface { + return false + } + iface := clean as types.Interface + mut iface_name := iface.name + base_name, _, is_generic_iface := g.shared_generic_app_parts(iface_name) + if is_generic_iface { + // A specialized generic interface dispatches through the base + // interface's runtime box and method table. + iface_name = base_name + } + if g.is_ierror_type_name(iface_name) { + return false + } + if iface_name !in g.interfaces { + return false + } + if _ := g.field_type(base_type, fn_node.value) { + return false + } + method_name := '${iface_name}.${fn_node.value}' + param_types := g.interface_method_param_types(method_name) or { []types.Type{} } + base_id := g.a.child(fn_node, 0) + g.write(g.cname(method_name)) + g.write('(') + needs_address := g.interface_receiver_needs_address(base_id, base_type) + wrap_rvalue := needs_address && !g.expr_is_addressable(base_id) + if wrap_rvalue { + g.write('&((${g.value_c_type(base_type)}[]){') + } else if needs_address { + g.write('&') + } + g.gen_expr(base_id) + if wrap_rvalue { + g.write('})[0]') + } + mut emitted_arg_count := 0 + for i in 1 .. node.children_count { + arg_id := g.a.child(&node, i) + if int(arg_id) < 0 { + continue + } + arg_node := g.a.nodes[int(arg_id)] + g.write(', ') + emitted_arg_count++ + param_idx := i + if param_idx < param_types.len { + if g.gen_fixed_array_pointer_lvalue_arg(arg_id, param_types[param_idx]) { + continue + } + if fixed := array_fixed_type(param_types[param_idx]) { + g.gen_fixed_array_data_arg(arg_id, fixed) + continue + } + if g.gen_pointer_arg_from_array_literal(arg_node, param_types[param_idx]) { + continue + } + if g.gen_mut_sum_lvalue_arg(arg_id, param_types[param_idx]) { + continue + } + if child_id := g.addressed_rvalue_arg(arg_node) { + pt := param_types[param_idx] + if g.gen_addressed_rvalue_arg(child_id, pt) { + continue + } + } + emitted_variant := g.gen_sum_variant_arg(arg_id, param_types[param_idx]) + if !emitted_variant { + if g.gen_optional_arg(arg_id, param_types[param_idx]) { + // handled + } else if g.gen_embedded_interface_receiver(arg_id, g.usable_expr_type(arg_id), + param_types[param_idx], param_types[param_idx] is types.Pointer) + { + // handled + } else { + g.gen_arg_for_expected_type(arg_id, param_types[param_idx]) + } + } + } else { + g.gen_expr(arg_id) + } + } + expected_args := if param_types.len > 0 { param_types.len - 1 } else { 0 } + if emitted_arg_count < expected_args { + for pi in emitted_arg_count .. expected_args { + g.write(', ') + g.gen_default_value_for_type(param_types[pi + 1]) + } + } + g.write(')') + return true +} + +fn (g &FlatGen) call_target_name(id flat.NodeId) string { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return '' + } + node := g.a.nodes[int(id)] + match node.kind { + .ident { + // A bare call target resolves to a function in the current module before a + // same-spelled imported or module constant. For example, builtin.f32_max() + // must not be rewritten to the math.internal.f32_max constant. + if node.value in g.tc.fn_ret_types || node.value in g.tc.fn_param_types + || g.non_generic_fn_decl_exists_in_module(node.value, g.tc.cur_module) { + return node.value + } + if target := g.const_fn_call_target_name(node) { + return target + } + return node.value + } + .selector { + if target := g.const_fn_call_target_name(node) { + return target + } + if node.children_count == 0 { + return node.value + } + base := g.a.child_node(&node, 0) + if base.kind == .ident { + if mod := g.import_alias_module(base.value) { + return '${mod}.${node.value}' + } + return '${base.value}.${node.value}' + } + return node.value + } + .index { + if node.children_count > 0 { + return g.call_target_name(g.a.child(&node, 0)) + } + return node.value + } + else { + return node.value + } + } +} + +fn (g &FlatGen) const_fn_call_target_name(node flat.Node) ?string { + key := g.const_key_for_call_target(node) or { g.const_ref_name_from_node(node) } + if key.len == 0 { + return none + } + expr_id := g.tc.const_exprs[key] or { g.const_vals[key] or { return none } } + if int(expr_id) < 0 || int(expr_id) >= g.a.nodes.len { + return none + } + expr := g.a.nodes[int(expr_id)] + match expr.kind { + .ident, .selector, .index { + target := g.call_target_name(expr_id) + if target in g.tc.fn_ret_types || target in g.tc.fn_param_types + || g.cname(target) in g.tc.fn_ret_types || g.cname(target) in g.tc.fn_param_types { + return target + } + typ := g.const_fn_call_type(node, key, expr_id) + if typ.name().starts_with('fn ') { + return target + } + return none + } + else { + typ := g.const_fn_call_type(node, key, expr_id) + if _ := fn_type_from(typ) { + return g.const_ident_c_name(key) + } + return none + } + } +} + +fn (g &FlatGen) const_fn_call_type(node flat.Node, key string, expr_id flat.NodeId) types.Type { + if node.kind == .selector { + if typ := g.tc.selector_const_type(node) { + return typ + } + } + return g.tc.const_types[key] or { g.tc.resolve_type(expr_id) } +} + +fn (g &FlatGen) const_key_for_call_target(node flat.Node) ?string { + name := g.call_target_key(node) + if name.len == 0 { + return none + } + if name in g.tc.const_types { + return name + } + if key := g.tc.const_suffixes[name] { + if key.len > 0 { + return key + } + } + return none +} + +fn (g &FlatGen) call_target_key(node flat.Node) string { + match node.kind { + .ident { + if node.value.len == 0 { + return '' + } + if g.tc.cur_module.len > 0 && g.tc.cur_module != 'main' && g.tc.cur_module != 'builtin' { + qname := '${g.tc.cur_module}.${node.value}' + if qname in g.tc.const_types { + return qname + } + } + return node.value + } + .selector { + if node.children_count == 0 { + return node.value + } + base := g.a.child_node(&node, 0) + if base.kind != .ident { + return '' + } + mod := g.import_alias_module(base.value) or { base.value } + qname := '${mod}.${node.value}' + if qname in g.tc.const_types { + return qname + } + return '${base.value}.${node.value}' + } + .index { + if node.children_count > 0 { + return g.call_target_key(g.a.child_node(&node, 0)) + } + return node.value + } + else { + return '' + } + } +} + +fn (g &FlatGen) call_has_selector_name(id flat.NodeId, name string) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(id)] + if node.kind == .selector && node.value == name { + return true + } + for i in 0 .. node.children_count { + if g.call_has_selector_name(g.a.child(&node, i), name) { + return true + } + } + return false +} + +fn (g &FlatGen) is_json_decode_target_name(target string) bool { + // Only the old C-magic `json` module uses the cgen shortcut. `json2`/`x.json2` + // are pure V and must compile through normal codegen, so they are not matched + // here (matching them would inject cJSON calls and hijack json2's own + // internal `encode`/`decode` functions). + return target == 'json.decode' || (target == 'decode' && g.tc.cur_module == 'json') +} + +fn (g &FlatGen) is_json_decode_call(id flat.NodeId, target string) bool { + if resolved := g.tc.resolved_call_name(id) { + if g.is_json_decode_target_name(resolved) { + return true + } + } + return g.is_json_decode_target_name(target) +} + +fn (g &FlatGen) is_json_decode_self_call(target string, resolved string) bool { + return g.tc.cur_module == 'json' && target == 'decode' + && resolved in ['', 'decode', 'json.decode'] +} + +fn (mut g FlatGen) gen_json_encode_call(node flat.Node) bool { + if node.children_count < 2 { + return false + } + arg_id := g.a.child(&node, 1) + typ := types.unwrap_pointer(g.usable_expr_type(arg_id)) + expr := g.expr_to_string_with_expected_type(arg_id, typ) + // Bind the argument to a single temporary so it is evaluated exactly once, + // no matter how many times the field/enum expansion references it. + tmp := g.tmp_name() + encoded := g.json_encode_value_c_expr(typ, tmp) or { return false } + g.write('({ ${g.value_c_type(typ)} ${tmp} = ${expr}; ${encoded}; })') + return true +} + +fn (mut g FlatGen) preintern_json_encode_strings() { + for idx, node in g.a.nodes { + if node.kind != .call || node.children_count < 2 { + continue + } + resolved := g.tc.resolved_call_name(flat.NodeId(idx)) or { continue } + if resolved !in ['json.encode', 'json__encode'] { + continue + } + arg_id := g.a.child(&node, 1) + mut typ := types.unwrap_pointer(g.usable_expr_type(arg_id)) + if typ is types.Void || typ is types.Unknown { + arg := g.a.nodes[int(arg_id)] + typ = types.unwrap_pointer(g.tc.parse_type(arg.typ)) + } + g.preintern_json_encode_value_strings(typ, []string{}) + } +} + +fn (mut g FlatGen) preintern_json_encode_value_strings(typ types.Type, seen []string) { + clean := if typ is types.Alias { typ.base_type } else { typ } + if clean is types.Enum { + names, labels := g.json_enum_labels(clean.name) + for name in names { + g.intern_string(labels[name] or { name }) + } + return + } + if clean is types.Array { + g.preintern_json_encode_value_strings(clean.elem_type, seen) + return + } + if clean is types.Map { + g.preintern_json_encode_value_strings(clean.value_type, seen) + return + } + if clean is types.Primitive { + if clean.props.has(.boolean) { + g.intern_string('true') + g.intern_string('false') + } + return + } + if clean !is types.Struct { + return + } + struct_type := clean as types.Struct + if struct_type.name in seen { + return + } + fields := g.json_encode_struct_field_exprs(struct_type.name, '_v', seen) or { return } + g.intern_string('{') + g.intern_string('}') + mut has_omitempty := false + for field in fields { + if json_attrs_skip_field(field.attrs) { + continue + } + if json_attrs_have_name(field.attrs, 'omitempty') { + if !g.json_encode_omitempty_supported(field.typ) { + return + } + has_omitempty = true + } + } + mut next_seen := seen.clone() + next_seen << struct_type.name + mut emitted_fields := 0 + for field in fields { + if json_attrs_skip_field(field.attrs) { + continue + } + if has_omitempty { + g.intern_string(json_struct_field_label_prefix(field.label, '')) + g.intern_string(json_struct_field_label_prefix(field.label, ',')) + } else { + separator := if emitted_fields == 0 { '' } else { ',' } + g.intern_string(json_struct_field_label_prefix(field.label, separator)) + } + g.preintern_json_encode_value_strings(field.typ, next_seen) + emitted_fields++ + } +} + +struct JsonEncodeFieldExpr { + label string + typ types.Type + expr string + attrs []string +} + +fn (mut g FlatGen) json_encode_value_c_expr(typ types.Type, expr string) ?string { + clean := if typ is types.Alias { typ.base_type } else { typ } + if clean is types.Enum { + if cast := g.json_enum_number_cast(clean.name) { + // `@[json_as_number]` enum: encode the numeric backing value, not the label. + num_fn := if cast == 'u64' { 'u64__str' } else { 'i64__str' } + return '${num_fn}((${cast})(${expr}))' + } + names, labels := g.json_enum_labels(clean.name) + if names.len == 0 { + return none + } + mut result := '' + for i := names.len - 1; i >= 0; i-- { + name := names[i] + label := labels[name] or { name } + sid := g.intern_string(label) + encoded_label := 'v3_json_encode_string(_str_${sid})' + value := g.enum_value_expr_for_type(clean.name, name) or { + '${g.cname(clean.name)}__${g.cname(name)}' + } + if result.len == 0 { + result = encoded_label + } else { + result = '((${expr}) == ${value} ? ${encoded_label} : ${result})' + } + } + return result + } + if clean is types.String { + // Escape the contents with a length-aware escaper (quotes, backslashes and + // control characters), preserving embedded NUL bytes; cJSON_CreateString is + // C-NUL-terminated and would truncate the V string. + return 'v3_json_encode_string(${expr})' + } + if clean is types.Array { + elem_ct := g.value_c_type(clean.elem_type) + out_name := g.tmp_name() + idx_name := g.tmp_name() + elem_name := g.tmp_name() + encoded := g.json_encode_value_c_expr(clean.elem_type, '(*${elem_name})') or { return none } + return '({ string ${out_name} = v3_c_lit("[", 1); for (int ${idx_name} = 0; ${idx_name} < (${expr}).len; ++${idx_name}) { if (${idx_name} > 0) ${out_name} = string__plus(${out_name}, v3_c_lit(",", 1)); ${elem_ct}* ${elem_name} = (${elem_ct}*)array_get(${expr}, ${idx_name}); ${out_name} = string__plus(${out_name}, ${encoded}); } string__plus(${out_name}, v3_c_lit("]", 1)); })' + } + if clean is types.Map { + key_clean := if clean.key_type is types.Alias { + clean.key_type.base_type + } else { + clean.key_type + } + if key_clean !is types.String { + return none + } + value_ct := g.value_c_type(clean.value_type) + map_name := g.tmp_name() + out_name := g.tmp_name() + idx_name := g.tmp_name() + key_name := g.tmp_name() + value_name := g.tmp_name() + encoded := g.json_encode_value_c_expr(clean.value_type, '(*${value_name})') or { + return none + } + return '({ map ${map_name} = ${expr}; string ${out_name} = v3_c_lit("{", 1); bool ${out_name}_first = true; for (int ${idx_name} = 0; ${idx_name} < ${map_name}.key_values.len; ++${idx_name}) { if (${map_name}.key_values.deletes != 0 && ${map_name}.key_values.all_deleted != 0 && ${map_name}.key_values.all_deleted[${idx_name}] != 0) continue; if (!${out_name}_first) ${out_name} = string__plus(${out_name}, v3_c_lit(",", 1)); string* ${key_name} = (string*)(${map_name}.key_values.keys + ${idx_name} * ${map_name}.key_values.key_bytes); ${value_ct}* ${value_name} = (${value_ct}*)(${map_name}.key_values.values + ${idx_name} * ${map_name}.key_values.value_bytes); ${out_name} = string__plus(string__plus(string__plus(${out_name}, v3_json_encode_string(*${key_name})), v3_c_lit(":", 1)), ${encoded}); ${out_name}_first = false; } string__plus(${out_name}, v3_c_lit("}", 1)); })' + } + if clean is types.Primitive { + if clean.props.has(.boolean) { + true_sid := g.intern_string('true') + false_sid := g.intern_string('false') + return '((bool)(${expr}) ? _str_${true_sid} : _str_${false_sid})' + } + if clean.props.has(.integer) { + if clean.props.has(.unsigned) { + return 'u64__str((u64)(${expr}))' + } + return 'i64__str((i64)(${expr}))' + } + if clean.props.has(.float) { + value_name := g.tmp_name() + null_sid := g.intern_string('null') + return '({ double ${value_name} = (double)(${expr}); isfinite(${value_name}) ? f64__str(${value_name}) : _str_${null_sid}; })' + } + return none + } + if clean is types.Struct { + fields := g.json_encode_struct_field_exprs(clean.name, expr, []string{}) or { return none } + open := g.intern_string('{') + close := g.intern_string('}') + mut has_omitempty := false + for field in fields { + attrs := field.attrs + if json_attrs_skip_field(attrs) { + continue + } + if json_attrs_have_name(attrs, 'omitempty') { + if g.json_encode_omitempty_supported(field.typ) { + has_omitempty = true + } else { + return none + } + } + } + if has_omitempty { + res := g.tmp_name() + count := g.tmp_name() + mut body := '({ string ${res} = _str_${open}; int ${count} = 0; ' + for field in fields { + attrs := field.attrs + if json_attrs_skip_field(attrs) { + continue + } + label := field.label + prefix := g.intern_string(json_struct_field_label_prefix(label, '')) + prefix_with_separator := g.intern_string(json_struct_field_label_prefix(label, ',')) + field_expr := field.expr + encoded := g.json_encode_value_c_expr(field.typ, field_expr) or { return none } + append := '${res} = string__plus(string__plus(${res}, (${count} == 0 ? _str_${prefix} : _str_${prefix_with_separator})), ${encoded}); ${count}++;' + if json_attrs_have_name(attrs, 'omitempty') { + empty := g.json_encode_omitempty_expr(field.typ, field_expr) or { return none } + body += 'if (!(${empty})) { ${append} } ' + } else { + body += '{ ${append} } ' + } + } + body += 'string__plus(${res}, _str_${close}); })' + return body + } + mut result := '_str_${open}' + mut emitted_fields := 0 + for field in fields { + attrs := field.attrs + if json_attrs_skip_field(attrs) { + continue + } + label := field.label + separator := if emitted_fields == 0 { '' } else { ',' } + prefix := g.intern_string(json_struct_field_label_prefix(label, separator)) + field_expr := field.expr + encoded := g.json_encode_value_c_expr(field.typ, field_expr) or { return none } + result = 'string__plus(string__plus(${result}, _str_${prefix}), ${encoded})' + emitted_fields++ + } + return 'string__plus(${result}, _str_${close})' + } + if clean is types.Map { + if clean.key_type !is types.String { + return none + } + value_ct := g.value_c_type(clean.value_type) + map_name := g.tmp_name() + out_name := g.tmp_name() + idx_name := g.tmp_name() + count_name := g.tmp_name() + key_name := g.tmp_name() + value_name := g.tmp_name() + open := g.intern_string('{') + close := g.intern_string('}') + comma := g.intern_string(',') + colon := g.intern_string(':') + empty := g.intern_string('') + value_expr := g.json_encode_value_c_expr(clean.value_type, '(*(${value_ct}*)${value_name})') or { + return none + } + return '({ map ${map_name} = ${expr}; string ${out_name} = _str_${open}; int ${count_name} = 0; for (int ${idx_name} = 0; ${idx_name} < ${map_name}.key_values.len; ++${idx_name}) { if (${map_name}.key_values.deletes != 0 && ${map_name}.key_values.all_deleted != 0 && ${map_name}.key_values.all_deleted[${idx_name}] != 0) continue; string ${key_name} = *(string*)(${map_name}.key_values.keys + ${idx_name} * ${map_name}.key_values.key_bytes); void* ${value_name} = (void*)(${map_name}.key_values.values + ${idx_name} * ${map_name}.key_values.value_bytes); ${out_name} = string__plus(${out_name}, ${count_name} == 0 ? _str_${empty} : _str_${comma}); ${out_name} = string__plus(${out_name}, v3_json_encode_string(${key_name})); ${out_name} = string__plus(${out_name}, _str_${colon}); ${out_name} = string__plus(${out_name}, ${value_expr}); ${count_name}++; } string__plus(${out_name}, _str_${close}); })' + } + return none +} + +fn (g &FlatGen) json_encode_struct_field_exprs(struct_name string, expr string, seen []string) ?[]JsonEncodeFieldExpr { + if struct_name in seen || g.json_struct_has_encode_field_attrs(struct_name) { + return none + } + fields := g.tc.structs[struct_name] or { return none } + mut next_seen := seen.clone() + next_seen << struct_name + mut out := []JsonEncodeFieldExpr{} + for field in fields { + attrs := g.json_struct_field_attrs(struct_name, field.name) + if json_attrs_skip_field(attrs) { + continue + } + field_expr := '(${expr}).${g.cname(field.name)}' + if embedded := g.json_encode_embedded_struct_field_type(field) { + if attrs.len > 0 { + return none + } + nested := g.json_encode_struct_field_exprs(embedded.name, field_expr, next_seen) or { + return none + } + out << nested + continue + } + out << JsonEncodeFieldExpr{ + label: json_struct_field_label(field.name, attrs) + typ: field.typ + expr: field_expr + attrs: attrs + } + } + return out +} + +fn (g &FlatGen) json_encode_embedded_struct_field_type(field types.StructField) ?types.Struct { + mut field_type := field.typ + if field_type is types.Alias { + field_type = field_type.base_type + } + if field_type is types.Pointer { + return none + } + if field_type is types.Struct && g.json_struct_field_is_embedded(field, field_type.name) { + return field_type + } + return none +} + +fn (g &FlatGen) json_struct_field_is_embedded(field types.StructField, type_name string) bool { + if g.embedded_field_type_name(field).len > 0 { + return true + } + short_field := if field.name.contains('.') { field.name.all_after_last('.') } else { field.name } + short_type := if type_name.contains('.') { type_name.all_after_last('.') } else { type_name } + return field.name == type_name || short_field == short_type + || embedded_field_c_names_match(field.name, type_name) +} + +fn (g &FlatGen) json_encode_omitempty_supported(typ types.Type) bool { + clean := if typ is types.Alias { typ.base_type } else { typ } + if clean is types.String || clean is types.Enum { + return true + } + if clean is types.Primitive { + return clean.props.has(.boolean) || clean.props.has(.integer) || clean.props.has(.float) + } + return false +} + +fn (mut g FlatGen) json_encode_omitempty_expr(typ types.Type, expr string) ?string { + clean := if typ is types.Alias { typ.base_type } else { typ } + if clean is types.String { + return '((${expr}).len == 0)' + } + if clean is types.Enum { + default_value := g.enum_default_value_expr_for_type(clean.name) or { '0' } + return '((${expr}) == ${default_value})' + } + if clean is types.Primitive { + if clean.props.has(.boolean) { + return '(!((bool)(${expr})))' + } + if clean.props.has(.integer) || clean.props.has(.float) { + return '((${expr}) == 0)' + } + } + return none +} + +fn (mut g FlatGen) gen_json_decode_call(node flat.Node) bool { + if node.children_count < 3 { + return false + } + ret_type := g.json_decode_result_type_for_call(node) or { return false } + mut result_base := types.Type(types.void_) + if ret_type is types.ResultType { + result_base = ret_type.base_type + } else { + return false + } + + base := types.unwrap_pointer(result_base) + if base !is types.Struct { + return false + } + struct_type := base as types.Struct + fields := g.tc.structs[struct_type.name] or { return false } + // The shortcut can only faithfully decode a fixed set of field types and + // attributes. Decline anything else so the result stays an error instead of + // silently succeeding with dropped/renamed/rounded data. + if g.json_struct_has_decode_field_attrs(struct_type.name) { + return false + } + // A field with a default initializer (`n int = 5`) must keep that default when the + // JSON omits it, but the fast path would zero it; decline such structs so the + // normal decoder preserves the default instead of silently changing the value. + if g.json_struct_has_field_default(struct_type.name) { + return false + } + mut needs_exact_integer := false + for field in fields { + if !g.json_decode_value_supported(field.typ, 0) { + return false + } + if g.json_decode_value_needs_exact_integer(field.typ, 0) { + needs_exact_integer = true + } + } + json_id := g.a.child(&node, 2) + json_name := g.tmp_name() + root_name := g.tmp_name() + out_name := g.tmp_name() + opt_ct := g.optional_type_name(ret_type) + struct_ct := g.value_c_type(struct_type) + g.write('({ string ${json_name} = ') + g.gen_expr_with_expected_type(json_id, types.Type(types.string_)) + g.write('; cJSON* ${root_name} = cJSON_ParseWithLength((char*)${json_name}.str, (size_t)${json_name}.len); ') + if needs_exact_integer { + g.write('v3_json_preserve_number_tokens(${json_name}.str, ${json_name}.len, ${root_name}); ') + } + // A struct only decodes successfully from a JSON object; `null`, arrays, + // strings and numbers must remain an error rather than a zero-valued struct. + // A present field must also have the expected cJSON type, otherwise the decode + // fails instead of silently substituting a default (e.g. `{"n":"bad"}` for int). + mut checks := []string{} + for field in fields { + checks << g.json_decode_field_valid_expr(root_name, struct_type.name, field) + } + valid_cond := if checks.len > 0 { checks.join(' && ') } else { 'true' } + g.write('${opt_ct} ${out_name} = (${opt_ct}){0}; if (${root_name} != NULL) { if (cJSON_IsObject(${root_name})) { if (${valid_cond}) { ') + g.write('${out_name}.ok = true; ${out_name}.value = (${struct_ct}){') + for i, field in fields { + if i > 0 { + g.write(', ') + } + g.write('.${g.cname(field.name)} = ') + g.gen_json_decode_field_expr(root_name, struct_type.name, field) + } + g.write('}; } } cJSON_Delete(${root_name}); } ${out_name}; })') + return true +} + +// json_decode_field_valid_expr returns a C boolean expression that is true when the +// JSON value for `field` is absent (defaulted) or present with the cJSON type the +// fast-path decoder can faithfully read. A present wrong-typed value fails the decode. +fn (mut g FlatGen) json_decode_field_valid_expr(root_name string, struct_name string, field types.StructField) string { + item := g.json_decode_field_item(root_name, struct_name, field) + return g.json_decode_value_valid_expr(item, field.typ) +} + +fn (g &FlatGen) json_decode_field_item(root_name string, struct_name string, field types.StructField) string { + if g.json_decode_struct_field_is_embedded(field) { + return root_name + } + attrs := g.json_struct_field_attrs(struct_name, field.name) + label := json_struct_field_label(field.name, attrs) + return 'cJSON_GetObjectItemCaseSensitive(${root_name}, "${c_escape(label)}")' +} + +fn (mut g FlatGen) json_decode_value_valid_expr(item string, typ types.Type) string { + clean := if typ is types.Alias { typ.base_type } else { typ } + // Mirror the json module: string fields accept strings and stringify objects/arrays, + // bool fields require booleans, and numeric/enum fields tolerate wrong-typed or + // unknown values by falling back to a default. + if clean is types.String { + return '(${item} == NULL || cJSON_IsNull(${item}) || cJSON_IsString(${item}) || cJSON_IsObject(${item}) || cJSON_IsArray(${item}))' + } + if clean is types.Primitive && clean.props.has(.boolean) { + return '(${item} == NULL || cJSON_IsBool(${item}))' + } + if clean is types.Array { + item_name := g.tmp_name() + elem_name := g.tmp_name() + valid_name := g.tmp_name() + elem_valid := g.json_decode_value_valid_expr(elem_name, clean.elem_type) + return '({ cJSON* ${item_name} = ${item}; bool ${valid_name} = (${item_name} == NULL || cJSON_IsArray(${item_name})); if (${valid_name} && ${item_name} != NULL) { cJSON* ${elem_name} = NULL; cJSON_ArrayForEach(${elem_name}, ${item_name}) { if (!(${elem_valid})) { ${valid_name} = false; break; } } } ${valid_name}; })' + } + if clean is types.Pointer { + inner := g.json_decode_value_valid_expr(item, clean.base_type) + return '(${item} == NULL || cJSON_IsNull(${item}) || ${inner})' + } + if clean is types.Struct { + fields := g.tc.structs[clean.name] or { return 'false' } + mut checks := []string{cap: fields.len} + for field in fields { + field_item := g.json_decode_field_item(item, clean.name, field) + checks << g.json_decode_value_valid_expr(field_item, field.typ) + } + children_valid := if checks.len > 0 { checks.join(' && ') } else { 'true' } + return '(${item} == NULL || (cJSON_IsObject(${item}) && ${children_valid}))' + } + return 'true' +} + +// json_decode_value_supported reports whether the fast-path decoder can preserve `typ`. +// The depth guard also prevents recursive structures from making this preflight recurse forever. +fn (g &FlatGen) json_decode_value_supported(typ types.Type, depth int) bool { + if depth > 12 { + return false + } + clean := if typ is types.Alias { typ.base_type } else { typ } + if clean is types.String { + return true + } + if clean is types.Enum { + names, _ := g.json_enum_labels(clean.name) + return names.len > 0 + } + if clean is types.Primitive { + return true + } + if clean is types.Array { + return g.json_decode_value_supported(clean.elem_type, depth + 1) + } + if clean is types.Pointer { + return g.json_decode_value_supported(clean.base_type, depth + 1) + } + if clean is types.Struct { + if g.json_struct_has_decode_field_attrs(clean.name) + || g.json_struct_has_field_default(clean.name) { + return false + } + fields := g.tc.structs[clean.name] or { return false } + for field in fields { + if !g.json_decode_value_supported(field.typ, depth + 1) { + return false + } + } + return true + } + return false +} + +fn (g &FlatGen) json_decode_value_needs_exact_integer(typ types.Type, depth int) bool { + if depth > 12 { + return false + } + clean := if typ is types.Alias { typ.base_type } else { typ } + if clean is types.Primitive { + return clean.props.has(.integer) && clean.size == 64 + } + if clean is types.Array { + return g.json_decode_value_needs_exact_integer(clean.elem_type, depth + 1) + } + if clean is types.Pointer { + return g.json_decode_value_needs_exact_integer(clean.base_type, depth + 1) + } + if clean is types.Struct { + fields := g.tc.structs[clean.name] or { return false } + for field in fields { + if g.json_decode_value_needs_exact_integer(field.typ, depth + 1) { + return true + } + } + } + return false +} + +fn (mut g FlatGen) gen_json_decode_field_expr(root_name string, struct_name string, field types.StructField) { + item := g.json_decode_field_item(root_name, struct_name, field) + clean := if field.typ is types.Alias { field.typ.base_type } else { field.typ } + if clean is types.Pointer { + if info, default_id := g.json_struct_field_default_expr(struct_name, field.name) { + default_node := g.a.node(default_id) + if default_node.kind != .nil_literal { + item_name := g.tmp_name() + out_name := g.tmp_name() + field_ct := g.value_c_type(field.typ) + g.write('({ cJSON* ${item_name} = ${item}; ${field_ct} ${out_name}; if (${item_name} == NULL) { ${out_name} = ') + old_module := g.tc.cur_module + old_file := g.tc.cur_file + g.tc.cur_module = info.module + g.tc.cur_file = info.file + g.gen_struct_field_expr_for_field(default_id, info.full_name, field.name, field.typ) + g.tc.cur_module = old_module + g.tc.cur_file = old_file + g.write('; } else { ${out_name} = ') + g.gen_json_decode_value_expr(item_name, field.typ) + g.write('; } ${out_name}; })') + return + } + } + } + g.gen_json_decode_value_expr(item, field.typ) +} + +fn (g &FlatGen) json_struct_field_default_expr(struct_name string, field_name string) ?(StructDeclInfo, flat.NodeId) { + info := g.find_struct_decl(json_struct_decl_name(struct_name)) or { return none } + for i in 0 .. info.node.children_count { + field := g.a.child_node(&info.node, i) + if field.kind == .field_decl && field.value == field_name && field.children_count > 0 { + return info, g.a.child(field, 0) + } + } + return none +} + +fn (mut g FlatGen) gen_json_decode_value_expr(item string, typ types.Type) { + clean := if typ is types.Alias { typ.base_type } else { typ } + if clean is types.String { + empty := g.intern_string('') + item_name := g.tmp_name() + out_name := g.tmp_name() + raw_name := g.tmp_name() + g.write('({ cJSON* ${item_name} = ${item}; string ${out_name} = _str_${empty}; if (${item_name} != NULL && cJSON_IsString(${item_name}) && ${item_name}->valuestring != NULL) { ${out_name} = tos_clone((u8*)${item_name}->valuestring); } else if (${item_name} != NULL && (cJSON_IsObject(${item_name}) || cJSON_IsArray(${item_name}))) { char* ${raw_name} = cJSON_PrintUnformatted(${item_name}); if (${raw_name} != NULL) { ${out_name} = tos_clone((u8*)${raw_name}); cJSON_free(${raw_name}); } } ${out_name}; })') + return + } + if clean is types.Enum { + names, labels := g.json_enum_labels(clean.name) + if names.len == 0 { + g.write('0') + return + } + default_value := g.enum_value_expr_for_type(clean.name, names[0]) or { + '${g.cname(clean.name)}__${g.cname(names[0])}' + } + if _ := g.json_enum_number_cast(clean.name) { + // `@[json_as_number]`: the JSON value is a number, not a label string. + g.write('(${item} != NULL ? (${g.value_c_type(clean)})${item}->valuedouble : ${default_value})') + return + } + mut result := default_value + for i := names.len - 1; i >= 0; i-- { + name := names[i] + label := labels[name] or { name } + value := g.enum_value_expr_for_type(clean.name, name) or { + '${g.cname(clean.name)}__${g.cname(name)}' + } + mut comparisons := [ + 'strcmp(${item}->valuestring, "${c_escape(name)}") == 0', + ] + if label != name { + comparisons << 'strcmp(${item}->valuestring, "${c_escape(label)}") == 0' + } + matches := comparisons.join(' || ') + result = '(${item} != NULL && ${item}->valuestring != NULL && (${matches}) ? ${value} : ${result})' + } + g.write(result) + return + } + if clean is types.Primitive { + if clean.props.has(.boolean) { + // cJSON records booleans via the node type (cJSON_True/cJSON_False), + // not in valuedouble, so read them with cJSON_IsTrue. + g.write('(${item} != NULL ? (bool)cJSON_IsTrue(${item}) : 0)') + return + } + if clean.props.has(.integer) && clean.size == 64 { + // cJSON's double cannot exactly represent every i64/u64. The fast-path + // setup preserves the original number token in valuestring; use it for + // decimal integers and retain valuedouble for fractional/exponent forms. + item_name := g.tmp_name() + raw_name := g.tmp_name() + scan_name := g.tmp_name() + is_decimal_name := g.tmp_name() + is_negative_name := g.tmp_name() + magnitude_name := g.tmp_name() + limit_name := g.tmp_name() + digit_name := g.tmp_name() + out_name := g.tmp_name() + ct := g.value_c_type(clean) + limit_expr := if clean.props.has(.unsigned) { + '18446744073709551615ULL' + } else { + '(${is_negative_name} ? 9223372036854775808ULL : 9223372036854775807ULL)' + } + value_expr := if clean.props.has(.unsigned) { + '(${is_negative_name} ? (u64)0 - ${magnitude_name} : ${magnitude_name})' + } else { + '(${is_negative_name} ? (${magnitude_name} == 9223372036854775808ULL ? (i64)(-9223372036854775807LL - 1LL) : -(i64)${magnitude_name}) : (i64)${magnitude_name})' + } + g.write('({ cJSON* ${item_name} = ${item}; const char* ${raw_name} = ${item_name} != NULL ? ${item_name}->valuestring : NULL; const char* ${scan_name} = ${raw_name}; bool ${is_decimal_name} = ${scan_name} != NULL; bool ${is_negative_name} = false; if (${is_decimal_name} && (*${scan_name} == \'-\' || *${scan_name} == \'+\')) { ${is_negative_name} = *${scan_name} == \'-\'; ${scan_name}++; } if (${is_decimal_name} && *${scan_name} == \'\\0\') { ${is_decimal_name} = false; } u64 ${magnitude_name} = 0; u64 ${limit_name} = ${limit_expr}; while (${is_decimal_name} && *${scan_name} != \'\\0\') { if (*${scan_name} < \'0\' || *${scan_name} > \'9\') { ${is_decimal_name} = false; break; } u64 ${digit_name} = (u64)(*${scan_name} - \'0\'); if (${magnitude_name} > (${limit_name} - ${digit_name}) / 10) { ${is_decimal_name} = false; break; } ${magnitude_name} = ${magnitude_name} * 10 + ${digit_name}; ${scan_name}++; } ${ct} ${out_name} = ${item_name} != NULL ? (${ct})${item_name}->valuedouble : 0; if (${is_decimal_name}) { ${out_name} = (${ct})${value_expr}; } ${out_name}; })') + return + } + g.write('(${item} != NULL ? (${g.value_c_type(clean)})${item}->valuedouble : 0)') + return + } + if clean is types.Array { + item_name := g.tmp_name() + out_name := g.tmp_name() + elem_name := g.tmp_name() + value_name := g.tmp_name() + elem_ct := g.value_c_type(clean.elem_type) + g.write('({ cJSON* ${item_name} = ${item}; Array ${out_name} = array_new(sizeof(${elem_ct}), 0, (${item_name} != NULL && cJSON_IsArray(${item_name})) ? cJSON_GetArraySize(${item_name}) : 0); if (${item_name} != NULL && cJSON_IsArray(${item_name})) { cJSON* ${elem_name} = NULL; cJSON_ArrayForEach(${elem_name}, ${item_name}) { ${elem_ct} ${value_name} = ') + g.gen_json_decode_value_expr(elem_name, clean.elem_type) + g.write('; array_push(&${out_name}, &${value_name}); } } ${out_name}; })') + return + } + if clean is types.Pointer { + item_name := g.tmp_name() + out_name := g.tmp_name() + value_name := g.tmp_name() + base_ct := g.value_c_type(clean.base_type) + g.write('({ cJSON* ${item_name} = ${item}; ${base_ct}* ${out_name} = NULL; if (${item_name} != NULL && !cJSON_IsNull(${item_name})) { ${base_ct} ${value_name} = ') + g.gen_json_decode_value_expr(item_name, clean.base_type) + g.write('; ${out_name} = ${g.heap_local_memdup_expr(value_name, clean.base_type, base_ct, + false)}; } ${out_name}; })') + return + } + if clean is types.Struct { + fields := g.tc.structs[clean.name] or { + g.gen_default_value_for_type(typ) + return + } + g.write('(${g.value_c_type(clean)}){') + for i, field in fields { + if i > 0 { + g.write(', ') + } + g.write('.${g.cname(field.name)} = ') + g.gen_json_decode_field_expr(item, clean.name, field) + } + g.write('}') + return + } + g.gen_default_value_for_type(typ) +} + +fn (g &FlatGen) json_decode_struct_field_is_embedded(field types.StructField) bool { + mut field_type := field.typ + if field_type is types.Alias { + field_type = field_type.base_type + } + field_type = types.unwrap_pointer(field_type) + if field_type is types.Struct { + return g.json_struct_field_is_embedded(field, field_type.name) + } + return false +} + +// json_struct_has_field_default reports whether `struct_name` has a default initializer +// that the fast-path decoder cannot preserve. Pointer defaults are handled separately +// by gen_json_decode_field_expr; other defaults still require the full decoder. +fn (g &FlatGen) json_struct_has_field_default(struct_name string) bool { + decl_name := json_struct_decl_name(struct_name) + mut cur_module := '' + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + if node.kind == .module_decl { + cur_module = node.value + continue + } + if node.kind != .struct_decl { + continue + } + qualified := if cur_module.len > 0 && cur_module !in ['main', 'builtin'] { + '${cur_module}.${node.value}' + } else { + node.value + } + if decl_name != node.value && decl_name != qualified { + continue + } + for i in 0 .. node.children_count { + field := g.a.child_node(&node, i) + if field.kind != .field_decl || field.children_count == 0 { + continue + } + if field.typ.trim_space().starts_with('&') { + // Explicit nil defaults use the pointer zero value. Non-nil pointer + // defaults are emitted by gen_json_decode_field_expr when the key is + // absent, so both pointer forms remain safe on the shortcut. + continue + } + return true + } + return false + } + return false +} + +fn json_struct_decl_name(name string) string { + bracket := name.index_u8(`[`) + if bracket <= 0 { + return name + } + return name[..bracket] +} + +// json_struct_has_field_attrs reports whether any field of `struct_name` carries +// attributes (`@[json: 'x']`, `@[skip]`, `@[required]`, ...). Field attributes are +// stored in `field_decl.generic_params` with index 0 holding the mut/pub flags and +// any further entries being attributes. +fn (g &FlatGen) json_struct_has_field_attrs(struct_name string) bool { + return g.json_struct_has_disallowed_field_attrs(struct_name, []string{}) +} + +fn (g &FlatGen) json_struct_has_decode_field_attrs(struct_name string) bool { + // `omitempty` changes encoding only. Renamed `json` fields are handled by + // json_decode_field_item. Skip/required and other attributes still need the + // full decoder. + if g.json_struct_has_disallowed_field_attrs(struct_name, ['json', 'omitempty']) { + return true + } + info := g.find_struct_decl(json_struct_decl_name(struct_name)) or { return false } + for i in 0 .. info.node.children_count { + field := g.a.child_node(&info.node, i) + if field.kind != .field_decl { + continue + } + attrs := field.generic_params() + if attrs.len > 1 && json_attrs_skip_field(attrs[1..]) { + return true + } + } + return false +} + +fn (g &FlatGen) json_struct_has_encode_field_attrs(struct_name string) bool { + return g.json_struct_has_disallowed_field_attrs(struct_name, ['skip', 'json', 'omitempty']) +} + +fn (g &FlatGen) json_struct_has_disallowed_field_attrs(struct_name string, allowed []string) bool { + decl_name := json_struct_decl_name(struct_name) + mut cur_module := '' + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + if node.kind == .module_decl { + cur_module = node.value + continue + } + if node.kind != .struct_decl { + continue + } + qualified := if cur_module.len > 0 && cur_module !in ['main', 'builtin'] { + '${cur_module}.${node.value}' + } else { + node.value + } + if decl_name != node.value && decl_name != qualified { + continue + } + for i in 0 .. node.children_count { + field := g.a.child_node(&node, i) + if field.kind != .field_decl { + continue + } + attrs := field.generic_params() + if attrs.len > 1 { + for attr in attrs[1..] { + name := attr.all_before(':').trim_space() + if name !in allowed { + return true + } + } + } + } + return false + } + return false +} + +fn (g &FlatGen) json_struct_field_attrs(struct_name string, field_name string) []string { + info := g.find_struct_decl(json_struct_decl_name(struct_name)) or { return []string{} } + for i in 0 .. info.node.children_count { + field := g.a.child_node(&info.node, i) + field_params := field.generic_params() + if field.kind == .field_decl && field.value == field_name && field_params.len > 1 { + return field_params[1..] + } + } + return []string{} +} + +fn json_attrs_have_name(attrs []string, name string) bool { + for attr in attrs { + if attr.all_before(':').trim_space() == name { + return true + } + } + return false +} + +fn json_attrs_skip_field(attrs []string) bool { + if json_attrs_have_name(attrs, 'skip') { + return true + } + for attr in attrs { + if attr.starts_with('json:') && json_enum_attr_label(attr.all_after(':')) == '-' { + return true + } + } + return false +} + +fn json_struct_field_label(field_name string, attrs []string) string { + for attr in attrs { + if attr.starts_with('json:') { + return json_enum_attr_label(attr.all_after(':')) + } + } + return field_name +} + +fn json_struct_field_label_prefix(label string, separator string) string { + return '${separator}"${json_string_content_escape(label)}":' +} + +fn json_string_content_escape(value string) string { + mut out := strings.new_builder(value.len) + for i in 0 .. value.len { + c := value[i] + match c { + `"` { + out.write_string('\\"') + } + `\\` { + out.write_string('\\\\') + } + 8 { + out.write_string('\\b') + } + 12 { + out.write_string('\\f') + } + `\n` { + out.write_string('\\n') + } + `\r` { + out.write_string('\\r') + } + `\t` { + out.write_string('\\t') + } + else { + if c < 32 { + out.write_string('\\u00') + out.write_u8('0123456789abcdef'[int((c >> 4) & 15)]) + out.write_u8('0123456789abcdef'[int(c & 15)]) + } else { + out.write_u8(c) + } + } + } + } + return out.str() +} + +// json_enum_number_cast returns the integer cast (`i64`/`u64`) to use when an enum is +// declared `@[json_as_number]`, so json.encode emits its numeric value rather than a +// quoted label; returns none for a plain enum. +fn (g &FlatGen) json_enum_number_cast(enum_name string) ?string { + mut cur_module := '' + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + if node.kind == .module_decl { + cur_module = node.value + continue + } + if node.kind != .enum_decl { + continue + } + qualified := if cur_module.len > 0 && cur_module !in ['main', 'builtin'] { + '${cur_module}.${node.value}' + } else { + node.value + } + if enum_name != node.value && enum_name != qualified { + continue + } + if 'json_as_number' !in node.generic_params() { + return none + } + backing := if node.generic_params().len > 0 { node.generic_params()[0] } else { '' } + return if backing in ['u8', 'byte', 'u16', 'u32', 'u64', 'usize'] { + 'u64' + } else { + 'i64' + } + } + return none +} + +fn (g &FlatGen) json_enum_labels(enum_name string) ([]string, map[string]string) { + mut names := []string{} + mut labels := map[string]string{} + mut cur_module := '' + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + if node.kind == .module_decl { + cur_module = node.value + continue + } + if node.kind != .enum_decl { + continue + } + qualified := if cur_module.len > 0 && cur_module !in ['main', 'builtin'] { + '${cur_module}.${node.value}' + } else { + node.value + } + if enum_name != node.value && enum_name != qualified { + continue + } + for i in 0 .. node.children_count { + field := g.a.child_node(&node, i) + names << field.value + for attr in field.generic_params() { + if attr.starts_with('json:') { + labels[field.value] = json_enum_attr_label(attr.all_after(':')) + } + } + } + break + } + return names, labels +} + +fn json_enum_attr_label(raw_value string) string { + mut value := raw_value.trim_space() + mut is_raw := false + if value.len >= 3 && value[0] == `r` && value[1] in [`'`, `"`] + && value[value.len - 1] == value[1] { + is_raw = true + value = value[1..] + } + if value.len < 2 || value[0] !in [`'`, `"`] || value[value.len - 1] != value[0] { + return value + } + inner := value[1..value.len - 1] + if is_raw || !inner.contains('\\') { + return inner + } + mut out := strings.new_builder(inner.len) + mut i := 0 + for i < inner.len { + if inner[i] != `\\` || i + 1 >= inner.len { + out.write_u8(inner[i]) + i++ + continue + } + next := inner[i + 1] + hex_len := match next { + `x` { 2 } + `u` { 4 } + `U` { 8 } + else { 0 } + } + + if hex_len > 0 && i + 2 + hex_len <= inner.len { + if code := json_enum_attr_hex(inner, i + 2, hex_len) { + if next == `x` { + out.write_u8(u8(code)) + } else { + out.write_rune(rune(code)) + } + i += 2 + hex_len + continue + } + } + match next { + `n` { + out.write_u8(`\n`) + } + `t` { + out.write_u8(`\t`) + } + `r` { + out.write_u8(`\r`) + } + `\\` { + out.write_u8(`\\`) + } + `'` { + out.write_u8(`'`) + } + `"` { + out.write_u8(`"`) + } + `$` { + out.write_u8(`$`) + } + `0` { + out.write_u8(0) + } + `a` { + out.write_u8(7) + } + `b` { + out.write_u8(8) + } + `f` { + out.write_u8(12) + } + `v` { + out.write_u8(11) + } + else { + out.write_u8(`\\`) + out.write_u8(next) + } + } + + i += 2 + } + return out.str() +} + +fn json_enum_attr_hex(value string, start int, count int) ?u32 { + mut code := u32(0) + for i in 0 .. count { + ch := value[start + i] + digit := if ch >= `0` && ch <= `9` { + int(ch - `0`) + } else if ch >= `a` && ch <= `f` { + int(ch - `a`) + 10 + } else if ch >= `A` && ch <= `F` { + int(ch - `A`) + 10 + } else { + return none + } + code = (code << 4) | u32(digit) + } + return code +} + +fn (g &FlatGen) is_veb_json_result_call(fn_node flat.Node) bool { + if fn_node.kind == .ident { + if fn_node.value in ['veb.Context.json', 'veb.Context.json_pretty'] { + return true + } + if fn_node.value !in ['Context.json', 'Context.json_pretty'] { + return false + } + if fn_node.value in g.tc.fn_param_types || fn_node.value in g.tc.fn_ret_types { + return false + } + return g.type_name_known_in_current_module('Context') + && g.type_embeds_veb_context(g.tc.parse_type('Context')) + } + if fn_node.kind != .selector || fn_node.children_count == 0 { + return false + } + if fn_node.value !in ['json', 'json_pretty'] { + return false + } + receiver_id := g.a.child(fn_node, 0) + receiver_type := types.unwrap_pointer(g.tc.resolve_type(receiver_id)) + if receiver_type is types.Struct { + receiver_name := receiver_type.name + if receiver_name != 'veb.Context' { + method_name := '${receiver_name}.${fn_node.value}' + if method_name in g.tc.fn_param_types || method_name in g.tc.fn_ret_types { + return false + } + } + } + if embedded_method := g.embedded_method_name_for_type(receiver_type, fn_node.value) { + return embedded_method in ['veb.Context.json', 'veb.Context.json_pretty'] + } + return g.is_veb_context_receiver(receiver_id) +} + +fn (g &FlatGen) is_veb_context_receiver(id flat.NodeId) bool { + typ := g.tc.resolve_type(id) + return g.type_embeds_veb_context(typ) +} + +fn (g &FlatGen) type_embeds_veb_context(typ types.Type) bool { + clean := types.unwrap_pointer(typ) + if clean is types.Alias { + return g.type_embeds_veb_context(clean.base_type) + } + if clean !is types.Struct { + return false + } + struct_name := types.Type(clean).name() + if struct_name == 'veb.Context' { + return true + } + fields := g.struct_fields_for_type(struct_name) or { return false } + for field in fields { + embedded_type_name := g.embedded_field_type_name(field) + if embedded_type_name == 'veb.Context' { + return true + } + if embedded_type_name.len > 0 + && g.type_embeds_veb_context(g.tc.parse_type(embedded_type_name)) { + return true + } + } + return false +} + +fn (g &FlatGen) is_missing_middleware_use_call(fn_node flat.Node) bool { + if fn_node.kind == .ident { + if !fn_node.value.ends_with('.use') { + return false + } + if fn_node.value in g.tc.fn_param_types || fn_node.value in g.tc.fn_ret_types { + return false + } + receiver := fn_node.value.all_before_last('.') + return g.struct_has_middleware_receiver(receiver) + } + if fn_node.kind != .selector || fn_node.value != 'use' || fn_node.children_count == 0 { + return false + } + base_type := g.tc.resolve_type(g.a.child(fn_node, 0)) + clean_type := types.unwrap_pointer(base_type) + if clean_type !is types.Struct { + return false + } + clean_name := types.Type(clean_type).name() + method_name := '${clean_name}.use' + if method_name in g.tc.fn_param_types || method_name in g.tc.fn_ret_types { + return false + } + return g.struct_has_middleware_receiver(clean_name) +} + +fn (g &FlatGen) struct_has_middleware_receiver(type_name string) bool { + if is_middleware_type_name(type_name) { + return true + } + fields := g.struct_fields_for_type(type_name) or { return false } + for field in fields { + embedded_type_name := g.embedded_field_type_name(field) + if is_middleware_type_name(embedded_type_name) { + return true + } + } + return false +} + +fn is_middleware_type_name(name string) bool { + base := if name.contains('[') { name.all_before('[') } else { name } + return base == 'veb.Middleware' +} + +fn (g &FlatGen) call_default_return_type(id flat.NodeId) types.Type { + if g.expected_expr_type is types.OptionType || g.expected_expr_type is types.ResultType { + return g.expected_expr_type + } + if g.expected_expr_type is types.Struct && g.expected_expr_type.name.starts_with('Optional') { + return g.expected_expr_type + } + if g.expected_expr_type is types.Pointer { + return g.expected_expr_type + } + if g.expected_expr_type is types.String { + return g.expected_expr_type + } + if typ := g.tc.expr_type(id) { + if typ !is types.Unknown && typ !is types.Void { + return typ + } + } + if int(id) >= 0 && int(id) < g.a.nodes.len { + node := g.a.nodes[int(id)] + if node.typ.len > 0 && node.typ != 'unknown' { + typ := g.parse_node_type(&node) + if typ !is types.Unknown && typ !is types.Void { + return typ + } + } + } + return g.tc.resolve_type(id) +} + +fn (g &FlatGen) json_decode_result_type_for_call(node flat.Node) ?types.Type { + if node.typ.len > 0 { + ret_type := g.parse_node_type(&node) + if ret_type is types.ResultType { + return ret_type + } + } + if node.children_count == 0 { + return none + } + if ret_type := g.json_decode_result_type(g.a.child(&node, 0)) { + return ret_type + } + if node.children_count < 2 { + return none + } + type_name := g.json_decode_type_arg_name(g.a.child(&node, 1)) + if type_name.len == 0 { + return none + } + return types.Type(types.ResultType{ + base_type: g.tc.parse_type(type_name) + }) +} + +fn (g &FlatGen) json_decode_result_type(callee_id flat.NodeId) ?types.Type { + if int(callee_id) < 0 || int(callee_id) >= g.a.nodes.len { + return none + } + callee := g.a.nodes[int(callee_id)] + if callee.kind != .index || callee.children_count < 2 { + return none + } + arg := g.a.child_node(&callee, 1) + mut type_name := '' + if arg.typ.len > 0 { + type_name = arg.typ + } else if arg.kind == .array_init && arg.value.len > 0 { + type_name = '[]${arg.value}' + } else if arg.value.len > 0 { + type_name = arg.value + } + if type_name.len == 0 { + return none + } + return types.Type(types.ResultType{ + base_type: g.tc.parse_type(type_name) + }) +} + +fn (g &FlatGen) json_decode_type_arg_name(id flat.NodeId) string { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return '' + } + node := g.a.nodes[int(id)] + match node.kind { + .ident { + return node.value + } + .selector { + if node.children_count == 0 { + return node.value + } + base := g.json_decode_type_arg_name(g.a.child(&node, 0)) + if base.len == 0 { + return node.value + } + return '${base}.${node.value}' + } + .index { + if node.children_count < 2 || node.value == 'range' { + return '' + } + base := g.json_decode_type_arg_name(g.a.child(&node, 0)) + if base.len == 0 { + return '' + } + mut args := []string{} + for i in 1 .. node.children_count { + arg := g.json_decode_type_arg_name(g.a.child(&node, i)) + if arg.len == 0 { + return '' + } + args << arg + } + return '${base}[${args.join(', ')}]' + } + .array_init { + if node.value.len > 0 { + return '[]${node.value}' + } + return '' + } + .map_init { + return node.value + } + .struct_decl { + return node.value + } + .prefix { + if node.children_count == 0 { + return '' + } + child := g.json_decode_type_arg_name(g.a.child(&node, 0)) + if child.len == 0 { + return '' + } + if node.op == .amp { + return '&${child}' + } + return child + } + else { + return '' + } + } +} + +fn (g &FlatGen) embedded_method_name_for_type(base_type types.Type, method string) ?string { + type_name := g.type_lookup_name(base_type) + if type_name.len == 0 || method.len == 0 { + return none + } + return g.embedded_method_name_for_struct(type_name, method) +} + +fn (g &FlatGen) embedded_method_name_for_struct(type_name string, method string) ?string { + fields := g.struct_fields_for_type(type_name) or { return none } + for field in fields { + embedded_type_name := g.embedded_field_type_name(field) + if embedded_type_name.len == 0 { + continue + } + method_name := '${embedded_type_name}.${method}' + if method_name in g.tc.fn_param_types { + return method_name + } + if found := g.embedded_method_name_for_struct(embedded_type_name, method) { + return found + } + } + return none +} + +fn (mut g FlatGen) gen_embedded_method_receiver(base_id flat.NodeId, base_type types.Type, expected_type types.Type, wants_ptr bool) bool { + path := g.embedded_receiver_path_for_expected(base_type, expected_type) or { return false } + target_is_ptr := path[path.len - 1].typ is types.Pointer + if wants_ptr && !target_is_ptr { + g.write('&') + } else if !wants_ptr && target_is_ptr { + g.write('*') + } + needs_paren := g.a.nodes[int(base_id)].kind !in [.ident, .selector] + if needs_paren { + g.write('(') + } + g.gen_expr(base_id) + if needs_paren { + g.write(')') + } + mut access_is_ptr := base_type is types.Pointer + for field in path { + op := if access_is_ptr { '->' } else { '.' } + g.write('${op}${c_field_name(field.name)}') + access_is_ptr = field.typ is types.Pointer + } + return true +} + +fn (mut g FlatGen) gen_embedded_named_method_receiver(base_id flat.NodeId, base_type types.Type, method string, resolved_method_name string, emitted_callee_name string, wants_ptr bool) bool { + embedded_name := g.embedded_method_name_for_type(base_type, method) or { return false } + if embedded_name != resolved_method_name + && (emitted_callee_name.len == 0 || g.cname(embedded_name) != emitted_callee_name) { + return false + } + params := g.tc.fn_param_types[embedded_name] or { return false } + if params.len == 0 { + return false + } + return g.gen_embedded_method_receiver(base_id, base_type, params[0], wants_ptr) +} + +fn (mut g FlatGen) gen_embedded_interface_receiver(base_id flat.NodeId, base_type types.Type, expected_type types.Type, wants_ptr bool) bool { + if wants_ptr { + return false + } + source_iface := g.interface_receiver_name(base_type) + target_iface := g.interface_receiver_name(expected_type) + if source_iface.len == 0 || target_iface.len == 0 || source_iface == target_iface { + return false + } + if !g.tc.interface_implements_interface(source_iface, target_iface) { + return false + } + mappings := g.interface_receiver_type_id_mappings(source_iface, target_iface) + if mappings.len == 0 { + return false + } + target_ct := g.tc.c_type(types.unwrap_pointer(expected_type)) + base_is_ptr := cgen_type_is_pointer_like(base_type) + if !g.interface_receiver_base_expr_is_reusable(base_id) { + source_ct := g.tc.c_type(base_type) + tmp := g.tmp_name() + g.write('({ ${source_ct} ${tmp} = ') + g.gen_expr(base_id) + g.write('; ') + g.gen_embedded_interface_receiver_from_expr(tmp, base_is_ptr, target_ct, target_iface, + mappings) + g.write('; })') + return true + } + op := if base_is_ptr { '->' } else { '.' } + g.write('(${target_ct}){._typ = ') + for mapping in mappings { + g.write('(') + g.gen_interface_receiver_base_expr(base_id, base_is_ptr) + g.write('${op}_typ == ${mapping.source_id} ? ${mapping.target_id} : ') + } + g.write('0') + for _ in mappings { + g.write(')') + } + g.write(', ._object = ') + g.gen_interface_receiver_base_expr(base_id, base_is_ptr) + g.write('${op}_object') + for field in g.tc.interface_fields[target_iface] or { []types.StructField{} } { + if interface_field_type_contains_self_by_value(field.typ, target_iface) { + continue + } + field_ct := g.tc.c_type(field.typ) + g.write(', .${g.cname(field.name)} = ') + for mapping in mappings { + impl_ct := g.tc.c_type(g.tc.parse_type(mapping.impl)) + g.write('(') + g.gen_interface_receiver_base_expr(base_id, base_is_ptr) + g.write('${op}_typ == ${mapping.source_id} ? ((${impl_ct}*)') + g.gen_interface_receiver_base_expr(base_id, base_is_ptr) + g.write('${op}_object)${g.interface_impl_field_access_suffix(mapping.impl, field.name)} : ') + } + g.write('(${field_ct}){0}') + for _ in mappings { + g.write(')') + } + } + g.write('}') + return true +} + +fn (g &FlatGen) interface_receiver_base_expr_is_reusable(base_id flat.NodeId) bool { + if int(base_id) < 0 || int(base_id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(base_id)] + if node.kind == .ident { + return true + } + if node.kind != .selector || node.children_count == 0 { + return false + } + return g.interface_receiver_base_expr_is_reusable(g.a.child(&node, 0)) +} + +fn (mut g FlatGen) gen_embedded_interface_receiver_from_expr(base_expr string, base_is_ptr bool, target_ct string, target_iface string, mappings []InterfaceReceiverIdMapping) { + op := if base_is_ptr { '->' } else { '.' } + g.write('(${target_ct}){._typ = ') + for mapping in mappings { + g.write('(${base_expr}${op}_typ == ${mapping.source_id} ? ${mapping.target_id} : ') + } + g.write('0') + for _ in mappings { + g.write(')') + } + g.write(', ._object = ${base_expr}${op}_object') + for field in g.tc.interface_fields[target_iface] or { []types.StructField{} } { + if interface_field_type_contains_self_by_value(field.typ, target_iface) { + continue + } + field_ct := g.tc.c_type(field.typ) + g.write(', .${g.cname(field.name)} = ') + for mapping in mappings { + impl_ct := g.tc.c_type(g.tc.parse_type(mapping.impl)) + field_suffix := g.interface_impl_field_access_suffix(mapping.impl, field.name) + g.write('(${base_expr}${op}_typ == ${mapping.source_id} ? ((${impl_ct}*)${base_expr}${op}_object)${field_suffix} : ') + } + g.write('(${field_ct}){0}') + for _ in mappings { + g.write(')') + } + } + g.write('}') +} + +fn (g &FlatGen) interface_impl_field_access_suffix(impl_name string, field_name string) string { + if g.direct_struct_field_exists(impl_name, field_name) { + return '->${g.cname(field_name)}' + } + if suffix := g.struct_promoted_field_suffix(impl_name, field_name, true) { + return suffix + } + return '->${g.cname(field_name)}' +} + +fn (g &FlatGen) interface_impl_field_access_expr(object_expr string, impl_name string, field_name string) string { + impl_ct := g.tc.c_type(g.tc.parse_type(impl_name)) + return '((${impl_ct}*)${object_expr})${g.interface_impl_field_access_suffix(impl_name, + field_name)}' +} + +struct InterfaceReceiverIdMapping { + source_id int + target_id int + impl string +} + +fn (g &FlatGen) interface_receiver_type_id_mappings(source_iface string, target_iface string) []InterfaceReceiverIdMapping { + mut mappings := []InterfaceReceiverIdMapping{} + for impl in g.iface_impls[source_iface] or { []string{} } { + source_id := g.iface_type_id(source_iface, impl) + target_id := g.iface_type_id(target_iface, impl) + if source_id == 0 || target_id == 0 { + continue + } + mappings << InterfaceReceiverIdMapping{ + source_id: source_id + target_id: target_id + impl: impl + } + } + return mappings +} + +fn (g &FlatGen) interface_receiver_name(typ types.Type) string { + clean_type := types.unwrap_pointer(typ) + if clean_type !is types.Interface && clean_type !is types.Struct && clean_type !is types.Alias { + return '' + } + mut name := g.type_lookup_name(clean_type) + if name.len == 0 { + return '' + } + base, _, is_generic := g.shared_generic_app_parts(name) + if is_generic { + name = base + } + if !isnil(g.interface_receiver_cache) { + mut cache := g.interface_receiver_cache + if cached := cache.get(name) { + return cached + } + } + mut result := '' + if name in g.tc.interface_names { + result = name + } else { + metadata_name := g.tc.interface_metadata_name(name) + if metadata_name in g.tc.interface_names { + result = metadata_name + } + } + if !isnil(g.interface_receiver_cache) { + mut cache := g.interface_receiver_cache + cache.put(name, result) + } + return result +} + +fn (mut g FlatGen) gen_interface_receiver_base_expr(base_id flat.NodeId, is_ptr bool) { + needs_paren := !is_ptr && g.a.nodes[int(base_id)].kind != .ident + if needs_paren { + g.write('(') + } + g.gen_expr(base_id) + if needs_paren { + g.write(')') + } +} + +fn (g &FlatGen) embedded_receiver_field_for_expected(base_type types.Type, expected_type types.Type) ?types.StructField { + path := g.embedded_receiver_path_for_expected(base_type, expected_type) or { return none } + if path.len == 0 { + return none + } + return path[0] +} + +fn (g &FlatGen) embedded_receiver_path_for_expected(base_type types.Type, expected_type types.Type) ?[]types.StructField { + base_name := g.type_lookup_name(base_type) + expected_name := g.embedded_receiver_expected_name(expected_type) + if base_name.len == 0 || expected_name.len == 0 { + return none + } + mut seen := map[string]bool{} + return g.embedded_receiver_path_for_expected_name(base_name, expected_name, mut seen) +} + +fn (g &FlatGen) embedded_receiver_expected_name(expected_type types.Type) string { + clean_type := types.unwrap_pointer(expected_type) + if clean_type is types.Alias { + return g.struct_init_import_alias_type_name(clean_type.name) + } + return g.type_lookup_name(expected_type) +} + +fn (g &FlatGen) embedded_receiver_path_for_expected_name(base_name string, expected_name string, mut seen map[string]bool) ?[]types.StructField { + if seen[base_name] { + return none + } + seen[base_name] = true + for field in g.struct_embedded_fields(base_name) { + embedded_type_name := g.embedded_field_type_name(field) + if g.embedded_receiver_type_names_match(embedded_type_name, expected_name) { + return [field] + } + if nested := g.embedded_receiver_path_for_expected_name(embedded_type_name, expected_name, mut + seen) + { + mut path := [field] + path << nested + return path + } + } + return none +} + +fn (g &FlatGen) embedded_receiver_type_names_match(actual string, expected string) bool { + if actual == expected { + return true + } + if actual.contains('.') && expected.contains('.') { + return false + } + qualified := if actual.contains('.') { actual } else { expected } + bare := if actual.contains('.') { expected } else { actual } + return qualified.all_before_last('.') == g.tc.cur_module + && qualified.all_after_last('.') == bare +} + +// current_param_type returns current param type data for FlatGen. +fn (g &FlatGen) current_param_type(name string) ?types.Type { + if g.cur_param_types.len == 0 { + return none + } + typ := g.cur_param_types[name] or { return none } + if g.cur_mut_params.len > 0 && g.current_mut_param_binding_is_shadowed(name) { + return none + } + return typ +} + +fn (g &FlatGen) current_param_map_type(name string) ?types.Type { + if g.cur_param_types.len == 0 { + return none + } + typ := g.cur_param_types[name] or { return none } + if g.cur_mut_params.len > 0 && g.current_mut_param_binding_is_shadowed(name) { + return none + } + return typ +} + +fn (g &FlatGen) call_uses_concrete_optional_params(name string) bool { + if name in g.concrete_optional_abi_fns { + return true + } + return g.name_uses_specialized_generic_abi(name) +} + +fn (g &FlatGen) name_uses_specialized_generic_abi(name string) bool { + if g.skip_generics { + return false + } + generic_base, generic_args, is_generic := parse_shared_generic_app_parts(name) + if is_generic && !generic_base.ends_with('.') && generic_args.len > 0 && generic_args[0].len > 0 + && name.ends_with(']') { + return true + } + if _ := g.generic_receiver_method_call_info(name) { + return true + } + if name in g.tc.specialized_generic_fns { + return true + } + return false +} + +fn (g &FlatGen) call_callee_uses_specialized_generic_abi(callee_id flat.NodeId) bool { + if g.skip_generics { + return false + } + if int(callee_id) < 0 || int(callee_id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(callee_id)] + if node.kind == .index && node.children_count > 0 { + base_id := g.a.child(&node, 0) + base_name := g.call_target_name(base_id) + return g.name_uses_specialized_generic_abi(base_name) || g.generic_fn_base_known(base_name) + } + name := g.call_target_name(callee_id) + return g.name_uses_specialized_generic_abi(name) +} + +fn (g &FlatGen) generic_fn_base_known(base string) bool { + mut candidates := []string{cap: 8} + candidates << base + if base.starts_with('main.') { + candidates << base.all_after('main.') + } + if !base.contains('.') { + candidates << 'main.${base}' + } + if !base.contains('.') && g.tc.cur_module.len > 0 && g.tc.cur_module !in ['', 'main', 'builtin'] { + candidates << '${g.tc.cur_module}.${base}' + } + short := base.all_after_last('.') + if short != base { + candidates << short + } + if base.contains('__') { + dotted := base.replace('__', '.') + candidates << dotted + candidates << dotted.all_after_last('.') + if !dotted.contains('.') { + candidates << 'main.${dotted}' + } + } + for candidate in candidates { + if candidate.len == 0 { + continue + } + if candidate in g.tc.fn_generic_params || candidate in g.tc.fn_param_type_texts + || candidate in g.tc.fn_ret_type_texts { + return true + } + } + return false +} + +fn type_is_optional_result(t types.Type) bool { + clean := optional_result_unalias_type(t) + return clean is types.OptionType || clean is types.ResultType +} + +fn params_have_optional_result(params []types.Type) bool { + for param in params { + if type_is_optional_result(param) { + return true + } + } + return false +} + +fn (mut g FlatGen) precompute_concrete_optional_abi_fns() { + mut cur_module := '' + mut cur_file := '' + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + kind_id := node_kind_id(node) + if kind_id == 77 { + cur_file = node.value + g.tc.cur_file = cur_file + cur_module = '' + g.tc.cur_module = cur_module + continue + } + if kind_id == 73 { + cur_module = node.value + g.tc.cur_file = cur_file + g.tc.cur_module = cur_module + continue + } + if kind_id != 61 + || (!g.a.specialized_fn_nodes[node_idx] && !g.is_specialized_generic_fn_node(node)) { + continue + } + params := g.fn_node_param_types_or_decl(node, cur_module) + ret_type := g.fn_node_return_type(node, cur_module) + if !params_have_optional_result(params) && !type_is_optional_result(ret_type) { + continue + } + g.register_concrete_optional_abi_fn(cur_module, node.value) + for param in params { + if type_is_optional_result(param) { + g.concrete_optional_type_name(param) + } + } + if type_is_optional_result(ret_type) { + g.concrete_optional_type_name(ret_type) + } + } +} + +fn (mut g FlatGen) fn_node_param_types_or_decl(node flat.Node, module_name string) []types.Type { + params := g.fn_node_param_types(node, module_name) + if params.len > 0 { + return params + } + mut result := []types.Type{} + for i in 0 .. node.children_count { + child := g.a.child_node(&node, i) + if child.kind != .param { + if g.prefix_param_scan { + break + } + continue + } + result << g.tc.parse_resolution_type(child.typ) + } + return result +} + +fn (mut g FlatGen) register_concrete_optional_abi_fn(module_name string, name string) { + for candidate in concrete_optional_abi_fn_name_candidates(module_name, name, g.fn_c_name_in_module(module_name, + name)) { + if candidate.len > 0 { + g.concrete_optional_abi_fns[candidate] = true + } + } +} + +fn concrete_optional_abi_fn_name_candidates(module_name string, name string, emitted string) []string { + qname := qualify_name_in_module(module_name, name) + mut candidates := []string{cap: 8} + candidates << name + candidates << c_name(name) + candidates << qname + candidates << c_name(qname) + candidates << emitted + mut deduped := []string{cap: candidates.len} + for candidate in candidates { + if candidate.len > 0 && candidate !in deduped { + deduped << candidate + } + } + return deduped +} + +fn (g &FlatGen) concrete_optional_param_type_for_expr(id flat.NodeId) ?types.Type { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return none + } + node := g.a.nodes[int(id)] + if node.kind == .paren && node.children_count > 0 { + return g.concrete_optional_param_type_for_expr(g.a.child(&node, 0)) + } + if node.kind != .ident || node.value.len == 0 { + return none + } + if !(g.cur_concrete_optional_params[node.value] or { false }) { + return none + } + param_type := g.current_param_type(node.value) or { return none } + if type_is_optional_result(param_type) { + return param_type + } + return none +} + +fn (g &FlatGen) optional_source_type_for_expr(id flat.NodeId, typ types.Type) types.Type { + if json_type := g.json_decode_call_expr_result_type(id) { + return json_type + } + if type_is_optional_result(typ) { + if param_type := g.concrete_optional_param_type_for_expr(id) { + return param_type + } + } + return typ +} + +fn (mut g FlatGen) optional_type_name_for_expr(id flat.NodeId, typ types.Type) string { + if json_type := g.json_decode_call_expr_result_type(id) { + return g.optional_type_name(json_type) + } + if param_type := g.concrete_optional_param_type_for_expr(id) { + return g.concrete_optional_type_name(param_type) + } + if int(id) >= 0 && int(id) < g.a.nodes.len { + node := g.a.nodes[int(id)] + if node.kind == .ident { + if local_ct := g.local_storage_c_type(node.value) { + if local_ct == 'Optional' || local_ct.starts_with('Optional_') { + return local_ct + } + } + } + if node.kind == .call && node.children_count > 0 { + mut concrete_optional_return := false + if resolved := g.tc.resolved_call_name(id) { + concrete_optional_return = g.call_uses_concrete_optional_params(resolved) + } + if !concrete_optional_return { + target := g.call_target_name(g.a.child(&node, 0)) + concrete_optional_return = g.call_uses_concrete_optional_params(target) + || g.call_uses_concrete_optional_params(g.normalize_call_key(target)) + || g.call_uses_concrete_optional_params(g.direct_call_name_for_call(id, target)) + } + if concrete_optional_return { + declared := g.declared_call_return_type(id) + if type_is_optional_result(declared) { + return g.concrete_optional_type_name(declared) + } + if type_is_optional_result(typ) { + return g.concrete_optional_type_name(typ) + } + } + if raw_return := g.call_declared_return_type_text(id, node) { + clean_return := trimmed_space(raw_return) + if clean_return.len > 1 && clean_return[0] in [`?`, `!`] { + if shared_ptr := g.shared_alias_pointer_type_from_text(clean_return[1..]) { + return g.optional_type_name(types.Type(types.OptionType{ + base_type: shared_ptr + })) + } + } + } + declared := g.declared_call_return_type(id) + if declared is types.OptionType || declared is types.ResultType { + return g.optional_type_name(declared) + } + } + } + return g.optional_type_name(typ) +} + +fn (g &FlatGen) call_declared_return_type_text(id flat.NodeId, node flat.Node) ?string { + mut candidates := []string{} + if resolved := g.tc.resolved_call_name(id) { + candidates << resolved + } + target := g.call_target_name(g.a.child(&node, 0)) + candidates << target + candidates << g.normalize_call_key(target) + candidates << g.cname(target) + for candidate in candidates { + if candidate.len == 0 { + continue + } + if ret := g.tc.fn_ret_type_texts[candidate] { + return ret + } + if !candidate.contains('.') { + if ret := g.tc.fn_ret_type_texts['main.${candidate}'] { + return ret + } + } + } + return none +} + +// current_param_is_mut returns true when a current param originated from `mut name T` +// and the identifier still resolves to that parameter (not a shadowing local). +fn (g &FlatGen) current_param_is_mut(name string) bool { + if g.cur_mut_params.len == 0 { + return false + } + if !(g.cur_mut_params[name] or { false }) { + return false + } + owner := g.cur_mut_param_owners[name] or { return false } + if g.tc == unsafe { nil } || g.tc.cur_scope == unsafe { nil } { + return false + } + if owner.belongs_to_scope(g.tc.cur_scope) { + return true + } + return g.tc.cur_scope.nearest_binding_owned_by(name, owner) +} + +fn (g &FlatGen) current_param_is_mut_pointer(name string) bool { + return g.current_param_is_mut(name) && (g.cur_mut_pointer_params[name] or { false }) +} + +fn (g &FlatGen) current_mut_param_binding_is_shadowed(name string) bool { + if g.cur_mut_params.len == 0 { + return false + } + if !(g.cur_mut_params[name] or { false }) { + return false + } + owner := g.cur_mut_param_owners[name] or { return false } + if g.tc == unsafe { nil } || g.tc.cur_scope == unsafe { nil } { + return false + } + if owner.belongs_to_scope(g.tc.cur_scope) { + return false + } + return !g.tc.cur_scope.nearest_binding_owned_by(name, owner) +} + +// gen_enum_str_call emits an explicit `enum_val.str()` (with no user-defined `str`) +// by routing to the compiler-synthesized `__autostr`, so it matches `${enum}` +// interpolation exactly — including `[flag]` enums' `Enum{.a | .b}` form, which the +// old inline single-value ternary chain could not render. +fn (mut g FlatGen) gen_enum_str_call(fn_node &flat.Node, enum_type types.Enum) { + mut name := enum_type.name + if name.starts_with('main.') { + name = name[5..] + } + g.write('${g.enum_autostr_c_name(name)}__autostr(') + g.gen_expr(g.a.child(fn_node, 0)) + g.write(')') +} + +// enum_receiver_method_name supports enum receiver method name handling for FlatGen. +fn (g &FlatGen) enum_receiver_method_name(enum_type types.Enum, method string) ?string { + name := enum_type.name + direct := '${name}.${method}' + if direct in g.tc.fn_param_types { + return direct + } + if name.contains('.') { + return none + } + for candidate, _ in g.tc.fn_param_types { + if candidate.ends_with('.${direct}') { + return candidate + } + } + return none +} + +// gen_fn_field_call emits fn field call output for c. +fn (mut g FlatGen) gen_fn_field_call(node flat.Node, fn_node &flat.Node, base_type types.Type) bool { + field_type := g.field_type(base_type, fn_node.value) or { return false } + fn_type := fn_type_from(field_type) or { return false } + field_is_ptr := fn_type_is_pointer(field_type) + base_id := g.a.child(fn_node, 0) + base := g.a.nodes[int(base_id)] + needs_paren := base.kind !in [.ident, .selector, .call] + if field_is_ptr { + g.write('(*') + } + if needs_paren { + g.write('(') + } + g.gen_expr(base_id) + if needs_paren { + g.write(')') + } + if base_type is types.Pointer { + g.write('->') + } else { + g.write('.') + } + g.write(g.cname(fn_node.value)) + if field_is_ptr { + g.write(')') + } + g.write('(') + for i in 1 .. node.children_count { + if i > 1 { + g.write(', ') + } + arg_id := g.a.child(&node, i) + arg_idx := i - 1 + if arg_idx < fn_type.params.len { + g.gen_arg_for_expected_type(arg_id, fn_type.params[arg_idx]) + } else { + g.gen_expr(arg_id) + } + } + g.write(')') + return true +} + +// call_key updates call key state for FlatGen. +fn (g &FlatGen) call_key(id flat.NodeId, name string) string { + if name.contains('.') { + normalized := g.normalize_call_key(name) + if normalized in g.tc.fn_param_types || normalized in g.tc.fn_ret_types { + return normalized + } + } + if resolved := g.tc.resolved_call_name(id) { + if resolved_call_matches_target(resolved, name) { + return g.normalize_call_key(resolved) + } + } + return g.normalize_call_key(name) +} + +fn resolved_call_matches_target(resolved string, target string) bool { + if resolved.len == 0 || target.len == 0 { + return false + } + if resolved == target || c_name(resolved) == target || resolved == c_name(target) { + return true + } + resolved_short := c_short_name_view(resolved) + target_short := c_short_name_view(target) + return resolved_short == target_short || c_name(resolved_short) == target_short + || resolved_short == c_name(target_short) +} + +// normalize_call_key transforms normalize call key data for c. +fn (g &FlatGen) normalize_call_key(name string) string { + cache_key := '${g.tc.cur_module}\x01${g.tc.cur_file}\x01${name}' + if !isnil(g.normalize_call_cache) { + mut cache := g.normalize_call_cache + if cached := cache.get(cache_key) { + return cached + } + } + result := g.normalize_call_key_uncached(name) + if !isnil(g.normalize_call_cache) { + mut cache := g.normalize_call_cache + cache.put(cache_key, result) + } + return result +} + +fn (g &FlatGen) normalize_call_key_uncached(name string) string { + if name.starts_with('main.') { + short_name := name.all_after_last('.') + if short_name in g.tc.fn_param_types || short_name in g.tc.fn_ret_types { + return short_name + } + } + if !name.contains('.') && g.tc.cur_module.len > 0 && g.tc.cur_module != 'main' + && g.tc.cur_module != 'builtin' { + local := '${g.tc.cur_module}.${name}' + if local in g.tc.fn_param_types || local in g.tc.fn_ret_types { + return local + } + } + // A selected import belongs to the current source file and must win over a + // same-spelled short signature retained from an imported module. The checker + // can omit per-node resolution data when the program unit is emitted from the + // module cache, so recover the file-local declaration before accepting `name`. + if imported := g.selective_import_call_key_in_file(name, g.tc.cur_file) { + return imported + } + if name in g.tc.fn_param_types || name in g.tc.fn_ret_types { + return name + } + if imported := g.selective_import_call_key(name) { + return imported + } + qname := g.tc.qualify_fn_name(name) + if qname in g.tc.fn_param_types || qname in g.tc.fn_ret_types { + return qname + } + for _, mod_name in g.tc.imports { + imported := '${mod_name}.${name}' + if imported in g.tc.fn_param_types || imported in g.tc.fn_ret_types { + return imported + } + } + return qname +} + +fn (g &FlatGen) selective_import_call_key(name string) ?string { + if name.contains('.') { + return none + } + if imported := g.selective_import_call_key_in_file(name, g.tc.cur_file) { + return imported + } + mut resolved := []string{} + suffix := '\n${name}' + for key, candidates in g.tc.file_selective_imports { + if !key.ends_with(suffix) { + continue + } + for candidate in candidates { + if (candidate in g.tc.fn_param_types || candidate in g.tc.fn_ret_types) + && candidate !in resolved { + resolved << candidate + } + } + } + if resolved.len == 1 { + return resolved[0] + } + return none +} + +fn (g &FlatGen) selective_import_call_key_in_file(name string, file string) ?string { + if name.contains('.') || file.len == 0 { + return none + } + mut resolved := '' + for candidate in g.tc.file_selective_imports['${file}\n${name}'] or { return none } { + if candidate !in g.tc.fn_param_types && candidate !in g.tc.fn_ret_types { + continue + } + if resolved.len > 0 && resolved != candidate { + return none + } + resolved = candidate + } + if resolved.len > 0 { + return resolved + } + return none +} + +// param_types_for supports param types for handling for FlatGen. +// param_types_for resolves the parameter types of a called function. It is invoked once +// per call site during codegen, and the slow path below scans every known function, so +// results are memoized: without this, generic/monomorphized call names that miss the +// direct lookups re-scan (and copy) the whole function table on every call (O(n^2)). +// fn_decl_is_variadic resolves the variadic flag for a call target using the +// same key candidates as param_types_for, so import-alias and C-name call +// sites (e.g. `http.new_header` for module `net.http`) resolve consistently. +fn (g &FlatGen) fn_decl_is_variadic(name string, fallback string) bool { + if name.contains('__') { + dotted_name := name.replace('__', '.') + if v := g.fn_decl_variadic_entry(dotted_name) { + return v + } + if v := g.import_resolved_fn_decl_variadic(dotted_name) { + return v + } + if v := g.unique_short_fn_decl_variadic(dotted_name) { + return v + } + } + for candidate in [name, fallback] { + if !candidate.contains('.') && !candidate.contains('__') { + if v := g.local_or_unique_short_fn_decl_variadic(candidate) { + return v + } + continue + } + if v := g.import_resolved_fn_decl_variadic(candidate) { + return v + } + if v := g.fn_decl_variadic_entry(candidate) { + return v + } + if candidate.starts_with('main.') { + if v := g.unique_short_fn_decl_variadic(candidate) { + return v + } + } + } + if name.contains('.') { + if v := g.unique_short_fn_decl_variadic(name) { + return v + } + } + return false +} + +fn (g &FlatGen) fn_decl_variadic_entry(name string) ?bool { + if value := g.fn_decl_variadic[name] { + return value + } + if name in g.fn_decl_param_types { + return false + } + return none +} + +fn (g &FlatGen) import_resolved_fn_decl_variadic(name string) ?bool { + if !name.contains('.') { + return none + } + alias := name.all_before('.') + module_name := if g.tc != unsafe { nil } { + if g.tc.cur_file.len == 0 { + return none + } + g.tc.file_imports['${g.tc.cur_file}\n${alias}'] or { return none } + } else { + g.import_alias_module(alias) or { return none } + } + resolved_name := '${module_name}.${name.all_after('.')}' + if v := g.fn_decl_variadic_entry(resolved_name) { + return v + } + return none +} + +fn (g &FlatGen) local_or_unique_short_fn_decl_variadic(name string) ?bool { + if g.tc != unsafe { nil } { + module_key := fn_decl_module_key(g.tc.cur_module, name) + if v := g.fn_decl_variadic_entry(module_key) { + return v + } + } + return g.unique_short_fn_decl_variadic(name) +} + +fn (g &FlatGen) unique_short_fn_decl_variadic(name string) ?bool { + short_name := name.all_after_last('.') + if g.fn_decl_variadic_short_counts[short_name] != 1 { + return none + } + if v := g.fn_decl_variadic_entry(short_name) { + return v + } + return none +} + +fn (mut g FlatGen) param_types_for(name string, fallback string) []types.Type { + cache_key := if name == fallback { name } else { '${name}\x01${fallback}' } + if cached := g.param_types_cache[cache_key] { + return cached + } + result := g.param_types_for_uncached(name, fallback) + g.param_types_cache[cache_key] = result + return result +} + +fn (mut g FlatGen) param_types_for_uncached(name string, fallback string) []types.Type { + if name == 'Array_string__join' || fallback == 'Array_string__join' { + if params := g.tc.fn_param_types['[]string.join'] { + return params + } + } + if name.contains('__') { + exact_decl_types := g.fn_decl_param_types[name] or { []types.Type{} } + if params := g.tc.fn_param_types[name] { + return g.merge_decl_pointer_param_abi(params, exact_decl_types) + } + if exact_decl_types.len > 0 { + return exact_decl_types + } + dotted_name := name.replace('__', '.') + dotted_decl_types := g.param_types_from_decl(dotted_name, dotted_name) + for candidate in [dotted_name, dotted_name.all_after_last('.')] { + if params := g.tc.fn_param_types[candidate] { + return g.merge_decl_pointer_param_abi(params, dotted_decl_types) + } + } + if dotted_decl_types.len > 0 { + return dotted_decl_types + } + } + decl_types := g.param_types_from_decl(name, fallback) + for candidate in [name, fallback] { + if params := g.tc.fn_param_types[candidate] { + return g.merge_decl_pointer_param_abi(params, decl_types) + } + if candidate.starts_with('main.') { + short_name := candidate.all_after_last('.') + if params := g.tc.fn_param_types[short_name] { + return g.merge_decl_pointer_param_abi(params, decl_types) + } + } + } + if decl_types.len > 0 { + return decl_types + } + if name.contains('__') { + short_name := name.all_after_last('__') + if params := g.param_types_by_short[short_name] { + return g.merge_decl_pointer_param_abi(params, decl_types) + } + } + if generic_params := g.generic_receiver_method_param_types(name) { + return generic_params + } + if interface_types := g.interface_method_param_types(name) { + return interface_types + } + if name.contains('.') { + // O(1) lookup via the precomputed short-name index instead of scanning the whole + // function table on every (cache-missing) call — this fallback ran ~3000× and was + // a top cgen self-time cost (each scan is O(functions)). + short_name := name.all_after_last('.') + if params := g.param_types_by_short[short_name] { + return params + } + } + return []types.Type{} +} + +fn (g &FlatGen) merge_decl_pointer_param_abi(params []types.Type, decl_types []types.Type) []types.Type { + if params.len == 0 { + return params + } + if params.len != decl_types.len { + return if decl_types.len > params.len { decl_types } else { params } + } + if !g.lazy_param_abi_merge { + mut merged := params.clone() + mut changed := false + for i, decl_type in decl_types { + if decl_type is types.Unknown || decl_type is types.Void { + continue + } + if decl_type.name() != merged[i].name() { + merged[i] = decl_type + changed = true + } + } + return if changed { merged } else { params } + } + for i, decl_type in decl_types { + if decl_type is types.Unknown || decl_type is types.Void { + continue + } + if decl_type.name() != params[i].name() { + mut merged := params.clone() + merged[i] = decl_type + for j := i + 1; j < decl_types.len; j++ { + later := decl_types[j] + if later is types.Unknown || later is types.Void { + continue + } + if later.name() != merged[j].name() { + merged[j] = later + } + } + return merged + } + } + return params +} + +fn (g &FlatGen) generic_receiver_method_param_types(name string) ?[]types.Type { + if !name.contains('.') || !name.contains('[') || !name.contains(']') { + return none + } + receiver := name.all_before_last('.') + method := name.all_after_last('.') + info := g.tc.resolve_generic_struct_method(receiver, method) or { return none } + return info.params.clone() +} + +// precompute_param_type_index builds short-name -> param-types, preserving the fallback's +// priority (fn_decl_param_types first, then the checker's fn_param_types; first match wins). +fn (mut g FlatGen) precompute_param_type_index() { + for name, params in g.fn_decl_param_types { + if name.contains('.') { + short := name.all_after_last('.') + if short !in g.param_types_by_short { + g.param_types_by_short[short] = params + } + } + } + for name, params in g.tc.fn_param_types { + if name.contains('.') { + short := name.all_after_last('.') + if short !in g.param_types_by_short { + g.param_types_by_short[short] = params + } + } + } +} + +fn (g &FlatGen) interface_method_param_types(name string) ?[]types.Type { + if !name.contains('.') { + return none + } + iface_name := name.all_before_last('.') + if iface_name !in g.interfaces { + return none + } + method := name.all_after_last('.') + if iface_name == 'IError' && method == 'str' { + return none + } + decl_key := g.interface_method_signature_key(iface_name, method) or { return none } + decl_params := g.tc.fn_param_types[decl_key] or { return none } + mut params := []types.Type{cap: decl_params.len} + params << types.Type(types.Pointer{ + base_type: types.Type(types.Interface{ + name: iface_name + }) + }) + if decl_params.len > 1 { + for i in 1 .. decl_params.len { + params << decl_params[i] + } + } + return params +} + +// param_types_from_decl converts param types from decl data for c. +fn (mut g FlatGen) param_types_from_decl(name string, fallback string) []types.Type { + if name.contains('.') { + if ptypes := g.fn_decl_param_types[name] { + return ptypes + } + if ptypes := g.tc.fn_param_types[name] { + return ptypes + } + } else { + for candidate in [fallback, name] { + if ptypes := g.fn_decl_param_types[fn_decl_module_key(g.tc.cur_module, candidate)] { + return ptypes + } + if ptypes := g.tc.fn_param_types[fn_decl_module_key(g.tc.cur_module, candidate)] { + return ptypes + } + } + for candidate in [fallback, name] { + if ptypes := g.fn_decl_param_types[candidate] { + return ptypes + } + if ptypes := g.tc.fn_param_types[candidate] { + return ptypes + } + } + } + return []types.Type{} +} + +// short_receiver_method_name supports short receiver method name handling for c. +fn short_receiver_method_name(name string) string { + if !name.contains('.') { + return '' + } + receiver := name.all_before_last('.') + if !receiver.contains('.') { + return '' + } + return '${receiver.all_after_last('.')}.${name.all_after_last('.')}' +} + +// gen_arg_for_expected_type emits arg for expected type output for c. +fn (mut g FlatGen) gen_arg_for_expected_type(arg_id flat.NodeId, expected types.Type) { + arg_node := g.a.nodes[int(arg_id)] + if g.gen_mut_sum_lvalue_arg(arg_id, expected) { + return + } + mut needs_addr := false + if expected is types.Pointer && !(arg_node.kind == .prefix && arg_node.op == .amp) + && !g.arg_is_null_pointer_literal(arg_id, arg_node) { + arg_type := g.usable_expr_type(arg_id) + value_local := arg_node.kind == .ident && !g.local_storage_is_pointer(arg_node.value) && (g.current_param_type(arg_node.value) or { + types.Type(types.void_) + }) !is types.Pointer && (g.global_type_for_ident(arg_node.value) or { + types.Type(types.void_) + }) !is types.Pointer + if arg_type !is types.Pointer || value_local { + needs_addr = true + } + } + if g.gen_interface_pointer_arg(arg_id, expected) { + return + } + if needs_addr { + if g.arg_is_const_ident(arg_node) { + ct := g.tc.c_type(types.unwrap_pointer(expected)) + g.write('(${ct}[]){') + g.gen_expr_with_expected_type(arg_id, types.unwrap_pointer(expected)) + g.write('}') + return + } + is_rvalue := arg_node.kind == .call + || (arg_node.kind == .index && arg_node.value == 'range') + if is_rvalue { + ct := g.tc.c_type(types.unwrap_pointer(expected)) + g.write('({${ct} _t${g.tmp_count} = ') + g.gen_expr_with_expected_type(arg_id, types.unwrap_pointer(expected)) + g.write('; &_t${g.tmp_count};})') + g.tmp_count++ + return + } + if g.gen_mut_sum_lvalue_arg(arg_id, expected) { + return + } + g.write('&') + } + if !needs_addr && g.gen_sum_variant_arg(arg_id, expected) { + return + } + if !needs_addr && g.gen_optional_arg(arg_id, expected) { + return + } + if !needs_addr && g.gen_interface_pointer_arg(arg_id, expected) { + return + } + g.gen_expr_with_expected_type(arg_id, expected) +} + +fn (mut g FlatGen) gen_interface_pointer_arg(arg_id flat.NodeId, expected types.Type) bool { + ptr_type := if expected is types.Pointer { expected } else { return false } + node := g.a.nodes[int(arg_id)] + if g.arg_is_null_pointer_literal(arg_id, node) { + return false + } + mut iface_type := ptr_type.base_type + if iface_type is types.Alias { + iface_type = iface_type.base_type + } + if iface_type !is types.Interface { + return false + } + mut actual := g.usable_expr_type(arg_id) + if node.kind == .ident { + if param_type := g.current_param_type(node.value) { + actual = param_type + } + } + actual_unaliased := cgen_unalias_type(actual) + if actual_unaliased is types.Nil || (actual_unaliased is types.Pointer + && cgen_unalias_type(actual_unaliased.base_type) is types.Void) { + g.write('(${g.tc.c_type(iface_type)}*)') + g.gen_expr(arg_id) + return true + } + actual_depth := cgen_type_pointer_depth(actual) + expected_depth := cgen_type_pointer_depth(expected) + if actual_depth > expected_depth { + actual_root := cgen_unalias_unwrap_all_pointers(actual) + expected_root := cgen_unalias_unwrap_all_pointers(expected) + if actual_root is types.Interface && expected_root is types.Interface + && g.tc.c_type(actual_root) == g.tc.c_type(expected_root) { + for _ in expected_depth .. actual_depth { + g.write('*') + } + g.gen_expr(arg_id) + return true + } + } + if actual is types.Pointer { + mut actual_base := actual.base_type + if actual_base is types.Alias { + actual_base = actual_base.base_type + } + if actual_base is types.Interface { + return false + } + } + mut actual_base := actual + if actual_base is types.Alias { + actual_base = actual_base.base_type + } + if actual_base is types.Interface && g.tc.c_type(actual_base) == g.tc.c_type(iface_type) + && g.expr_is_addressable(arg_id) { + g.write('&') + gen_expr_lvalue(mut g, arg_id) + return true + } + ct := g.tc.c_type(iface_type) + iface_value := g.interface_value_to_string(arg_id, iface_type) + if iface_value.len == 0 { + return false + } + g.write('&((${ct}[]){') + g.write(iface_value) + g.write('})[0]') + return true +} + +fn (mut g FlatGen) gen_callback_fn_value_for_expected_type(arg_id flat.NodeId, expected types.Type) bool { + return g.gen_callback_fn_value_for_expected_c_abi(arg_id, expected, '') +} + +fn (mut g FlatGen) gen_callback_fn_value_for_expected_c_abi(arg_id flat.NodeId, expected types.Type, expected_c_abi string) bool { + expected_fn := fn_type_from(expected) or { return false } + actual_name := g.callback_fn_value_name(arg_id, expected) or { + g.direct_callback_ident_name(arg_id) or { return false } + } + actual_fn := g.callback_fn_value_type(actual_name) or { return false } + wrapper := g.ensure_callback_userdata_wrapper(actual_name, actual_fn, expected_fn, + expected_c_abi) or { return false } + g.write(wrapper) + return true +} + +fn (mut g FlatGen) gen_callback_fn_value_for_field_c_abi(arg_id flat.NodeId, expected types.Type, expected_c_abi string) bool { + if expected_c_abi.len == 0 { + return false + } + if g.gen_callback_fn_value_for_expected_c_abi(arg_id, expected, expected_c_abi) { + return true + } + if call_name := g.callback_direct_fn_value_name_for_c_abi(arg_id, expected, expected_c_abi) { + g.write(g.callback_c_fn_name(call_name)) + return true + } + if _ := g.callback_fn_value_name(arg_id, expected) { + g.gen_expr(arg_id) + return true + } + return false +} + +fn (mut g FlatGen) callback_fn_value_name(id flat.NodeId, expected types.Type) ?string { + if name := g.tc.resolved_fn_value_name(id) { + return name + } + if int(id) < 0 || int(id) >= g.a.nodes.len { + return none + } + node := g.a.nodes[int(id)] + if node.kind in [.cast_expr, .paren, .expr_stmt] && node.children_count > 0 { + return g.callback_fn_value_name(g.a.child(&node, 0), expected) + } + return none +} + +fn (mut g FlatGen) callback_direct_fn_value_name(id flat.NodeId, expected types.Type) ?string { + return g.callback_direct_fn_value_name_for_c_abi(id, expected, '') +} + +fn (mut g FlatGen) callback_direct_fn_value_name_for_c_abi(id flat.NodeId, expected types.Type, expected_c_abi string) ?string { + expected_fn := fn_type_from(expected) or { return none } + actual_name := g.callback_fn_value_name(id, expected) or { + g.direct_callback_ident_name(id) or { return none } + } + actual_fn := g.callback_fn_value_type(actual_name) or { return none } + if !g.callback_fn_types_direct_compatible(actual_fn, expected_fn, expected_c_abi) { + return none + } + return actual_name +} + +fn (g &FlatGen) direct_callback_ident_name(id flat.NodeId) ?string { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return none + } + node := g.a.nodes[int(id)] + if node.kind in [.cast_expr, .paren, .expr_stmt] && node.children_count > 0 { + return g.direct_callback_ident_name(g.a.child(&node, 0)) + } + if node.kind == .selector && node.children_count > 0 { + base := g.a.child_node(&node, 0) + if base.kind == .ident { + looked_up := g.tc.cur_scope.lookup(base.value) or { types.Type(types.void_) } + if looked_up !is types.Void { + return none + } + name := '${base.value}.${node.value}' + if name in g.tc.fn_param_types && name in g.tc.fn_ret_types { + return name + } + qname := '${g.tc.cur_module}.${name}' + if qname in g.tc.fn_param_types && qname in g.tc.fn_ret_types { + return qname + } + } + } + if node.kind != .ident || node.value.len == 0 { + return none + } + looked_up := g.tc.cur_scope.lookup(node.value) or { types.Type(types.void_) } + if looked_up !is types.Void { + return none + } + call_key := g.call_key(id, node.value) + if call_key in g.tc.fn_param_types && call_key in g.tc.fn_ret_types { + return call_key + } + if node.value in g.tc.fn_param_types && node.value in g.tc.fn_ret_types { + return node.value + } + if call_key in g.fn_decl_param_types && call_key in g.fn_decl_ret_types { + return call_key + } + if node.value in g.fn_decl_param_types && node.value in g.fn_decl_ret_types { + return node.value + } + return none +} + +fn (mut g FlatGen) ident_fn_value_c_name(id flat.NodeId, node flat.Node) ?string { + if node.kind != .ident || node.value.len == 0 { + return none + } + looked_up := g.tc.cur_scope.lookup(node.value) or { types.Type(types.void_) } + if looked_up !is types.Void { + return none + } + call_key := g.call_key(id, node.value) + if call_key in g.tc.fn_param_types && call_key in g.tc.fn_ret_types { + return g.direct_call_name_for_call(id, call_key) + } + if node.value in g.tc.fn_param_types && node.value in g.tc.fn_ret_types { + return g.direct_call_name_for_call(id, node.value) + } + if call_key in g.fn_decl_param_types && call_key in g.fn_decl_ret_types { + return g.direct_call_name_for_call(id, call_key) + } + if node.value in g.fn_decl_param_types && node.value in g.fn_decl_ret_types { + return g.direct_call_name_for_call(id, node.value) + } + return none +} + +fn (mut g FlatGen) callback_fn_value_type(name string) ?types.FnType { + params := if p := g.tc.fn_param_types[name] { + p + } else if p := g.fn_decl_param_types[name] { + p + } else { + return none + } + ret := if r := g.tc.fn_ret_types[name] { + r + } else if r := g.fn_decl_ret_types[name] { + r + } else { + types.Type(types.void_) + } + return types.FnType{ + params: params.clone() + return_type: ret + } +} + +fn (mut g FlatGen) callback_fn_types_direct_compatible(actual types.FnType, expected types.FnType, expected_c_abi string) bool { + if actual.params.len != expected.params.len { + return false + } + if g.callback_c_type(actual.return_type) != g.callback_expected_return_c_type(expected.return_type, + expected_c_abi) { + return false + } + for i in 0 .. expected.params.len { + if g.callback_c_type(fn_type_param(actual, i)) != g.callback_expected_param_c_type(expected, + i, expected_c_abi) { + return false + } + } + return true +} + +fn (mut g FlatGen) ensure_callback_userdata_wrapper(actual_name string, actual types.FnType, expected types.FnType, expected_c_abi string) ?string { + if actual.params.len != expected.params.len { + return none + } + actual_ret_ct := g.callback_c_type(actual.return_type) + expected_ret_ct := g.callback_expected_return_c_type(expected.return_type, expected_c_abi) + if actual_ret_ct != expected_ret_ct { + return none + } + mut needs_wrapper := false + mut param_decls := []string{} + mut call_args := []string{} + for i in 0 .. expected.params.len { + expected_param := fn_type_param(expected, i) + actual_param := fn_type_param(actual, i) + expected_ct := g.callback_expected_param_c_type(expected, i, expected_c_abi) + actual_ct := g.callback_c_type(actual_param) + param_decls << '${expected_ct} arg${i}' + if actual_ct == expected_ct { + call_args << 'arg${i}' + continue + } + if g.callback_can_cast_userdata_param(actual_param, expected_param) { + call_args << '(${actual_ct})arg${i}' + needs_wrapper = true + continue + } + if callback_can_cast_const_abi_param(actual_ct, expected_ct) { + call_args << '(${actual_ct})arg${i}' + needs_wrapper = true + continue + } + return none + } + if !needs_wrapper { + return none + } + actual_c_name := g.callback_c_fn_name(actual_name) + expected_key := if expected_c_abi.len > 0 { + expected_c_abi + } else { + g.callback_fn_type_key(expected) + } + key := '${actual_c_name}|${g.callback_fn_type_key(actual)}|${expected_key}' + if name := g.callback_wrapper_names[key] { + return name + } + name := g.cname('${actual_c_name}_callback_adapter_${callback_stable_key_hash(key)}') + g.callback_wrapper_names[key] = name + params := if param_decls.len == 0 { 'void' } else { param_decls.join(', ') } + call := '${actual_c_name}(${call_args.join(', ')})' + body := if expected_ret_ct == 'void' { + 'static void ${name}(${params}) { ${call}; }' + } else { + 'static ${expected_ret_ct} ${name}(${params}) { return ${call}; }' + } + g.add_callback_wrapper_def(body) + return name +} + +fn (mut g FlatGen) callback_expected_return_c_type(typ types.Type, expected_c_abi string) string { + if expected_c_abi.len > 0 { + ret, _ := fn_ptr_typedef_parts(expected_c_abi) + return trimmed_space(ret) + } + return g.callback_c_type(typ) +} + +fn (mut g FlatGen) callback_expected_param_c_type(expected types.FnType, idx int, expected_c_abi string) string { + if expected_c_abi.len > 0 { + params := callback_fn_ptr_param_c_types(expected_c_abi) + if idx < params.len { + return params[idx] + } + } + return g.callback_c_type(fn_type_param(expected, idx)) +} + +fn callback_fn_ptr_param_c_types(encoded string) []string { + _, params := fn_ptr_typedef_parts(encoded) + clean := trimmed_space(params) + if clean.len == 0 || clean == 'void' { + return []string{} + } + mut out := []string{} + for param in clean.split(',') { + out << trimmed_space(param) + } + return out +} + +fn callback_can_cast_const_abi_param(actual_ct string, expected_ct string) bool { + actual := trimmed_space(actual_ct) + expected := trimmed_space(expected_ct) + if !expected.starts_with('const ') || !expected.ends_with('*') { + return false + } + return actual == expected['const '.len..].trim_space() +} + +fn (mut g FlatGen) callback_c_type(typ types.Type) string { + if typ is types.Void { + return 'void' + } + mut ct := if typ is types.OptionType || typ is types.ResultType { + g.optional_type_name(typ) + } else { + g.tc.c_type(typ) + } + if ct.starts_with('fn_ptr:') { + ct = g.resolve_fn_ptr_type(ct) + } + return ct +} + +fn (mut g FlatGen) callback_fn_type_key(typ types.FnType) string { + mut parts := []string{} + for i in 0 .. typ.params.len { + parts << g.callback_c_type(fn_type_param(typ, i)) + } + return '${g.callback_c_type(typ.return_type)}|${parts.join(',')}' +} + +fn callback_stable_key_hash(key string) string { + mut hash := u64(1469598103934665603) + for b in key.bytes() { + hash ^= u64(b) + hash *= u64(1099511628211) + } + return '${hash}' +} + +fn (g &FlatGen) callback_can_cast_userdata_param(actual types.Type, expected types.Type) bool { + return (callback_is_voidptr_type(expected) && callback_is_nonvoid_pointer_type(actual)) + || (callback_is_nonvoid_pointer_type(expected) && callback_is_voidptr_type(actual)) +} + +fn callback_is_voidptr_type(typ types.Type) bool { + clean := callback_unalias_type(typ) + if clean is types.Pointer { + base := callback_unalias_type(clean.base_type) + return base is types.Void + } + return false +} + +fn callback_is_nonvoid_pointer_type(typ types.Type) bool { + clean := callback_unalias_type(typ) + if clean is types.Pointer { + base := callback_unalias_type(clean.base_type) + return base !is types.Void + } + return false +} + +fn callback_unalias_type(typ types.Type) types.Type { + if typ is types.Alias { + return callback_unalias_type(typ.base_type) + } + return typ +} + +fn (mut g FlatGen) callback_c_fn_name(name string) string { + if name.starts_with('C.') { + return g.cname(name) + } + if g.test_files.len > 0 && (name == 'main' || name == 'main.main') { + return g.test_user_main_c_name() + } + if name.starts_with('main.') { + fn_name := name.all_after_last('.') + if shadow_name := g.main_runtime_shadow_fn_c_name('main', fn_name) { + return shadow_name + } + return g.fn_c_name_in_module('main', fn_name) + } + if shadow_name := g.main_runtime_shadow_fn_c_name(g.tc.cur_module, name) { + return shadow_name + } + return g.direct_call_name(name) +} + +fn fn_type_param(typ types.FnType, idx int) types.Type { + return typ.params[idx] +} + +// gen_optional_arg emits optional arg output for c. +fn (mut g FlatGen) gen_optional_arg(arg_id flat.NodeId, expected types.Type) bool { + return g.gen_optional_arg_with_abi(arg_id, expected, false) +} + +fn (mut g FlatGen) gen_optional_arg_with_abi(arg_id flat.NodeId, expected types.Type, concrete_abi bool) bool { + mut base_type := types.Type(types.void_) + if expected is types.OptionType { + base_type = expected.base_type + } else if expected is types.ResultType { + base_type = expected.base_type + } else { + return false + } + if g.gen_current_mut_param_value_read(arg_id, cgen_unalias_type(expected)) { + return true + } + if g.expr_is_optional_literal(arg_id, expected) { + collapsed := g.collapsed_optional_literal(arg_id, expected) + if concrete_abi { + ct := g.concrete_optional_type_name(expected) + if value_id := g.optional_literal_value_id(collapsed) { + if base_type is types.Void { + g.write('(${ct}){.ok = true}') + } else { + g.gen_optional_success_value(ct, value_id, base_type) + } + return true + } + g.write('(${ct}){.ok = false') + if err_id := g.optional_literal_err_id(collapsed) { + g.write(', .err = ') + g.gen_expr(err_id) + } + g.write('}') + return true + } + g.gen_expr_with_expected_type(collapsed, expected) + return true + } + arg_node := g.a.nodes[int(arg_id)] + // The checker can contextually type a plain call as `?T` when it is used + // where an option is expected. The callee's declared return type remains + // authoritative: only treat a call as an already materialized option when + // that function actually returns one. + plain_call_in_optional_context := arg_node.kind == .call + && !g.expr_really_returns_optional(arg_id) + if concrete_abi && arg_node.kind == .none_expr { + ct := g.concrete_optional_type_name(expected) + g.write('(${ct}){.ok = false}') + return true + } + if arg_node.typ.len > 0 && !plain_call_in_optional_context { + raw_arg_type := g.parse_node_type(&arg_node) + if raw_arg_type is types.OptionType || raw_arg_type is types.ResultType { + if concrete_abi { + g.gen_concrete_optional_arg_from_optional_expr(arg_id, raw_arg_type, expected, + base_type) + return true + } + if g.type_names_match(raw_arg_type, expected) || g.expr_really_returns_optional(arg_id) { + g.gen_expr(arg_id) + return true + } + } + } + if arg_node.kind == .ident { + if local_ct := g.local_storage_c_type(arg_node.value) { + expected_ct := if concrete_abi { + g.concrete_optional_type_name(expected) + } else { + g.optional_type_name(expected) + } + if local_ct == expected_ct { + g.gen_expr(arg_id) + return true + } + } + } + arg_type := g.usable_expr_type(arg_id) + arg_optional_type := optional_result_unalias_type(arg_type) + if !plain_call_in_optional_context + && (arg_optional_type is types.OptionType || arg_optional_type is types.ResultType) { + if concrete_abi { + g.gen_concrete_optional_arg_from_optional_expr(arg_id, arg_optional_type, expected, + base_type) + return true + } + if g.type_names_match(arg_optional_type, expected) + || g.optional_type_name(arg_type) == g.optional_type_name(expected) { + g.gen_expr(arg_id) + return true + } + if arg_node.kind == .none_expr || g.expr_really_returns_optional(arg_id) { + g.gen_expr(arg_id) + return true + } + } + ct := if concrete_abi { + g.concrete_optional_type_name(expected) + } else { + g.optional_type_name(expected) + } + if base_type is types.Void { + g.write('(${ct}){.ok = true}') + return true + } + if fixed := array_fixed_type(base_type) { + g.write('({ ${ct} __opt = {.ok = true}; memcpy(__opt.value, ') + g.gen_fixed_array_copy_source(arg_id, types.Type(fixed)) + g.write(', sizeof(__opt.value)); __opt; })') + return true + } + g.write('(${ct}){.ok = true, .value = ') + g.gen_expr_with_expected_type(arg_id, base_type) + g.write('}') + return true +} + +fn (mut g FlatGen) gen_optional_success_value(ct string, value_id flat.NodeId, base_type types.Type) { + if fixed := array_fixed_type(base_type) { + initializer := g.fixed_array_initializer_string(value_id, fixed) + if trimmed_space(initializer).len > 0 { + g.write('(${ct}){.ok = true, .value = ${initializer}}') + return + } + tmp := g.tmp_name() + g.write('({ ${ct} ${tmp} = {.ok = true}; memcpy(${tmp}.value, ') + g.gen_fixed_array_copy_source(value_id, base_type) + g.write(', sizeof(${tmp}.value)); ${tmp}; })') + return + } + g.write('(${ct}){.ok = true, .value = ') + g.gen_expr_with_expected_type(value_id, base_type) + g.write('}') +} + +fn (mut g FlatGen) gen_optional_payload_value(value_id flat.NodeId, base_type types.Type) { + if fixed := array_fixed_type(base_type) { + initializer := g.fixed_array_initializer_string(value_id, fixed) + if trimmed_space(initializer).len > 0 { + g.write(initializer) + return + } + } + g.gen_expr_with_expected_type(value_id, base_type) +} + +fn (mut g FlatGen) gen_concrete_optional_arg_from_optional_expr(arg_id flat.NodeId, arg_type types.Type, expected types.Type, base_type types.Type) { + dest_ct := g.concrete_optional_type_name(expected) + source_ct := g.optional_type_name_for_expr(arg_id, arg_type) + if source_ct == dest_ct { + g.gen_expr_with_expected_type(arg_id, expected) + return + } + tmp := g.tmp_count + g.tmp_count++ + g.write('({ ${source_ct} _opt${tmp} = ') + g.gen_expr_with_expected_type(arg_id, arg_type) + g.write('; _opt${tmp}.ok ? (${dest_ct}){.ok = true') + if base_type !is types.Void { + g.write(', .value = _opt${tmp}.value') + } + g.write('} : (${dest_ct}){.ok = false, .err = _opt${tmp}.err}; })') +} + +// expr_is_optional_literal supports expr is optional literal handling for FlatGen. +fn (mut g FlatGen) expr_is_optional_literal(id flat.NodeId, expected types.Type) bool { + if int(id) < 0 { + return false + } + node := g.a.nodes[int(id)] + if node.kind != .struct_init && node.kind != .cast_expr { + return false + } + expected_name := g.optional_type_name(expected) + return node.value.starts_with('?') || node.value.starts_with('!') || node.value == expected_name + || g.cname(node.value) == expected_name +} + +fn (mut g FlatGen) optional_literal_wrapped_expr(id flat.NodeId, expected types.Type) ?flat.NodeId { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return none + } + if g.expr_is_optional_literal(id, expected) { + return id + } + node := g.a.nodes[int(id)] + if node.kind in [.cast_expr, .as_expr, .paren, .expr_stmt] && node.children_count > 0 { + child_id := g.a.child(&node, 0) + if wrapped := g.optional_literal_wrapped_expr(child_id, expected) { + return wrapped + } + } + return none +} + +fn (g &FlatGen) optional_error_payload_err_expr(id flat.NodeId) ?flat.NodeId { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return none + } + node := g.a.nodes[int(id)] + if node.kind in [.cast_expr, .as_expr, .paren, .expr_stmt] && node.children_count > 0 { + return g.optional_error_payload_err_expr(g.a.child(&node, 0)) + } + if node.kind != .struct_init { + return none + } + mut has_false_ok := false + mut err_id := flat.empty_node + for i in 0 .. node.children_count { + field := g.a.child_node(&node, i) + if field.kind != .field_init || field.children_count == 0 { + continue + } + value_id := g.a.child(field, 0) + if field.value == 'ok' { + value := g.a.nodes[int(value_id)] + if value.kind == .bool_literal && value.value == 'false' { + has_false_ok = true + } + } else if field.value == 'err' { + err_id = value_id + } + } + if has_false_ok && int(err_id) >= 0 { + if nested := g.optional_error_payload_err_expr(err_id) { + return nested + } + return err_id + } + return none +} + +// collapsed_optional_literal supports collapsed optional literal handling for FlatGen. +fn (mut g FlatGen) collapsed_optional_literal(id flat.NodeId, expected types.Type) flat.NodeId { + mut current := id + for _ in 0 .. 4 { + value_id := g.optional_literal_value_id(current) or { break } + if !g.expr_is_optional_literal(value_id, expected) { + break + } + current = value_id + } + return current +} + +// optional_literal_value_id supports optional literal value id handling for FlatGen. +fn (g &FlatGen) optional_literal_value_id(id flat.NodeId) ?flat.NodeId { + if int(id) < 0 { + return none + } + node := g.a.nodes[int(id)] + if node.kind != .struct_init && node.kind != .cast_expr { + return none + } + for i in 0 .. node.children_count { + field := g.a.child_node(&node, i) + if field.kind == .field_init && field.value == 'value' && field.children_count > 0 { + return g.a.child(field, 0) + } + } + return none +} + +fn (g &FlatGen) optional_literal_err_id(id flat.NodeId) ?flat.NodeId { + if int(id) < 0 { + return none + } + node := g.a.nodes[int(id)] + if node.kind != .struct_init && node.kind != .cast_expr { + return none + } + for i in 0 .. node.children_count { + field := g.a.child_node(&node, i) + if field.kind == .field_init && field.value == 'err' && field.children_count > 0 { + return g.a.child(field, 0) + } + } + return none +} + +// fn_field_type supports fn field type handling for FlatGen. +fn (g &FlatGen) fn_field_type(base_type types.Type, field_name string) ?types.FnType { + field_type := g.field_type(base_type, field_name) or { return none } + return fn_type_from(field_type) +} + +// field_type supports field type handling for FlatGen. +fn (g &FlatGen) field_type(base_type types.Type, field_name string) ?types.Type { + clean0 := types.unwrap_pointer(base_type) + mut clean := clean0 + if clean0 is types.Alias { + clean = clean0.base_type + } + mut struct_name := '' + if clean is types.Struct { + struct_name = clean.name + } else if clean is types.Array { + struct_name = 'array' + } else if clean is types.Map { + struct_name = 'map' + } else if clean is types.String { + struct_name = 'string' + } else if clean is types.Interface { + for field in g.tc.interface_field_list(clean.name) { + if field.name == field_name { + return field.typ + } + } + return none + } + if struct_name.len == 0 { + return none + } + fields := g.tc.structs[struct_name] or { return none } + for field in fields { + if field.name == field_name { + return field.typ + } + } + return none +} + +// fn_type_from supports fn type from handling for c. +fn fn_type_from(t types.Type) ?types.FnType { + if t is types.FnType { + return t + } + if t is types.Alias { + return fn_type_from(t.base_type) + } + if t is types.Pointer { + return fn_type_from(t.base_type) + } + return none +} + +fn fn_type_is_pointer(t types.Type) bool { + if t is types.Alias { + return fn_type_is_pointer(t.base_type) + } + if t is types.Pointer { + return fn_type_from(t.base_type) != none + } + return false +} + +fn (mut g FlatGen) specialized_generic_plain_fn_name_for_call(id flat.NodeId, node flat.Node, name string) ?string { + if g.skip_generics { + return none + } + if name.len == 0 || name.contains('[') || node.children_count == 0 { + return none + } + if specialized := g.existing_specialized_generic_plain_fn_name(name) { + return specialized + } + // A transformed `_T_` callee already carries its explicit specialization. + // Do not replace it by re-inferring from default-typed literals (`i8(-3)` + // would otherwise be retargeted to the `int` specialization during cgen). + if name.contains('_T_') && g.resolved_name_is_generic_plain(name) { + return name + } + return g.inferred_generic_plain_fn_name_for_call(id, node, name) +} + +fn (mut g FlatGen) inferred_generic_plain_fn_name_for_call(id flat.NodeId, node flat.Node, name string) ?string { + if g.skip_generics { + return none + } + if name.len == 0 || name.contains('[') || node.children_count == 0 { + return none + } + // A transformed receiver call is an already-resolved qualified concrete + // function. Do not reinterpret its short method name as an unrelated plain + // generic (for example `sync.WaitGroup.add` versus a user `add[T]`). + if name.contains('.') && g.concrete_fn_return_known(name) { + return none + } + if g.plain_concrete_fn_name_shadows_generic(name) { + return none + } + base := g.generic_plain_fn_base_for_call(id, name) or { return none } + generic_params := g.tc.fn_generic_params[base] or { return none } + if generic_params.len == 0 { + return none + } + param_texts := g.tc.fn_param_type_texts[base] or { return none } + mut inferred := map[string]string{} + // Contextual return types must win over default-typed literal arguments. In + // `fn values() []Vec2[f64] { return [vec2(1.0, 1.0)] }`, a large combined + // test program can retain the literal arguments as untyped floats even though + // the array element has already fixed `T` to `f64`. + if ret_text := g.tc.fn_ret_type_texts[base] { + expected_ret := g.expected_expr_type.name() + if codegen_type_text_is_usable_for_generic_inference(expected_ret) { + infer_codegen_generic_type_args(ret_text, expected_ret, mut inferred) + } + } + g.infer_codegen_generic_call_type_args(node, base, param_texts, mut inferred) + if ret_text := g.tc.fn_ret_type_texts[base] { + ret_type := g.call_default_return_type(id).name() + if ret_type.len > 0 { + infer_codegen_generic_type_args(ret_text, ret_type, mut inferred) + } + } + mut args := []string{cap: generic_params.len} + for param in generic_params { + arg := inferred[param] or { return none } + if codegen_generic_arg_is_unresolved(arg) { + return none + } + args << arg + } + if args.len == 0 { + return none + } + for candidate in g.specialized_generic_plain_fn_candidates(base, args) { + if candidate in g.tc.fn_param_types || candidate in g.tc.fn_ret_types { + return candidate + } + } + return none +} + +fn (g &FlatGen) existing_specialized_generic_plain_fn_name(name string) ?string { + if g.skip_generics { + return none + } + // Generic declaration bases are present in the signature maps too. Only an + // actual monomorphized name may bypass call-site inference here. + if !g.resolved_name_is_generic_plain(name) + || (!name.contains('_T_') && !g.tc.specialized_generic_fns[name]) { + return none + } + mut candidates := [name] + if name.contains('__') { + candidates << name.replace('__', '.') + } + if !name.contains('.') && g.tc.cur_module.len > 0 && g.tc.cur_module !in ['', 'main', 'builtin'] { + candidates << '${g.tc.cur_module}.${name}' + } + for candidate in candidates { + if candidate in g.tc.fn_ret_types || candidate in g.tc.fn_param_types { + return candidate + } + c_name_candidate := g.cname(candidate) + if c_name_candidate != candidate + && (c_name_candidate in g.tc.fn_ret_types || c_name_candidate in g.tc.fn_param_types) { + return c_name_candidate + } + } + return none +} + +fn (mut g FlatGen) specialized_generic_plain_fn_name_for_explicit_call(id flat.NodeId, fn_node flat.Node, name string) ?string { + if g.skip_generics { + return none + } + if name.len == 0 || name.contains('[') { + return none + } + if g.plain_concrete_fn_name_shadows_generic(name) { + return none + } + base := g.generic_plain_fn_base_for_call(id, name) or { return none } + generic_params := g.tc.fn_generic_params[base] or { return none } + args := g.explicit_generic_call_type_arg_names(fn_node) + if args.len == 0 || args.len != generic_params.len { + return none + } + for arg in args { + if codegen_generic_arg_is_unresolved(arg) { + return none + } + } + for candidate in g.specialized_generic_plain_fn_candidates(base, args) { + if candidate in g.tc.fn_param_types || candidate in g.tc.fn_ret_types { + return candidate + } + } + return none +} + +fn (g &FlatGen) explicit_generic_call_type_arg_names(fn_node flat.Node) []string { + if fn_node.kind != .index || fn_node.children_count < 2 || fn_node.value == 'range' { + return []string{} + } + mut args := []string{} + for i in 1 .. fn_node.children_count { + arg := g.generic_call_type_arg_name(g.a.child(&fn_node, i)) + if arg.len == 0 { + return []string{} + } + args << arg + } + return args +} + +fn (g &FlatGen) generic_call_type_arg_name(id flat.NodeId) string { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return '' + } + node := g.a.nodes[int(id)] + match node.kind { + .ident { + return node.value + } + .selector { + if node.children_count == 0 { + return node.value + } + base := g.generic_call_type_arg_name(g.a.child(&node, 0)) + if base.len == 0 { + return node.value + } + return '${base}.${node.value}' + } + .prefix { + if node.op == .amp && node.children_count > 0 { + inner := g.generic_call_type_arg_name(g.a.child(&node, 0)) + if inner.len > 0 { + return '&${inner}' + } + } + } + .index { + if node.value != 'range' && node.children_count > 0 { + base := g.generic_call_type_arg_name(g.a.child(&node, 0)) + if base.len == 0 { + return '' + } + mut args := []string{} + for i in 1 .. node.children_count { + arg := g.generic_call_type_arg_name(g.a.child(&node, i)) + if arg.len == 0 { + return '' + } + args << arg + } + return '${base}[${args.join(',')}]' + } + } + else {} + } + + if node.typ.len > 0 { + return node.typ + } + return node.value +} + +fn (g &FlatGen) infer_codegen_generic_call_type_args(node flat.Node, base string, param_texts []string, mut inferred map[string]string) { + is_variadic := g.codegen_generic_fn_is_variadic(base) + for i, param_text in param_texts { + arg_idx := i + 1 + if arg_idx >= int(node.children_count) { + break + } + if is_variadic && i == param_texts.len - 1 { + elem_param := codegen_variadic_elem_param_text(param_text) + for call_arg_idx in arg_idx .. node.children_count { + arg_id := g.a.child(&node, call_arg_idx) + arg_node := g.a.nodes[int(arg_id)] + if spread_id := g.spread_arg_child(arg_node) { + arg_type := g.generic_call_arg_type_text(spread_id) + if arg_type.len > 0 { + infer_codegen_generic_type_args(param_text, arg_type, mut inferred) + } + continue + } + arg_type := g.generic_call_arg_type_text(arg_id) + if arg_type.len > 0 { + infer_codegen_generic_type_args(elem_param, arg_type, mut inferred) + } + } + break + } + arg_type := g.generic_call_arg_type_text(g.a.child(&node, arg_idx)) + if arg_type.len > 0 { + infer_codegen_generic_type_args(param_text, arg_type, mut inferred) + } + } +} + +fn (g &FlatGen) codegen_generic_fn_is_variadic(base string) bool { + return (g.tc.fn_variadic[base] or { false }) + || g.fn_decl_is_variadic(base, base.all_after_last('.')) +} + +fn codegen_variadic_elem_param_text(param_text string) string { + clean := trimmed_space(param_text) + if clean.starts_with('...') { + return trimmed_space(clean[3..]) + } + if clean.starts_with('[]') { + return trimmed_space(clean[2..]) + } + return clean +} + +fn (g &FlatGen) plain_concrete_fn_name_shadows_generic(name string) bool { + if name.len == 0 || name.contains('.') { + return false + } + if g.non_generic_fn_decl_exists_in_module(name, g.tc.cur_module) { + return true + } + if name in g.tc.fn_generic_params { + return false + } + qname := g.tc.qualify_fn_name(name) + if qname != name { + if qname in g.tc.fn_generic_params { + return false + } + if g.concrete_fn_return_known(qname) { + return true + } + } + if g.concrete_fn_return_known(name) { + return true + } + return qname != name && g.concrete_fn_return_known(qname) +} + +// precompute_non_generic_fn_index builds the lookup consumed by +// non_generic_fn_decl_exists_in_module. It mirrors that scan exactly: module +// starts at 'main' and is only advanced by `.module_decl` nodes (not reset per +// file), so the attribution matches the previous per-call behavior byte for byte. +fn (mut g FlatGen) precompute_non_generic_fn_index() { + mut cur_module := 'main' + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + match node.kind { + .module_decl { + cur_module = node.value + } + .fn_decl { + if node.generic_params().len == 0 && !node.typ.contains('generic') { + g.non_generic_fn_names_by_module['${cur_module}\x01${node.value}'] = true + } + } + else {} + } + } +} + +fn (g &FlatGen) non_generic_fn_decl_exists_in_module(name string, module_name string) bool { + if name.len == 0 || name.contains('.') { + return false + } + return '${module_name}\x01${name}' in g.non_generic_fn_names_by_module +} + +// precompute_generic_fn_key_index builds the lookups consumed by +// generic_plain_fn_base_for_call: every tc.fn_generic_params key indexed by its +// short (post-dot) name and by its c_name spelling, plus its position in the +// map's iteration order so multi-match resolution below replays the original +// full-map scan byte for byte. +fn (mut g FlatGen) precompute_generic_fn_key_index() { + mut ordinal := 0 + for key, _ in g.tc.fn_generic_params { + g.generic_fn_keys_by_short[key.all_after_last('.')] << key + g.generic_fn_keys_by_cname[g.cname(key)] << key + g.generic_fn_key_ordinal[key] = ordinal + ordinal++ + } +} + +// generic_fn_keys_matching returns the fn_generic_params keys whose short name +// is `short` or whose c_name is `name`, in the params map's iteration order. +fn (g &FlatGen) generic_fn_keys_matching(short string, name string) []string { + by_short := g.generic_fn_keys_by_short[short] or { []string{} } + by_cname := g.generic_fn_keys_by_cname[name] or { []string{} } + if by_cname.len == 0 { + return by_short + } + if by_short.len == 0 { + return by_cname + } + // Merge the two ordinal-sorted bucket lists, dropping keys present in both. + mut merged := []string{cap: by_short.len + by_cname.len} + mut i := 0 + mut j := 0 + for i < by_short.len || j < by_cname.len { + if i >= by_short.len { + merged << by_cname[j] + j++ + continue + } + if j >= by_cname.len { + merged << by_short[i] + i++ + continue + } + oi := g.generic_fn_key_ordinal[by_short[i]] + oj := g.generic_fn_key_ordinal[by_cname[j]] + if oi == oj { + merged << by_short[i] + i++ + j++ + } else if oi < oj { + merged << by_short[i] + i++ + } else { + merged << by_cname[j] + j++ + } + } + return merged +} + +fn (g &FlatGen) concrete_fn_return_known(name string) bool { + if name in g.tc.fn_generic_params { + return false + } + if ret := g.fn_decl_ret_types[name] { + return ret !is types.Unknown + } + ret := g.tc.fn_ret_types[name] or { return false } + return ret !is types.Unknown +} + +fn (g &FlatGen) resolved_name_is_generic_plain(name string) bool { + if name in g.tc.fn_generic_params { + return true + } + if !name.contains('_T_') { + return false + } + base := name.all_before('_T_') + return base in g.tc.fn_generic_params || base.replace('__', '.') in g.tc.fn_generic_params +} + +fn (g &FlatGen) generic_plain_fn_base_for_call(id flat.NodeId, name string) ?string { + mut candidates := []string{} + if resolved := g.tc.resolved_call_name(id) { + if resolved_call_matches_target(resolved, name) { + candidates << resolved + } + } + if !name.contains('.') && g.tc.cur_module.len > 0 && g.tc.cur_module !in ['', 'main', 'builtin'] { + candidates << '${g.tc.cur_module}.${name}' + } + candidates << name + if name.contains('__') { + candidates << name.replace('__', '.') + } + normalized := g.normalize_call_key(name) + candidates << normalized + short := name.all_after_last('.') + mut found := '' + for candidate in candidates { + if base := g.known_generic_plain_fn_base(candidate) { + return base + } + } + // `key == short || key.ends_with('.${short}')` is exactly "key's post-dot + // short name equals short", so the precomputed buckets cover the old + // full-map scan (see precompute_generic_fn_key_index). + cur_module_prefix := '${g.tc.cur_module}.' + for key in g.generic_fn_keys_matching(short, name) { + if key.starts_with(cur_module_prefix) { + return key + } + if found.len > 0 && found != key { + return none + } + found = key + } + if found.len > 0 { + return found + } + return none +} + +fn (g &FlatGen) known_generic_plain_fn_base(name string) ?string { + if name.len == 0 { + return none + } + if name in g.tc.fn_generic_params { + if !name.contains('.') { + if module_name := g.tc.fn_type_modules[name] { + if module_name.len > 0 && module_name !in ['main', 'builtin'] { + qualified := '${module_name}.${name}' + if qualified in g.tc.fn_generic_params { + return qualified + } + } + } + } + return name + } + if name.starts_with('main.') { + short := name.all_after_last('.') + if short in g.tc.fn_generic_params { + return short + } + } + if name.contains('__') { + dotted := name.replace('__', '.') + if dotted in g.tc.fn_generic_params { + return dotted + } + } + return none +} + +fn (g &FlatGen) generic_call_arg_type_text(id flat.NodeId) string { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return '' + } + node := g.a.nodes[int(id)] + if node.kind == .ident { + if raw_type := g.local_storage_raw_type(node.value) { + if codegen_type_text_is_usable_for_generic_inference(raw_type) { + return raw_type + } + } + typ := g.usable_expr_type(id) + name := typ.name() + if codegen_type_text_is_usable_for_generic_inference(name) { + return name + } + } + if node.kind == .prefix && node.op == .amp && node.children_count > 0 { + child := g.a.child_node(&node, 0) + if child.kind == .ident { + if raw_type := g.local_storage_raw_type(child.value) { + if codegen_type_text_is_usable_for_generic_inference(raw_type) { + return '&${raw_type}' + } + } + } + } + if node.kind == .call { + if ret_type := g.generic_call_return_type_text_for_inference(id, node) { + return ret_type + } + if ret_type := g.registered_call_return_type_text(node) { + return ret_type + } + } + if codegen_type_text_is_usable_for_generic_inference(node.typ) { + return node.typ + } + typ := g.usable_expr_type(id) + name := typ.name() + if codegen_type_text_is_usable_for_generic_inference(name) { + return name + } + return '' +} + +fn (g &FlatGen) generic_call_return_type_text_for_inference(id flat.NodeId, node flat.Node) ?string { + if node.kind != .call || node.children_count == 0 { + return none + } + target := g.call_target_name(g.a.child(&node, 0)) + base := g.generic_plain_fn_base_for_call(id, target) or { return none } + params := g.tc.fn_generic_params[base] or { return none } + if params.len == 0 { + return none + } + param_texts := g.tc.fn_param_type_texts[base] or { return none } + mut inferred := map[string]string{} + g.infer_codegen_generic_call_type_args(node, base, param_texts, mut inferred) + mut args := []string{cap: params.len} + for param in params { + arg := inferred[param] or { return none } + if codegen_generic_arg_is_unresolved(arg) { + return none + } + args << arg + } + ret_text := g.tc.fn_ret_type_texts[base] or { node.typ } + if ret_text.len == 0 { + return none + } + ret := substitute_shared_generic_type_text(ret_text, params, args) + if codegen_type_text_is_usable_for_generic_inference(ret) { + return ret + } + return none +} + +fn (g &FlatGen) registered_call_return_type_text(node flat.Node) ?string { + if node.kind != .call || node.children_count == 0 { + return none + } + if typ := g.specialized_receiver_call_return_type_text(node) { + return typ + } + target := g.call_target_name(g.a.child(&node, 0)) + for candidate in [target, g.normalize_call_key(target), g.cname(target)] { + if ret := g.fn_decl_ret_types[candidate] { + name := ret.name() + if codegen_type_text_is_usable_for_generic_inference(name) { + return name + } + } + if ret := g.tc.fn_ret_types[candidate] { + name := ret.name() + if codegen_type_text_is_usable_for_generic_inference(name) { + return name + } + } + } + return none +} + +fn (g &FlatGen) specialized_receiver_call_return_type_text(node flat.Node) ?string { + target := g.call_target_name(g.a.child(&node, 0)) + if !target.contains('.') { + return none + } + receiver := target.all_before_last('.') + method := target.all_after_last('.') + if receiver.len == 0 || method.len == 0 { + return none + } + mod_name := if receiver.contains('.') { receiver.all_before_last('.') } else { '' } + short_receiver := receiver.all_after_last('.') + for i := short_receiver.len - 1; i >= 0; i-- { + if short_receiver[i] != `_` { + continue + } + base_short := short_receiver[..i] + suffix := short_receiver[i + 1..] + if base_short.len == 0 || suffix.len == 0 { + continue + } + base := if mod_name.len > 0 { '${mod_name}.${base_short}' } else { base_short } + arg := codegen_generic_type_arg_from_suffix(suffix) + if arg.len == 0 { + continue + } + return '${base}[${arg}]' + } + return none +} + +fn codegen_generic_type_arg_from_suffix(suffix string) string { + clean := trimmed_space(suffix) + return match clean { + 'string' { 'string' } + 'bool' { 'bool' } + 'int' { 'int' } + 'u8' { 'u8' } + 'u16' { 'u16' } + 'u32' { 'u32' } + 'u64' { 'u64' } + 'i8' { 'i8' } + 'i16' { 'i16' } + 'i32' { 'i32' } + 'i64' { 'i64' } + 'f32' { 'f32' } + 'f64' { 'f64' } + else { clean.replace('__', '.') } + } +} + +fn codegen_type_text_is_usable_for_generic_inference(typ string) bool { + clean := trimmed_space(typ) + if clean.len == 0 || clean in ['void', 'unknown', 'generic'] { + return false + } + return !codegen_generic_arg_is_unresolved(clean) +} + +fn (mut g FlatGen) specialized_generic_plain_fn_candidates(base string, args []string) []string { + mut suffixes := codegen_generic_type_suffix_variants(args) + mut bases := []string{} + bases << base + if base.contains('.') { + bases << base.all_after_last('.') + } else if module_name := g.tc.fn_type_modules[base] { + if module_name.len > 0 && module_name !in ['main', 'builtin'] { + bases.insert(0, '${module_name}.${base}') + } + } else if g.tc.cur_module.len > 0 && g.tc.cur_module !in ['', 'main', 'builtin'] { + bases << '${g.tc.cur_module}.${base}' + } + mut result := []string{} + for suffix in suffixes { + for fn_base in bases { + if fn_base.len == 0 || suffix.len == 0 { + continue + } + codegen_specialized_receiver_fn_candidates(mut result, fn_base, suffix) + spec := '${fn_base}_T_${suffix}' + codegen_push_unique(mut result, spec) + codegen_push_unique(mut result, g.cname(spec)) + } + } + return result +} + +fn codegen_specialized_receiver_fn_candidates(mut result []string, fn_base string, suffix string) { + if !fn_base.contains('.') { + return + } + receiver := fn_base.all_before_last('.') + method := fn_base.all_after_last('.') + if receiver.len == 0 || method.len == 0 { + return + } + spec := '${receiver}_${suffix}.${method}' + codegen_push_unique(mut result, spec) + codegen_push_unique(mut result, c_name(spec)) + if receiver.contains('.') { + mod_name := receiver.all_before_last('.') + short_receiver := receiver.all_after_last('.') + short_spec := '${short_receiver}_${suffix}.${method}' + qualified_short_spec := '${mod_name}.${short_receiver}_${suffix}.${method}' + codegen_push_unique(mut result, short_spec) + codegen_push_unique(mut result, c_name(short_spec)) + codegen_push_unique(mut result, qualified_short_spec) + codegen_push_unique(mut result, c_name(qualified_short_spec)) + } +} + +fn codegen_generic_type_suffix_variants(args []string) []string { + mut suffixes := []string{} + mut short_raw_parts := []string{cap: args.len} + mut short_parts := []string{cap: args.len} + mut full_parts := []string{cap: args.len} + for arg in args { + clean := trimmed_space(arg) + short_raw := generic_receiver_type_arg_short(clean).replace('[]', 'Array_').replace('&', + 'ptr_') + short_raw_parts << short_raw + short_parts << c_name(short_raw) + full_parts << c_name(clean.replace('[]', 'Array_').replace('&', 'ptr_')) + } + codegen_push_unique(mut suffixes, full_parts.join('__')) + codegen_push_unique(mut suffixes, short_raw_parts.join('__')) + codegen_push_unique(mut suffixes, short_parts.join('__')) + codegen_push_unique(mut suffixes, full_parts.join('_')) + codegen_push_unique(mut suffixes, short_raw_parts.join('_')) + codegen_push_unique(mut suffixes, short_parts.join('_')) + return suffixes +} + +fn codegen_push_unique(mut values []string, value string) { + if value.len > 0 && value !in values { + values << value + } +} + +fn infer_codegen_generic_type_args(param_type string, arg_type string, mut inferred map[string]string) { + param := trimmed_space(param_type) + arg := trimmed_space(arg_type) + if param.len == 0 || arg.len == 0 || arg in ['unknown', 'generic'] { + return + } + if codegen_generic_placeholder_name(param) { + if param !in inferred { + inferred[param] = arg + } + return + } + if param.starts_with('&') { + infer_codegen_generic_type_args(param[1..], arg.trim_left('&'), mut inferred) + return + } + if param.starts_with('mut ') { + infer_codegen_generic_type_args(param[4..], arg.trim_left('&'), mut inferred) + return + } + if param.starts_with('...') { + infer_codegen_generic_type_args(param[3..], arg.trim_left('[]'), mut inferred) + return + } + if param.starts_with('[]') && arg.starts_with('[]') { + infer_codegen_generic_type_args(param[2..], arg[2..], mut inferred) + return + } + if (param.starts_with('?') && arg.starts_with('?')) + || (param.starts_with('!') && arg.starts_with('!')) { + infer_codegen_generic_type_args(param[1..], arg[1..], mut inferred) + return + } + if param.starts_with('fn') && arg.starts_with('fn') { + p_params, p_ret, p_ok := codegen_fn_type_parts(param) + a_params, a_ret, a_ok := codegen_fn_type_parts(arg) + if p_ok && a_ok { + for i, p_param in p_params { + if i < a_params.len { + infer_codegen_generic_type_args(p_param, a_params[i], mut inferred) + } + } + infer_codegen_generic_type_args(p_ret, a_ret, mut inferred) + } + return + } + if param.starts_with('map[') && arg.starts_with('map[') { + p_end := shared_generic_matching_bracket(param, 3) + a_end := shared_generic_matching_bracket(arg, 3) + if p_end < param.len && a_end < arg.len { + infer_codegen_generic_type_args(param[4..p_end], arg[4..a_end], mut inferred) + infer_codegen_generic_type_args(param[p_end + 1..], arg[a_end + 1..], mut inferred) + } + return + } + p_base, p_args, p_ok := parse_shared_generic_app_parts(param) + if p_ok { + a_base, a_args, a_ok := parse_shared_generic_app_parts(arg) + if a_ok && p_base.all_after_last('.') == a_base.all_after_last('.') { + for i, p_arg in p_args { + if i < a_args.len { + infer_codegen_generic_type_args(p_arg, a_args[i], mut inferred) + } + } + } + } +} + +fn codegen_fn_type_parts(typ string) ([]string, string, bool) { + clean := trimmed_space(typ) + if !clean.starts_with('fn') { + return []string{}, '', false + } + open := clean.index_u8(`(`) + if open < 0 { + return []string{}, '', false + } + close := codegen_matching_paren(clean, open) + if close <= open || close >= clean.len { + return []string{}, '', false + } + params_text := trimmed_space(clean[open + 1..close]) + mut params := []string{} + if params_text.len > 0 { + for param in shared_split_generic_args(params_text) { + params << codegen_fn_param_type_text(param) + } + } + ret := trimmed_space(clean[close + 1..]) + return params, if ret.len == 0 { + 'void' + } else { + ret + }, true +} + +fn codegen_matching_paren(s string, start int) int { + mut depth := 0 + for i in start .. s.len { + if s[i] == `(` { + depth++ + } else if s[i] == `)` { + depth-- + if depth == 0 { + return i + } + } + } + return s.len +} + +fn codegen_fn_param_type_text(param string) string { + clean := trimmed_space(param) + if clean.len == 0 { + return '' + } + if clean.starts_with('mut ') { + rest := trimmed_space(clean[4..]) + return 'mut ${codegen_fn_param_type_text(rest)}' + } + space := clean.last_index_u8(` `) + if space > 0 { + return trimmed_space(clean[space + 1..]) + } + return clean +} + +fn codegen_generic_placeholder_name(name string) bool { + clean := trimmed_space(name) + if clean.len == 0 || clean.contains('.') || clean.contains('[') || clean.contains(']') + || clean.contains(' ') { + return false + } + if clean.len == 1 { + return clean[0] >= `A` && clean[0] <= `Z` + } + return clean[0] >= `A` && clean[0] <= `Z` && clean[1] >= `0` && clean[1] <= `9` +} + +fn codegen_generic_arg_is_unresolved(arg string) bool { + clean := trimmed_space(arg) + if clean.len == 0 { + return true + } + if codegen_generic_placeholder_name(clean) { + return true + } + if clean.starts_with('&') || clean.starts_with('?') || clean.starts_with('!') { + return codegen_generic_arg_is_unresolved(clean[1..]) + } + if clean.starts_with('mut ') { + return codegen_generic_arg_is_unresolved(clean[4..]) + } + if clean.starts_with('...') { + return codegen_generic_arg_is_unresolved(clean[3..]) + } + if clean.starts_with('[]') { + return codegen_generic_arg_is_unresolved(clean[2..]) + } + if clean.starts_with('map[') { + bracket_end := shared_generic_matching_bracket(clean, 3) + if bracket_end < clean.len { + return codegen_generic_arg_is_unresolved(clean[4..bracket_end]) + || codegen_generic_arg_is_unresolved(clean[bracket_end + 1..]) + } + } + _, nested_args, ok := parse_shared_generic_app_parts(clean) + if ok { + for nested in nested_args { + if codegen_generic_arg_is_unresolved(nested) { + return true + } + } + } + return false +} + +fn (g &FlatGen) fn_value_call_param_types(callee_id flat.NodeId) ?[]types.Type { + if int(callee_id) < 0 { + return none + } + node := g.a.nodes[int(callee_id)] + if node.kind == .ident { + if typ := g.cur_param_types[node.value] { + if ft := fn_type_from(typ) { + return ft.params.clone() + } + } + if typ := g.local_ident_type(node.value) { + if ft := fn_type_from(typ) { + return ft.params.clone() + } + } + } + if node.typ.len > 0 { + if ft := fn_type_from(g.parse_node_type(&node)) { + return ft.params.clone() + } + } + if node.kind != .ident { + if ft := fn_type_from(g.tc.resolve_type(callee_id)) { + return ft.params.clone() + } + } + return none +} + +fn (mut g FlatGen) guarded_anon_self_call(node flat.Node) ?GuardedAnonSelfCall { + if !g.cur_fn_name.starts_with('__anon_fn_') || node.kind != .call || node.children_count == 0 { + return none + } + callee_id := g.a.child(&node, 0) + if int(callee_id) < 0 { + return none + } + callee_node := g.a.nodes[int(callee_id)] + if callee_node.kind != .ident || callee_node.value.len == 0 + || callee_node.value.starts_with('__anon_fn_') { + return none + } + capture_name := '${g.cur_fn_name}_${callee_node.value}' + qcapture_name := qualify_name_in_module(g.tc.cur_module, capture_name) + if capture_name !in g.global_types && qcapture_name !in g.global_types { + return none + } + callee_type := g.local_ident_type(callee_node.value) or { + if callee_node.typ.len > 0 { + g.parse_node_type(&callee_node) + } else { + g.tc.resolve_type(callee_id) + } + } + fn_type := fn_type_from(callee_type) or { return none } + if fn_type.return_type !is types.Void { + return none + } + mut callee_ct := g.local_storage_c_type(callee_node.value) or { '' } + if callee_ct.len == 0 { + callee_ct = g.tc.c_type(types.Type(fn_type)) + } + if callee_ct.starts_with('fn_ptr:') { + callee_ct = g.resolve_fn_ptr_type(callee_ct) + } + return GuardedAnonSelfCall{ + callee: g.cname(callee_node.value) + callee_ct: callee_ct + } +} + +fn (mut g FlatGen) gen_guarded_anon_self_call_stmt(node flat.Node) bool { + guard := g.guarded_anon_self_call(node) or { return false } + current_cname := g.fn_c_name_in_module(g.tc.cur_module, g.cur_fn_name) + current := if guard.callee_ct.len > 0 { + '(${guard.callee_ct})${current_cname}' + } else { + current_cname + } + g.write('if (${guard.callee} != ${current}) { ') + g.write(guard.callee) + g.write('(') + g.gen_call_args(guard.callee, node, 1) + g.writeln('); }') + return true +} + +fn (g &FlatGen) resolved_selector_module_arg_start(resolved string, node flat.Node) ?int { + if !resolved.contains('.') || node.children_count < 2 { + return none + } + callee := g.a.child_node(&node, 0) + if callee.kind != .selector || callee.children_count == 0 { + return none + } + base := g.a.child_node(callee, 0) + first_arg := g.a.child_node(&node, 1) + if base.kind != .ident || !g.selector_module_base_arg_matches(first_arg, base.value) { + return none + } + resolved_base := resolved.all_before_last('.') + if resolved_base != base.value && !resolved_base.ends_with('.${base.value}') { + return none + } + params := g.tc.fn_param_types[resolved] or { return none } + if g.module_call_arg_count_matches(resolved, params, int(node.children_count) - 2) { + return 2 + } + return none +} + +fn (g &FlatGen) selector_module_base_arg_matches(arg flat.Node, base string) bool { + if arg.kind == .ident { + return arg.value == base + } + if arg.kind == .prefix && arg.op == .amp && arg.children_count > 0 { + child := g.a.child_node(&arg, 0) + return child.kind == .ident && child.value == base + } + return false +} + +fn (g &FlatGen) selector_call_can_emit_direct(resolved string, node flat.Node) bool { + if resolved.len == 0 || node.children_count == 0 { + return false + } + fn_node := g.a.child_node(&node, 0) + if fn_node.kind != .selector { + return false + } + params := g.tc.fn_param_types[resolved] or { + if resolved in g.tc.fn_ret_types { + return node.children_count == 1 + } + return false + } + return params.len == node.children_count - 1 +} + +fn (g &FlatGen) resolved_receiver_method_for_call(resolved string, node flat.Node, method string) ?string { + if resolved.len == 0 || method.len == 0 || !resolved.ends_with('.${method}') { + return none + } + params := g.tc.fn_param_types[resolved] or { return none } + if params.len != node.children_count { + return none + } + return resolved +} + +fn (g &FlatGen) selector_module_call_name(id flat.NodeId, fn_node flat.Node, node flat.Node) ?string { + if fn_node.kind != .selector || fn_node.children_count == 0 { + return none + } + base := g.a.child_node(&fn_node, 0) + if base.kind != .ident { + return none + } + if g.selector_base_is_value(base.value) { + resolved := g.tc.resolved_call_name(id) or { return none } + source_file := g.a.source_files[fn_node.pos.id] or { return none } + lexical_module := g.tc.file_imports[source_file.name + '\n' + base.value] or { return none } + lexical_call := '${lexical_module}.${fn_node.value}' + if resolved != lexical_call && resolved.replace('__', '.') != lexical_call { + return none + } + } + if source_file := g.a.source_files[fn_node.pos.id] { + if lexical_module := g.tc.file_imports[source_file.name + '\n' + base.value] { + // The selector's own source file is authoritative for compiler-cloned + // default expressions. Their copied base can acquire the type of a caller + // local with the same name as the import. A concrete resolved method name + // still wins for a genuine local shadow in the original source. + call_name := '${lexical_module}.${fn_node.value}' + resolved_call := g.tc.resolved_call_name(id) or { '' } + resolved_is_lexical_module_call := resolved_call == call_name + || resolved_call.replace('__', '.') == call_name + if resolved_call.len == 0 || resolved_is_lexical_module_call { + arg_start := g.selector_module_call_arg_start(fn_node, node) + params := g.tc.fn_param_types[call_name] or { + if call_name in g.tc.fn_ret_types && node.children_count == arg_start { + return call_name + } + return none + } + if g.module_call_arg_count_matches(call_name, params, + node.children_count - arg_start) + { + return call_name + } + } + } + } + if g.selector_base_is_value(base.value) { + return none + } + mod_name := g.selector_base_module(base.value) or { + if base.value == g.tc.cur_module { + base.value + } else { + '' + } + } + if mod_name.len == 0 { + return none + } + call_name := '${mod_name}.${fn_node.value}' + if g.selector_base_is_value(base.value) { + resolved := g.tc.resolved_call_name(id) or { return none } + if resolved != call_name && resolved.replace('__', '.') != call_name { + return none + } + } + arg_start := g.selector_module_call_arg_start(fn_node, node) + params := g.tc.fn_param_types[call_name] or { + if call_name in g.tc.fn_ret_types && node.children_count == arg_start { + return call_name + } + return none + } + if g.module_call_arg_count_matches(call_name, params, node.children_count - arg_start) { + return call_name + } + return none +} + +fn (g &FlatGen) module_call_arg_count_matches(fn_name string, params []types.Type, supplied int) bool { + if params.len == supplied { + return true + } + if !(g.tc.fn_variadic[fn_name] or { false }) && !(g.tc.c_variadic_fns[fn_name] or { false }) { + return false + } + if params.len == 0 { + return supplied == 0 + } + min_args := if params[params.len - 1] is types.Array { params.len - 1 } else { params.len } + return supplied >= min_args +} + +fn (g &FlatGen) selector_module_call_arg_start(fn_node flat.Node, node flat.Node) int { + if fn_node.kind == .selector && fn_node.children_count > 0 && node.children_count > 1 { + base := g.a.child_node(&fn_node, 0) + first_arg := g.a.child_node(&node, 1) + if base.kind == .ident && g.selector_module_base_arg_matches(first_arg, base.value) { + return 2 + } + } + return 1 +} + +fn (g &FlatGen) target_module_call_name(target string, node flat.Node) ?string { + if !target.contains('.') { + return none + } + if node.children_count > 0 { + fn_node := g.a.child_node(&node, 0) + if fn_node.kind == .selector && fn_node.children_count > 0 { + original_base := g.a.child_node(fn_node, 0) + if original_base.kind == .ident && g.selector_base_is_value(original_base.value) { + source_file := g.a.source_files[fn_node.pos.id] or { return none } + lexical_module := g.tc.file_imports[source_file.name + '\n' + original_base.value] or { + return none + } + if target != '${lexical_module}.${fn_node.value}' { + return none + } + } + } + } + base := target.all_before_last('.') + method := target.all_after_last('.') + if base.len == 0 || method.len == 0 { + return none + } + arg_start := g.target_module_call_arg_start(target, node) + // A transformed module selector carries its namespace ident as a synthetic + // first argument. That syntactic marker remains authoritative when a global + // constant happens to share the module alias; the argument is discarded below. + if arg_start != 2 { + if typ := g.tc.cur_scope.lookup(base) { + if typ !is types.Void { + return none + } + } + } + static_name := '${base}.${method}' + if static_name in g.tc.fn_param_types || static_name in g.tc.fn_ret_types + || static_name in g.tc.fn_generic_params { + params := g.tc.fn_param_types[static_name] or { + if static_name in g.tc.fn_ret_types && node.children_count == arg_start { + return static_name + } + return none + } + if g.module_call_arg_count_matches(static_name, params, node.children_count - arg_start) { + return static_name + } + } + mod_name := g.selector_base_module(base) or { + if base == g.tc.cur_module { + base + } else { + '' + } + } + if mod_name.len == 0 { + return none + } + call_name := '${mod_name}.${method}' + params := g.tc.fn_param_types[call_name] or { + if call_name in g.tc.fn_ret_types && node.children_count == arg_start { + return call_name + } + return none + } + if g.module_call_arg_count_matches(call_name, params, node.children_count - arg_start) { + return call_name + } + return none +} + +fn (g &FlatGen) target_module_call_arg_start(target string, node flat.Node) int { + if !target.contains('.') || node.children_count <= 1 { + return 1 + } + base := target.all_before_last('.') + first_arg := g.a.child_node(&node, 1) + // Constant resolution can qualify the synthetic module-base argument when the + // imported module exports a same-named constant (`flags` -> `flags.flags`). It + // is still the namespace marker inserted while lowering the selector. + qualified_same_name := '${base}.${base.all_after_last('.')}' + if first_arg.kind == .ident && (first_arg.value == base || base.ends_with('.${first_arg.value}') + || first_arg.value == qualified_same_name) { + return 2 + } + return 1 +} + +fn (mut g FlatGen) isreftype_call(node flat.Node) bool { + if node.children_count != 2 { + return false + } + arg_id := g.a.child(&node, 1) + arg := g.a.nodes[int(arg_id)] + mut typ := types.Type(types.void_) + if arg.kind == .sizeof_expr { + typ = g.tc.parse_type(arg.value) + } else if arg.kind == .ident && g.isreftype_ident_is_type_name(arg.value) { + typ = g.tc.parse_type(arg.value) + } else { + typ = g.usable_expr_type(arg_id) + } + mut seen := map[string]bool{} + return g.type_is_reftype(typ, mut seen) +} + +fn (g &FlatGen) isreftype_ident_is_type_name(name string) bool { + if name.len == 0 { + return false + } + qname := g.tc.qualify_name(name) + return types.is_builtin_type_name(name) || name in g.tc.structs + || qname in g.tc.structs || name in g.tc.sum_types || qname in g.tc.sum_types + || name in g.tc.interface_names || qname in g.tc.interface_names + || name in g.tc.type_aliases || qname in g.tc.type_aliases + || (name[0] >= `A` && name[0] <= `Z`) +} + +fn (g &FlatGen) type_is_reftype(typ types.Type, mut seen map[string]bool) bool { + if typ is types.Alias { + return g.type_is_reftype(typ.base_type, mut seen) + } + if typ is types.Pointer || typ is types.Array || typ is types.Map || typ is types.String + || typ is types.Channel || typ is types.FnType || typ is types.Interface { + return true + } + if typ is types.OptionType { + return g.type_is_reftype(typ.base_type, mut seen) + } + if typ is types.ResultType { + return g.type_is_reftype(typ.base_type, mut seen) + } + if typ is types.ArrayFixed { + return g.type_is_reftype(typ.elem_type, mut seen) + } + if typ is types.SumType { + for variant in g.tc.sum_types[typ.name] or { []string{} } { + if g.type_is_reftype(g.tc.parse_type(variant), mut seen) { + return true + } + } + return false + } + if typ is types.Struct { + name := typ.name + if name in seen { + return false + } + seen[name] = true + fields := g.tc.structs[name] or { + g.tc.structs[name.all_after_last('.')] or { []types.StructField{} } + } + for field in fields { + if g.type_is_reftype(field.typ, mut seen) { + return true + } + } + } + return false +} + +// gen_call_args emits call args output for c. +fn (mut g FlatGen) gen_call_args(fn_name string, node flat.Node, start int) { + callee_name := if node.children_count > 0 { + g.call_target_name(g.a.child(&node, 0)) + } else { + '' + } + is_c_call := fn_name.starts_with('C.') || callee_name.starts_with('C.') + mut param_types := g.param_types_for(fn_name, fn_name.all_after_last('.')) + callee_is_module_selector := g.call_callee_is_module_selector(node) + mut uses_fn_value_param_types := false + if !is_c_call && !callee_is_module_selector && start == 1 && node.children_count > 0 { + if fn_value_params := g.fn_value_call_param_types(g.a.child(&node, 0)) { + param_types = fn_value_params.clone() + uses_fn_value_param_types = true + } + } + // Retry with the bare method name only when the qualified name is genuinely unresolved — + // not when it is a known function that simply takes no parameters. The short-name index + // matches any same-named function, so retrying a real 0-param fn (e.g. a static method + // `Type.build()`) would adopt an unrelated `build`'s parameters and append phantom args. + if !is_c_call && !uses_fn_value_param_types && param_types.len == 0 && fn_name.contains('.') + && fn_name !in g.tc.fn_param_types && g.cname(fn_name) !in g.tc.fn_param_types { + param_types = g.param_types_for(fn_name.all_after_last('.'), fn_name.all_after_last('.')) + } + callee_uses_specialized_generic_abi := if node.children_count > 0 { + g.call_callee_uses_specialized_generic_abi(g.a.child(&node, 0)) + } else { + false + } + concrete_optional_args := g.call_uses_concrete_optional_params(fn_name) + || g.call_uses_concrete_optional_params(g.direct_call_name(fn_name)) + || g.call_uses_concrete_optional_params(callee_name) + || g.call_uses_concrete_optional_params(g.direct_call_name(callee_name)) + || (callee_uses_specialized_generic_abi && params_have_optional_result(param_types)) + is_c_variadic_fn := g.tc.c_variadic_fns[fn_name] or { false } + is_variadic_fn := !is_c_variadic_fn && ((g.tc.fn_variadic[fn_name] or { false }) + || g.fn_decl_is_variadic(fn_name, callee_name)) + is_untyped_variadic_fn := is_variadic_fn && param_types.len > 0 + && variadic_array_is_native(param_types[param_types.len - 1]) + is_native_variadic_fn := is_c_variadic_fn || is_untyped_variadic_fn + variadic_idx := if is_variadic_fn && !is_untyped_variadic_fn && param_types.len > 0 + && param_types[param_types.len - 1] is types.Array { + param_types.len - 1 + } else { + -1 + } + typed_param_count := if is_native_variadic_fn && param_types.len > 0 + && param_types[param_types.len - 1] is types.Array { + param_types.len - 1 + } else { + param_types.len + } + num_args := node.children_count - start + is_variadic := variadic_idx >= 0 && num_args > param_types.len + for i in start .. node.children_count { + arg_idx := i - start + arg_id := g.a.child(&node, i) + arg_node := g.a.nodes[int(arg_id)] + if (param_types.len > 0 || uses_fn_value_param_types) && !is_native_variadic_fn + && variadic_idx < 0 && arg_idx >= typed_param_count { + continue + } + if i > start { + g.write(', ') + } + if arg_node.kind == .field_init && variadic_idx >= 0 && arg_idx == variadic_idx { + variadic_type := param_types[variadic_idx] + if variadic_type is types.Array { + if variadic_type.elem_type is types.Struct { + c_elem := g.tc.c_type(variadic_type.elem_type) + g.write('new_array_from_c_array(1, 1, sizeof(${c_elem}), (${c_elem}[]){') + g.gen_params_struct_arg(variadic_type.elem_type, node, i) + g.write('})') + break + } + } + } + arg_expected_is_voidptr := arg_idx >= 0 && arg_idx < typed_param_count + && type_is_void_pointer(param_types[arg_idx]) + is_storage_pointer_arg := g.call_arg_is_storage_pointer_arg(arg_idx, [fn_name, callee_name]) + if is_storage_pointer_arg && g.gen_generated_storage_pointer_arg(arg_idx, arg_id, arg_node) { + continue + } + if arg_expected_is_voidptr && !is_storage_pointer_arg && arg_node.kind == .ident + && g.node_is_fn_value_for_voidptr(arg_id, arg_node) { + g.gen_expr(arg_id) + continue + } + if arg_idx < typed_param_count + && g.gen_callback_fn_value_for_expected_type(arg_id, param_types[arg_idx]) { + continue + } + if arg_node.kind == .field_init { + // `@[params]` struct argument: trailing `key: value` args form a struct literal + ptyp := if arg_idx < typed_param_count { + param_types[arg_idx] + } else { + types.Type(types.void_) + } + g.gen_params_struct_arg(ptyp, node, i) + break + } + if g.gen_array_equality_literal_arg([fn_name, callee_name], arg_idx, arg_id, arg_node) { + continue + } + if !is_c_call && arg_idx == 0 && start == 1 && arg_node.kind == .ident + && (g.mut_receiver_arg_wants_addr(fn_name, arg_id) + || (!callee_is_module_selector && g.mut_receiver_arg_wants_addr(callee_name, arg_id))) { + param_type := g.current_param_type(arg_node.value) or { types.Type(types.void_) } + if g.local_storage_is_pointer(arg_node.value) || param_type is types.Pointer + || g.usable_expr_type(arg_id) is types.Pointer { + g.write(g.local_cname(arg_node.value)) + } else { + g.write('&') + g.gen_expr(arg_id) + } + continue + } + if arg_idx < typed_param_count { + arg_param_is_shared := + g.fn_param_is_shared_for_call(arg_idx, fn_name, callee_name, '', '') + if arg_param_is_shared + && (g.gen_shared_local_receiver_arg(arg_id) || g.gen_shared_storage_expr(arg_id)) { + continue + } + } + if arg_idx == 0 && fn_name in ['array_push', 'array__push'] { + if target := g.shared_array_payload_lvalue(arg_id) { + g.write('&${target}') + continue + } + arg_expr := g.expr_to_string(arg_id).trim_space() + if arg_expr.ends_with('->val') { + if arg_expr.starts_with('&') { + g.write(arg_expr) + continue + } + g.write('&${arg_expr}') + continue + } + } + if arg_idx == 0 && fn_name in ['array_get', 'array__get'] { + arg_expr := g.expr_to_string(arg_id).trim_space() + if storage_expr := shared_storage_from_payload_value_expr(arg_expr) { + g.write('${storage_expr}->val') + continue + } + } + if arg_idx == 1 && fn_name in ['array_push', 'array__push'] + && g.gen_shared_array_push_arg(node.value, arg_id) { + continue + } + if arg_idx == 0 && call_name_is_isnil(fn_name, callee_name) + && g.gen_isnil_fn_value_arg(arg_id, arg_node) { + continue + } + if arg_expected_is_voidptr && !is_storage_pointer_arg + && g.gen_voidptr_fn_value_arg(arg_id, arg_node) { + continue + } + if arg_node.kind == .sizeof_expr { + g.write('sizeof(${g.sizeof_target(arg_node.value)})') + continue + } + if g.gen_ierror_str_arg(fn_name, callee_name, arg_idx, arg_id) { + continue + } + if arg_idx < typed_param_count && param_types[arg_idx] !is types.Pointer + && g.gen_addressed_byvalue_arg(arg_node, param_types[arg_idx]) { + continue + } + if arg_idx < typed_param_count + && g.gen_fixed_array_pointer_lvalue_arg(arg_id, param_types[arg_idx]) { + continue + } + if g.gen_new_array_fixed_data_arg(node, start, arg_idx, arg_id, [fn_name, callee_name]) { + continue + } + if fixed := array_fixed_type(g.tc.resolve_type(arg_id)) { + g.gen_fixed_array_data_arg(arg_id, fixed) + continue + } + if arg_idx < typed_param_count { + if fixed := array_fixed_type(param_types[arg_idx]) { + g.gen_fixed_array_data_arg(arg_id, fixed) + continue + } + } + if arg_idx < typed_param_count + && g.gen_pointer_arg_from_array_literal(arg_node, param_types[arg_idx]) { + continue + } + if arg_idx < typed_param_count && g.gen_mut_sum_lvalue_arg(arg_id, param_types[arg_idx]) { + continue + } + if arg_idx < typed_param_count + && g.gen_mut_pointer_slot_arg(arg_id, arg_node, param_types[arg_idx]) { + continue + } + cb_param := if arg_idx >= 0 && arg_idx < typed_param_count { + param_types[arg_idx] + } else { + types.Type(types.void_) + } + if is_c_call && g.gen_special_c_callback_arg(fn_name, arg_idx, arg_id, cb_param) { + continue + } + if is_c_call { + if g.gen_c_va_list_macro_arg_direct(arg_idx, arg_id, fn_name, callee_name, '', '') { + continue + } + if arg_node.kind == .prefix && arg_node.op == .amp && arg_node.children_count > 0 { + inner := g.a.child_node(&arg_node, 0) + if inner.kind == .int_literal && inner.value in ['', '0'] { + g.write('0') + continue + } + } + if g.c_char_literal_arg(arg_id) { + if arg_idx < typed_param_count { + g.gen_expr_with_expected_type(arg_id, param_types[arg_idx]) + } else { + old_expected := g.expected_expr_type + g.expected_expr_type = types.Type(types.void_) + g.gen_expr(arg_id) + g.expected_expr_type = old_expected + } + } else if arg_idx < typed_param_count + && g.voidptr_value_arg_needs_address(arg_id, arg_node, g.usable_expr_type(arg_id), param_types[arg_idx], true) { + g.write('&') + g.gen_expr(arg_id) + } else { + g.gen_expr(arg_id) + } + continue + } + if variadic_idx >= 0 && arg_idx == variadic_idx { + variadic_type := param_types[variadic_idx] + if variadic_type is types.Array { + if spread_id := g.spread_arg_child(arg_node) { + g.gen_expr_with_expected_type(spread_id, variadic_type) + continue + } + } + } + if is_variadic && arg_idx == variadic_idx { + variadic_type := param_types[variadic_idx] + if variadic_type is types.Array { + g.gen_variadic_array_args(node, i, variadic_type.elem_type) + } + break + } + if variadic_idx >= 0 && arg_idx == variadic_idx && num_args == param_types.len { + arg_type := g.tc.resolve_type(arg_id) + // A struct literal can never itself be the variadic array; the checker + // propagates the expected `[]T` onto the node, masking its own type. + if arg_type !is types.Array || arg_node.kind == .struct_init { + variadic_type := param_types[variadic_idx] + if variadic_type is types.Array { + c_elem := g.tc.c_type(variadic_type.elem_type) + g.write('new_array_from_c_array(1, 1, sizeof(${c_elem}), (${c_elem}[]){') + if variadic_elem_is_voidptr(variadic_type.elem_type) { + g.gen_voidptr_variadic_arg(arg_id) + } else { + g.gen_expr_with_expected_type(arg_id, variadic_type.elem_type) + } + g.write('})') + continue + } + } + } + mut needs_addr := false + if arg_idx < typed_param_count && param_types[arg_idx] is types.Pointer + && !(arg_node.kind == .prefix && arg_node.op == .amp) + && !g.arg_is_null_pointer_literal(arg_id, arg_node) { + arg_type := g.usable_expr_type(arg_id) + arg_is_shared_local := arg_node.kind == .ident + && g.local_storage_is_shared(arg_node.value) + arg_param_is_shared := + g.fn_param_is_shared_for_call(arg_idx, fn_name, callee_name, '', '') + arg_is_pointer_param := arg_node.kind == .ident && (g.current_param_type(arg_node.value) or { + types.Type(types.void_) + }) is types.Pointer + arg_is_pointer_global := arg_node.kind == .ident && (g.global_type_for_ident(arg_node.value) or { + types.Type(types.void_) + }) is types.Pointer + value_local_mut_receiver := arg_idx == 0 && (g.method_receiver_is_mut(fn_name) + || g.method_receiver_is_mut(g.direct_call_name(fn_name))) && arg_node.kind == .ident + && !g.local_storage_is_pointer(arg_node.value) && !arg_is_pointer_param + && !arg_is_pointer_global && arg_type !is types.Pointer + value_local_mut_arg := arg_node.kind == .ident + && !g.local_storage_is_pointer(arg_node.value) && !arg_is_pointer_param + && !arg_is_pointer_global + explicit_mut_value := arg_node.is_mut && !(arg_node.kind == .ident + && (g.local_storage_is_pointer(arg_node.value) + || arg_is_pointer_param || arg_is_pointer_global)) + if g.fn_value_arg_passes_direct_to_voidptr(arg_id, arg_node, arg_type, + param_types[arg_idx]) + { + needs_addr = false + } else if arg_is_shared_local && !arg_param_is_shared + && !g.c_string_pointer_arg(arg_node, param_types[arg_idx]) { + needs_addr = true + } else if (arg_type !is types.Pointer || value_local_mut_receiver || value_local_mut_arg + || explicit_mut_value) && !g.c_string_pointer_arg(arg_node, param_types[arg_idx]) { + needs_addr = !(arg_node.kind == .ident + && (g.local_storage_is_pointer(arg_node.value) || arg_is_pointer_global)) + } + } + if !is_c_call && arg_idx < typed_param_count + && g.voidptr_value_arg_needs_address(arg_id, arg_node, g.usable_expr_type(arg_id), param_types[arg_idx], is_c_call) { + needs_addr = true + } + if arg_idx < typed_param_count && g.gen_interface_pointer_arg(arg_id, param_types[arg_idx]) { + continue + } + is_rvalue := arg_node.kind == .call + || (arg_node.kind == .index && arg_node.value == 'range') + || g.arg_is_const_ident(arg_node) + if needs_addr && g.arg_is_const_ident(arg_node) { + value_type := g.addressed_const_arg_value_type(arg_id, param_types[arg_idx]) + ct := g.tc.c_type(value_type) + g.write('(${ct}[]){') + g.gen_expr_with_expected_type(arg_id, value_type) + g.write('}') + } else if needs_addr && is_rvalue { + pt := param_types[arg_idx] + ct := g.tc.c_type(types.unwrap_pointer(pt)) + g.write('&((${ct}[]){') + g.gen_expr_with_expected_type(arg_id, types.unwrap_pointer(pt)) + g.write('})[0]') + } else if needs_addr && g.gen_mut_sum_lvalue_arg(arg_id, param_types[arg_idx]) { + // handled + } else { + if arg_idx < typed_param_count + && g.gen_interface_pointer_arg(arg_id, param_types[arg_idx]) { + continue + } + if arg_idx < typed_param_count { + if param_types[arg_idx] !is types.Pointer { + if child_id := g.addressed_byvalue_arg(arg_node) { + g.gen_expr_with_expected_type(child_id, param_types[arg_idx]) + continue + } + } + if child_id := g.addressed_rvalue_arg(arg_node) { + pt := param_types[arg_idx] + if g.gen_addressed_rvalue_arg(child_id, pt) { + continue + } + } + } + if needs_addr && arg_node.kind == .ident && g.usable_expr_type(arg_id) is types.Pointer { + g.write(g.local_cname(arg_node.value)) + continue + } + if needs_addr && arg_node.kind == .prefix && arg_node.op == .mul + && arg_node.children_count > 0 { + g.gen_expr(g.a.child(&arg_node, 0)) + continue + } + if needs_addr { + g.write('&') + } + emitted_variant := !needs_addr && arg_idx < typed_param_count + && g.gen_sum_variant_arg(arg_id, param_types[arg_idx]) + if !emitted_variant { + if arg_idx < typed_param_count + && g.gen_optional_arg_with_abi(arg_id, param_types[arg_idx], concrete_optional_args) { + // handled + } else if arg_idx < typed_param_count + && g.gen_pointer_backed_param_arg(arg_id, param_types[arg_idx]) { + // handled + } else if arg_idx < typed_param_count + && g.gen_embedded_interface_receiver(arg_id, g.usable_expr_type(arg_id), param_types[arg_idx], param_types[arg_idx] is types.Pointer) { + // handled + } else if arg_idx < typed_param_count && param_types[arg_idx] is types.Struct + && arg_node.kind == .struct_init { + g.gen_expr_with_expected_type(arg_id, param_types[arg_idx]) + } else if arg_idx < typed_param_count { + g.gen_expr_with_expected_type(arg_id, param_types[arg_idx]) + } else { + g.gen_expr(arg_id) + } + } + } + if variadic_idx >= 0 && num_args == variadic_idx { + if node.children_count > start { + g.write(', ') + } + variadic_type := param_types[variadic_idx] + if variadic_type is types.Array { + c_elem := g.tc.c_type(variadic_type.elem_type) + g.write('new_array_from_c_array(0, 0, sizeof(${c_elem}), (${c_elem}[]){0})') + } + } + } + num_provided := node.children_count - start + if !is_c_call && num_provided < typed_param_count { + mut emitted_defaults := 0 + for i in num_provided .. typed_param_count { + if g.type_contains_generic_placeholder(param_types[i]) { + continue + } + if num_provided > 0 || emitted_defaults > 0 { + g.write(', ') + } + g.gen_default_value_for_type(param_types[i]) + emitted_defaults++ + } + } +} + +fn (mut g FlatGen) gen_variadic_array_args(node flat.Node, start int, elem_type types.Type) { + mut count := 0 + mut saw_short_struct := false + for i in start .. node.children_count { + arg := g.a.child_node(&node, i) + if arg.kind == .field_init { + if !saw_short_struct { + count++ + saw_short_struct = true + } + } else { + count++ + } + } + c_elem := g.tc.c_type(elem_type) + g.write('new_array_from_c_array(${count}, ${count}, sizeof(${c_elem}), (${c_elem}[]){') + mut emitted := 0 + for i in start .. node.children_count { + arg_id := g.a.child(&node, i) + arg := g.a.nodes[int(arg_id)] + if arg.kind == .field_init { + if saw_short_struct { + if emitted > 0 { + g.write(', ') + } + g.gen_params_struct_arg(elem_type, node, i) + } + break + } + if emitted > 0 { + g.write(', ') + } + if variadic_elem_is_voidptr(elem_type) { + g.gen_voidptr_variadic_arg(arg_id) + } else { + g.gen_expr_with_expected_type(arg_id, elem_type) + } + emitted++ + } + g.write('})') +} + +fn (g &FlatGen) call_callee_is_module_selector(node flat.Node) bool { + if node.children_count == 0 { + return false + } + callee := g.a.child_node(&node, 0) + if callee.kind != .selector || callee.children_count == 0 { + return false + } + base := g.a.child_node(callee, 0) + if base.kind != .ident { + return false + } + if source_file := g.a.source_files[callee.pos.id] { + if _ := g.tc.file_imports[source_file.name + '\n' + base.value] { + return !g.selector_base_is_local_value(base.value) + } + } + if g.selector_base_is_value(base.value) { + return false + } + return g.selector_base_module(base.value) != none +} + +fn call_name_is_isnil(fn_name string, callee_name string) bool { + return fn_name == 'isnil' || fn_name == 'builtin.isnil' || fn_name.ends_with('__isnil') + || callee_name == 'isnil' || callee_name == 'builtin.isnil' + || callee_name.ends_with('__isnil') +} + +fn (g &FlatGen) call_arg_is_storage_pointer_arg(arg_idx int, names []string) bool { + for name in names { + if (name == 'array_push' || name == 'array__push' || name.ends_with('__array_push') + || name.ends_with('__array__push') || name.ends_with('.array_push') + || name.ends_with('.array__push')) && arg_idx == 1 { + return true + } + if (name == 'map__set' || name == 'map__get' || name == 'map__get_and_set' + || name.ends_with('__map__set') || name.ends_with('__map__get') + || name.ends_with('__map__get_and_set') || name.ends_with('.map__set') + || name.ends_with('.map__get') || name.ends_with('.map__get_and_set')) && arg_idx == 2 { + return true + } + } + return false +} + +fn call_arg_is_generated_storage_pointer_arg(arg_idx int, node flat.Node) bool { + if node.kind != .ident { + return false + } + if arg_idx == 1 && node.value.starts_with('__arr_val_') { + return true + } + return arg_idx == 2 + && (node.value.starts_with('__map_val_') || node.value.starts_with('__map_zero_')) +} + +fn generated_storage_pointer_arg_name(name string) bool { + return name.starts_with('__arr_val_') || name.starts_with('__map_val_') + || name.starts_with('__map_zero_') +} + +fn (mut g FlatGen) gen_generated_storage_pointer_arg(arg_idx int, arg_id flat.NodeId, arg_node flat.Node) bool { + if arg_node.kind == .ident && call_arg_is_generated_storage_pointer_arg(arg_idx, arg_node) { + g.write('&') + g.gen_expr(arg_id) + return true + } + if arg_node.kind != .prefix || arg_node.op != .amp || arg_node.children_count == 0 { + return false + } + inner_id := g.a.child(&arg_node, 0) + inner := g.a.nodes[int(inner_id)] + if inner.kind != .ident || !generated_storage_pointer_arg_name(inner.value) { + return false + } + g.gen_expr(arg_id) + return true +} + +fn (mut g FlatGen) gen_isnil_fn_value_arg(arg_id flat.NodeId, arg_node flat.Node) bool { + mut value_id := arg_id + mut value_node := arg_node + if arg_node.kind == .prefix && arg_node.op == .amp && arg_node.children_count > 0 { + value_id = g.a.child(&arg_node, 0) + value_node = g.a.nodes[int(value_id)] + } + if !g.node_is_fn_value_for_voidptr(value_id, value_node) { + return false + } + g.gen_expr(value_id) + return true +} + +fn (mut g FlatGen) gen_voidptr_fn_value_arg(arg_id flat.NodeId, arg_node flat.Node) bool { + mut value_id := arg_id + mut value_node := arg_node + for value_node.children_count > 0 { + if value_node.kind in [.cast_expr, .paren] + || (value_node.kind == .prefix && value_node.op == .amp) { + value_id = g.a.child(&value_node, 0) + value_node = g.a.nodes[int(value_id)] + continue + } + break + } + if !g.node_is_fn_value_for_voidptr(value_id, value_node) { + voidptr_type := types.Type(types.Pointer{ + base_type: types.Type(types.void_) + }) + if !g.voidptr_method_value_arg(value_id, voidptr_type) { + return false + } + } + g.gen_expr(value_id) + return true +} + +fn (g &FlatGen) node_is_fn_value_for_voidptr(id flat.NodeId, node flat.Node) bool { + if node.typ.starts_with('fn(') || node.typ.starts_with('fn (') { + return true + } + if type_is_fn_value(g.usable_expr_type(id)) { + return true + } + if node.kind == .ident { + if raw := g.local_storage_raw_type(node.value) { + if raw.starts_with('fn(') || raw.starts_with('fn (') { + return true + } + } + if ct := g.local_storage_c_type(node.value) { + if ct.starts_with('_fn_ptr_') || ct.starts_with('fn_ptr:') { + return true + } + } + } + return false +} + +fn (g &FlatGen) fn_value_arg_passes_direct_to_voidptr(arg_id flat.NodeId, arg_node flat.Node, actual types.Type, expected types.Type) bool { + if !type_is_void_pointer(expected) { + return false + } + if type_is_fn_value(actual) { + return true + } + if arg_node.typ.starts_with('fn(') || arg_node.typ.starts_with('fn (') { + return true + } + if arg_node.kind == .ident { + if raw := g.local_storage_raw_type(arg_node.value) { + if raw.starts_with('fn(') || raw.starts_with('fn (') { + return true + } + } + if ct := g.local_storage_c_type(arg_node.value) { + if ct.starts_with('_fn_ptr_') || ct.starts_with('fn_ptr:') { + return true + } + } + } + if int(arg_id) >= 0 && int(arg_id) < g.a.nodes.len { + node := g.a.nodes[int(arg_id)] + return node.typ.starts_with('fn(') || node.typ.starts_with('fn (') + } + return false +} + +fn type_is_fn_value(typ types.Type) bool { + if typ is types.FnType { + return true + } + if typ is types.Alias { + return type_is_fn_value(typ.base_type) + } + return false +} + +fn type_is_void_pointer(typ types.Type) bool { + if typ.name() == 'voidptr' { + return true + } + if typ is types.Pointer { + base := if typ.base_type is types.Alias { typ.base_type.base_type } else { typ.base_type } + return base is types.Void + } + if typ is types.Alias { + return type_is_void_pointer(typ.base_type) + } + return false +} + +@[direct_array_access] +fn (mut g FlatGen) gen_transformed_method_ident_call(id flat.NodeId, node flat.Node, fn_node flat.Node) bool { + if fn_node.kind != .ident || node.children_count < 2 || !fn_node.value.contains('.') { + return false + } + // A transformed module selector can retain its module ident as the first child. + // Let module-call generation discard that pseudo-argument before considering a + // same-named mutable receiver method. + if _ := g.target_module_call_name(fn_node.value, node) { + if g.target_module_call_arg_start(fn_node.value, node) == 2 { + return false + } + } + receiver_id := g.a.child(&node, 1) + emitted_name := g.direct_call_name_for_call_node(id, node, fn_node.value) + mut params := g.param_types_for(emitted_name, emitted_name) + if params.len == 0 { + params = g.param_types_for(fn_node.value, fn_node.value.all_after_last('.')) + } + if params.len == 0 { + return false + } + receiver_owner := fn_node.value.all_before_last('.').all_after_last('.') + expected_receiver_name := + g.type_lookup_name(types.unwrap_pointer(params[0])).all_after_last('.') + if receiver_owner.len == 0 || expected_receiver_name != receiver_owner { + return false + } + receiver_wants_ptr := params[0] is types.Pointer + || g.mut_receiver_arg_wants_addr(fn_node.value, receiver_id) + || g.mut_receiver_arg_wants_addr(emitted_name, receiver_id) + receiver_wants_shared := g.fn_param_is_shared_for_call(0, fn_node.value, emitted_name, + g.cname(fn_node.value), g.cname(emitted_name)) + receiver := g.a.nodes[int(receiver_id)] + receiver_type := g.receiver_base_type(receiver_id) + receiver_is_shared_payload := g.shared_local_arg_c_expr(receiver_id) != none + || g.shared_payload_deref_storage_c_expr(receiver_id) != none + receiver_is_ptr := !receiver_is_shared_payload + && (receiver_type is types.Pointer || g.receiver_ident_storage_is_pointer(receiver_id)) + if fn_node.value.ends_with('.str') && !receiver_wants_ptr && receiver_is_ptr { + mut stack := []string{} + receiver_expr := g.expr_to_string(receiver_id) + if pointer_str := g.interface_pointer_str_expr(types.unwrap_pointer(params[0]), + receiver_expr, true, mut stack) + { + g.write(pointer_str) + return true + } + } + g.write(emitted_name) + g.write('(') + if receiver_wants_shared + && (g.gen_shared_local_receiver_arg(receiver_id) || g.gen_shared_storage_expr(receiver_id)) { + // handled + } else if g.gen_embedded_method_receiver(receiver_id, receiver_type, params[0], + receiver_wants_ptr) + { + // handled + } else if receiver_wants_ptr && g.gen_deref_method_receiver(receiver_id, params[0]) { + // handled + } else { + materialize_receiver := receiver_wants_ptr && !receiver_is_ptr && receiver.kind == .call + if materialize_receiver { + receiver_ct := g.tc.c_type(types.unwrap_pointer(receiver_type)) + g.write('&((${receiver_ct}[]){') + } else if receiver_wants_ptr && !receiver_is_ptr { + g.write('&') + } else if !receiver_wants_ptr && receiver_is_ptr { + g.write('*') + } + g.gen_expr(receiver_id) + if materialize_receiver { + g.write('})[0]') + } + } + resolved_call := g.tc.resolved_call_name(id) or { '' } + callee_has_implicit_ctx := g.call_has_implicit_veb_ctx([resolved_call, fn_node.value, + emitted_name]) + may_forward_ctx := callee_has_implicit_ctx && params.len > 1 + && node.children_count - 1 < params.len && g.is_implicit_veb_ctx_param(params[1]) + current_ctx_name := if may_forward_ctx { g.cur_veb_ctx_name() or { '' } } else { '' } + forward_ctx := may_forward_ctx && current_ctx_name.len > 0 + if forward_ctx { + g.write(', ${g.cname(current_ctx_name)}') + } + mut next_param_idx := if forward_ctx { 2 } else { 1 } + concrete_optional_args := g.call_uses_concrete_optional_params(emitted_name) + || g.call_uses_concrete_optional_params(fn_node.value) + for i in 2 .. node.children_count { + arg_id := g.a.child(&node, i) + param_idx := i - 1 + (if forward_ctx { 1 } else { 0 }) + if param_idx >= params.len { + continue + } + g.write(', ') + if !g.gen_optional_arg_with_abi(arg_id, params[param_idx], concrete_optional_args) { + g.gen_arg_for_expected_type(arg_id, params[param_idx]) + } + next_param_idx = param_idx + 1 + } + if callee_has_implicit_ctx { + // Dynamic veb method calls are checked before their concrete method is + // selected. A branch that omits route parameters still has to be valid C even + // when a surrounding reflected condition makes it unreachable at runtime. + for param_idx in next_param_idx .. params.len { + g.write(', ') + g.gen_default_value_for_type(params[param_idx]) + } + } + g.write(')') + return true +} + +fn (mut g FlatGen) gen_ierror_str_arg(fn_name string, callee_name string, arg_idx int, arg_id flat.NodeId) bool { + if arg_idx != 0 { + return false + } + // C-name sanitization cannot turn an unrelated name into `IError__str`. + // Reject virtually every call before probing the generator's name cache. + if fn_name != 'str' && !fn_name.contains('IError') && !callee_name.contains('IError') { + return false + } + is_ierror_str := g.cname(fn_name) == 'IError__str' || g.cname(callee_name) == 'IError__str' + || fn_name == 'IError.str' || callee_name == 'IError.str' + if !is_ierror_str && fn_name != 'str' { + return false + } + arg_type := g.usable_expr_type(arg_id) + clean_type := types.unwrap_pointer(arg_type) + if !g.is_ierror_type_name(clean_type.name()) { + return false + } + arg_node := g.a.nodes[int(arg_id)] + if arg_node.kind == .prefix && arg_node.op == .amp { + g.gen_expr(g.a.child(&arg_node, 0)) + return true + } + if arg_type is types.Pointer { + g.write('*') + } + g.gen_expr(arg_id) + return true +} + +fn variadic_elem_is_voidptr(typ types.Type) bool { + if typ is types.Pointer { + return typ.base_type is types.Void + } + return false +} + +fn variadic_array_is_native(typ types.Type) bool { + if typ is types.Array { + return typ.elem_type is types.Void + } + return false +} + +fn c_va_list_macro_arg_passes_direct(arg_idx int, names ...string) bool { + for name in names { + cname := name.trim_space().trim_string_left('C.').all_after_last('__') + if cname in ['va_start', 'va_end'] && arg_idx == 0 { + return true + } + if cname == 'va_copy' && arg_idx < 2 { + return true + } + } + return false +} + +fn (mut g FlatGen) gen_c_va_list_macro_arg_direct(arg_idx int, arg_id flat.NodeId, names ...string) bool { + if !c_va_list_macro_arg_passes_direct(arg_idx, ...names) { + return false + } + mut value_id := arg_id + for { + value := g.a.node(value_id) + if value.kind == .ident { + g.write(g.local_cname(value.value)) + return true + } + if value.children_count != 1 || value.kind !in [.paren, .prefix] { + return false + } + value_id = g.a.child(value, 0) + } + return false +} + +fn (mut g FlatGen) gen_voidptr_variadic_arg(arg_id flat.NodeId) { + actual := g.tc.resolve_type(arg_id) + if voidptr_variadic_type_passes_direct(actual) { + g.write('(voidptr)(') + g.gen_expr_with_expected_type(arg_id, actual) + g.write(')') + return + } + storage_ct := g.voidptr_variadic_storage_c_type(actual) + g.write('(voidptr)&((${storage_ct}[]){') + g.gen_expr_with_expected_type(arg_id, actual) + g.write('}[0])') +} + +fn voidptr_variadic_type_passes_direct(typ types.Type) bool { + if typ is types.Alias { + return voidptr_variadic_type_passes_direct(typ.base_type) + } + return typ is types.Pointer || typ is types.Nil +} + +fn (g &FlatGen) voidptr_variadic_storage_c_type(actual types.Type) string { + mut clean := actual + for _ in 0 .. 8 { + if clean is types.Alias { + clean = clean.base_type + continue + } + break + } + if clean is types.Char { + return 'int' + } + if clean is types.Primitive { + if clean.props.has(.integer) && clean.size < 32 { + return 'int' + } + if clean.props.has(.float) && clean.size == 32 { + return 'double' + } + } + return g.tc.c_type(clean) +} + +fn raw_sizeof_arg_value(value string) ?string { + clean := trimmed_space(value) + if !clean.starts_with('sizeof(') || !clean.ends_with(')') { + return none + } + return clean['sizeof('.len..clean.len - 1].trim_space() +} + +fn raw_sizeof_needs_normalization(value string) bool { + return value.starts_with('fn_ptr:') || value.starts_with('Array_fixed_') +} + +// is_flag_enum_method reports whether is flag enum method applies in c. +fn (g &FlatGen) is_flag_enum_method(fn_node &flat.Node) bool { + if fn_node.kind != .selector { + return false + } + method := fn_node.value + if method !in ['has', 'all', 'set', 'clear', 'toggle', 'set_all', 'clear_all', 'is_empty'] { + return false + } + base_type := g.tc.resolve_type(g.a.child(fn_node, 0)) + clean := types.unwrap_pointer(base_type) + if clean is types.Enum { + return clean.is_flag || clean.name in g.tc.flag_enums + } else if clean is types.Primitive { + return clean.props.has(.integer) + } else if clean is types.Unknown { + return true + } + return false +} + +// gen_flag_enum_call emits flag enum call output for c. +fn (mut g FlatGen) gen_flag_enum_call(node flat.Node) { + fn_node := g.a.child_node(&node, 0) + method := fn_node.value + base_id := g.a.child(fn_node, 0) + base_type := types.unwrap_pointer(g.tc.resolve_type(base_id)) + match method { + 'has' { + g.write('((') + g.gen_expr(base_id) + g.write(' & ') + if node.children_count > 1 { + g.gen_flag_enum_arg(g.a.child(&node, 1), base_type) + } + g.write(') != 0)') + } + 'all' { + g.write('((') + g.gen_expr(base_id) + g.write(' & (') + if node.children_count > 1 { + g.gen_flag_enum_arg(g.a.child(&node, 1), base_type) + } + g.write(')) == (') + if node.children_count > 1 { + g.gen_flag_enum_arg(g.a.child(&node, 1), base_type) + } + g.write('))') + } + 'set' { + g.gen_expr(base_id) + g.write(' |= ') + if node.children_count > 1 { + g.gen_flag_enum_arg(g.a.child(&node, 1), base_type) + } + } + 'clear' { + g.gen_expr(base_id) + g.write(' &= ~(') + if node.children_count > 1 { + g.gen_flag_enum_arg(g.a.child(&node, 1), base_type) + } + g.write(')') + } + 'toggle' { + g.gen_expr(base_id) + g.write(' ^= ') + if node.children_count > 1 { + g.gen_flag_enum_arg(g.a.child(&node, 1), base_type) + } + } + 'set_all' { + g.gen_expr(base_id) + g.write(' = ${g.flag_enum_mask_expr(base_type.name())}') + } + 'clear_all' { + g.gen_expr(base_id) + g.write(' = 0') + } + 'is_empty' { + g.write('(') + g.gen_expr(base_id) + g.write(' == 0)') + } + else {} + } +} + +fn (mut g FlatGen) gen_flag_enum_zero_call(id flat.NodeId, fn_node flat.Node, node flat.Node) bool { + if fn_node.kind != .selector || fn_node.value != 'zero' || node.children_count != 1 { + return false + } + base := g.a.child_node(&fn_node, 0) + if base.kind != .ident || g.selector_base_is_value(base.value) + || g.selector_call_resolves_to_user_fn(id, base.value, fn_node.value) { + return false + } + enum_name := g.enum_selector_base_name(base.value) or { return false } + if enum_name !in g.tc.flag_enums { + return false + } + mut typ := g.usable_expr_type(id) + if typ is types.Alias { + typ = typ.base_type + } + if typ is types.Enum && (typ.name == enum_name || g.tc.qualify_name(typ.name) == enum_name) { + g.write('((${g.tc.c_type(typ)})0)') + return true + } + return false +} + +fn (mut g FlatGen) gen_flag_enum_from_call(id flat.NodeId, fn_node flat.Node, node flat.Node) bool { + if fn_node.kind != .selector || fn_node.value != 'from' || fn_node.children_count == 0 + || node.children_count < 2 { + return false + } + base_id := g.a.child(&fn_node, 0) + base := g.a.nodes[int(base_id)] + if base.kind == .ident && g.selector_base_is_value(base.value) { + return false + } + enum_name := g.enum_from_selector_base_name(base_id) or { return false } + if g.selector_call_resolves_to_user_fn(id, enum_name, fn_node.value) { + return false + } + is_flag := enum_name in g.tc.flag_enums + enum_info := types.Enum{ + name: enum_name + is_flag: is_flag + } + enum_type := types.Type(enum_info) + ct := g.optional_type_name(types.Type(types.OptionType{ + base_type: enum_type + })) + value_ct := g.enum_value_c_type(enum_info) + storage_ct := g.enum_storage_c_type(enum_info) + arg := g.expr_to_string(g.a.child(&node, 1)) + value_tmp := g.tmp_name() + ok_tmp := g.tmp_name() + mut valid_expr := '' + if is_flag { + mask := g.flag_enum_mask_expr(enum_name) + valid_expr = '((${value_tmp} & ~((u64)${mask})) == 0)' + } else { + fields := g.enum_fields_for_type(enum_name) or { return false } + mut values := []string{cap: fields.len} + for field in fields { + if value := g.enum_value_expr_for_type(enum_name, field) { + values << '(${value_tmp} == (u64)(${value}))' + } + } + if values.len == 0 { + return false + } + valid_expr = '(${values.join(' || ')})' + } + g.write('({ u64 ${value_tmp} = (u64)(${arg}); bool ${ok_tmp} = ${valid_expr}; (${ct}){.ok = ${ok_tmp}, .value = (${value_ct})(${ok_tmp} ? (${storage_ct})${value_tmp} : (${storage_ct})0), .err = (IError){._typ = 0, ._object = NULL, .message = (string){.str = (u8*)"invalid value", .len = 13, .is_lit = 1}, .code = 0}}; })') + return true +} + +fn (g &FlatGen) enum_from_selector_base_name(base_id flat.NodeId) ?string { + base := g.a.nodes[int(base_id)] + if base.kind == .ident { + return g.enum_selector_base_name(base.value) + } + if base.kind != .selector || base.children_count == 0 { + return none + } + module_node := g.a.child_node(&base, 0) + if module_node.kind != .ident || g.selector_base_is_value(module_node.value) { + return none + } + module_name := g.selector_base_module(module_node.value) or { return none } + return g.enum_selector_base_name('${module_name}.${base.value}') +} + +fn (g &FlatGen) selector_call_resolves_to_user_fn(id flat.NodeId, base_name string, method string) bool { + if resolved := g.tc.resolved_call_name(id) { + if g.fn_key_registered(resolved) { + return true + } + } + if _ := g.static_method_fn_name(base_name, method) { + return true + } + return false +} + +fn (g &FlatGen) flag_enum_mask_expr(enum_name string) string { + if _ := g.enum_backing_info(enum_name) { + fields := g.enum_fields_for_type(enum_name) or { return '0' } + mut parts := []string{cap: fields.len} + for field in fields { + if expr := g.enum_value_expr_for_type(enum_name, field) { + parts << expr + } + } + if parts.len == 0 { + return '0' + } + return '(${parts.join(' | ')})' + } + return '${g.flag_enum_mask(enum_name)}' +} + +fn (g &FlatGen) flag_enum_mask(enum_name string) int { + mut mask := 0 + prefix := '${enum_name}.' + for key, value in g.enum_vals { + if key.starts_with(prefix) { + mask |= value + } + } + return mask +} + +// gen_flag_enum_arg emits flag enum arg output for c. +fn (mut g FlatGen) gen_flag_enum_arg(arg_id flat.NodeId, base_type types.Type) { + if base_type is types.Enum { + g.gen_expr_with_expected_type(arg_id, base_type) + } else { + g.gen_expr(arg_id) + } +} + +// is_generic_type reports whether is generic type applies in c. +fn is_generic_type(typ string) bool { + t := typ.trim_left('&?!') + return t.len == 1 && t[0] >= `A` && t[0] <= `Z` +} + +// has_generic_params reports whether has generic params applies in c. +fn (g &FlatGen) has_generic_params(node flat.Node) bool { + for i in 0 .. node.children_count { + child := g.a.child_node(&node, i) + if child.kind != .param { + if g.prefix_param_scan { + break + } + continue + } + if is_generic_type(child.typ) { + return true + } + } + return is_generic_type(node.typ) +} + +fn (g &FlatGen) addressed_rvalue_arg(arg_node flat.Node) ?flat.NodeId { + if arg_node.kind != .prefix || arg_node.op != .amp || arg_node.children_count == 0 { + return none + } + child_id := g.a.child(&arg_node, 0) + child := g.a.nodes[int(child_id)] + if child.kind == .call && g.array_accessor_call_is_lvalue(child) { + return none + } + if child.kind == .call || (child.kind == .index && child.value == 'range') { + return child_id + } + return none +} + +fn (g &FlatGen) array_accessor_call_is_lvalue(node flat.Node) bool { + if node.kind != .call || node.children_count == 0 { + return false + } + callee := g.a.child_node(&node, 0) + if callee.kind != .selector || callee.value !in ['first', 'last'] || callee.children_count == 0 { + return false + } + base_id := g.a.child(callee, 0) + base_type := types.unwrap_pointer(g.usable_expr_type(base_id)) + return array_like_type(base_type) != none +} + +fn (mut g FlatGen) gen_array_accessor_lvalue_address(id flat.NodeId, node flat.Node) bool { + if !g.array_accessor_call_is_lvalue(node) { + return false + } + g.write('&(') + g.gen_expr(id) + g.write(')') + return true +} + +fn (g &FlatGen) addressed_byvalue_arg(arg_node flat.Node) ?flat.NodeId { + if arg_node.kind != .prefix || arg_node.op != .amp || arg_node.children_count == 0 { + return none + } + child_id := g.a.child(&arg_node, 0) + child := g.a.nodes[int(child_id)] + if child.kind in [.struct_init, .cast_expr, .call] + || (child.kind == .index && child.value == 'range') { + return child_id + } + return none +} + +fn (mut g FlatGen) gen_addressed_byvalue_arg(arg_node flat.Node, expected types.Type) bool { + child_id := g.addressed_byvalue_arg(arg_node) or { return false } + child := g.a.nodes[int(child_id)] + if child.kind == .struct_init { + g.gen_struct_init(child_id) + } else { + g.gen_expr_with_expected_type(child_id, expected) + } + return true +} + +fn (mut g FlatGen) gen_addressed_rvalue_arg(child_id flat.NodeId, pt types.Type) bool { + if pt !is types.Pointer { + return false + } + if g.c_typedef_nil_call(child_id) { + g.write('NULL') + return true + } + ct := g.tc.c_type(types.unwrap_pointer(pt)) + g.write('&((${ct}[]){') + g.gen_expr_with_expected_type(child_id, types.unwrap_pointer(pt)) + g.write('})[0]') + return true +} + +fn (mut g FlatGen) gen_mut_pointer_slot_arg(arg_id flat.NodeId, arg_node flat.Node, expected types.Type) bool { + if expected !is types.Pointer { + return false + } + // A mutable parameter is already the address of its caller-owned storage. In + // particular, `mut p &T` is represented as `T**`; forwarding `mut p` to + // another such parameter must pass that slot directly, not read `*p` first. + if arg_node.is_mut && arg_node.kind == .ident && g.current_param_is_mut(arg_node.value) { + g.write(g.cname(arg_node.value)) + return true + } + // Transform lowers a forwarded `mut p` argument to the lvalue `*p`. When p + // is itself a mutable parameter, its C variable is already the slot expected + // by the callee, so emitting the lowered dereference would lose one level. + if arg_node.kind == .prefix && arg_node.op == .mul && arg_node.children_count == 1 { + child := g.a.child_node(&arg_node, 0) + if child.kind == .ident && g.current_param_is_mut(child.value) + && !g.current_param_is_mut_pointer(child.value) { + param_type := g.current_param_type(child.value) or { types.Type(types.void_) } + if g.tc.c_type(param_type) == g.tc.c_type(expected) { + g.write(g.cname(child.value)) + return true + } + } + } + expected_base := (expected as types.Pointer).base_type + + if !c_type_is_pointer_like(expected_base) { + return false + } + if arg_node.is_mut && arg_node.kind == .ident { + arg_type := g.usable_expr_type(arg_id) + if g.tc.c_type(arg_type) == g.tc.c_type(expected_base) { + if g.current_param_is_mut(arg_node.value) { + g.gen_expr(arg_id) + } else { + g.write('&') + g.gen_expr(arg_id) + } + return true + } + if g.local_storage_is_pointer(arg_node.value) { + local_ct := g.local_storage_c_type(arg_node.value) or { '' } + if local_ct == g.tc.c_type(expected_base) { + g.write('&') + g.gen_expr(arg_id) + return true + } + } + } + if arg_node.is_mut && (arg_node.kind in [.index, .selector, .paren] + || (arg_node.kind == .prefix && arg_node.op == .mul)) { + arg_type := g.usable_expr_type(arg_id) + if g.tc.c_type(arg_type) == g.tc.c_type(expected_base) { + if arg_node.kind == .prefix && arg_node.op == .mul && arg_node.children_count > 0 { + g.gen_expr(g.a.child(&arg_node, 0)) + } else if arg_node.kind == .index && arg_node.value == 'range' { + ct := g.tc.c_type(expected_base) + g.write('&((${ct}[]){') + g.gen_expr_with_expected_type(arg_id, expected_base) + g.write('})[0]') + } else { + g.write('&') + g.gen_expr(arg_id) + } + return true + } + } + if arg_node.kind != .prefix || arg_node.op != .amp || arg_node.children_count == 0 { + return false + } + arg_type := g.usable_expr_type(arg_id) + child_id := g.a.child(&arg_node, 0) + child_type := g.usable_expr_type(child_id) + if child_type is types.Pointer && g.tc.c_type(child_type) == g.tc.c_type(expected_base) { + return false + } + if g.tc.c_type(arg_type) != g.tc.c_type(expected_base) { + return false + } + ct := g.tc.c_type(expected_base) + g.write('&((${ct}[]){') + g.gen_expr_with_expected_type(arg_id, expected_base) + g.write('})[0]') + return true +} + +fn (g &FlatGen) c_typedef_nil_call(id flat.NodeId) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(id)] + if node.kind != .call || node.children_count != 2 || g.c_typedef_cast_call_name(node).len == 0 { + return false + } + arg := g.a.child_node(&node, 1) + return arg.kind == .nil_literal +} + +fn (g &FlatGen) spread_arg_child(arg_node flat.Node) ?flat.NodeId { + if arg_node.kind == .prefix && arg_node.value == '...' && arg_node.children_count > 0 { + return g.a.child(&arg_node, 0) + } + return none +} + +fn (g &FlatGen) arg_is_const_ident(arg_node flat.Node) bool { + if arg_node.kind == .selector { + return g.const_ref_name_from_node(arg_node).len > 0 + || g.const_type_for_arg_node(arg_node) != none + } + if arg_node.kind != .ident || arg_node.value.len == 0 { + return false + } + if _ := g.current_param_type(arg_node.value) { + return false + } + const_name := g.const_ref_name(arg_node.value) + if owner := g.tc.cur_scope.lookup_owner(arg_node.value) { + key := owner.storage_key() + if !owner.belongs_to_scope(g.tc.file_scope) && key.len > 0 + && (key in g.local_c_type_by_owner || key in g.local_raw_type_by_owner + || key in g.shadowed_global_locals) { + return false + } + } + if const_name.len > 0 { + return true + } + if _ := g.const_ident_type(arg_node.value) { + return true + } + return false +} + +// find_prim_method resolves find prim method information for c. +fn (g &FlatGen) find_prim_method(method string) string { + if 'u8.${method}' in g.tc.fn_param_types { + return g.cname('u8.${method}') + } + if 'int.${method}' in g.tc.fn_param_types { + return g.cname('int.${method}') + } + if 'i64.${method}' in g.tc.fn_param_types { + return g.cname('i64.${method}') + } + if 'u32.${method}' in g.tc.fn_param_types { + return g.cname('u32.${method}') + } + if 'u64.${method}' in g.tc.fn_param_types { + return g.cname('u64.${method}') + } + return '' +} + +// find_alias_method converts find alias method data for c. +fn (g &FlatGen) find_alias_method(target string, method string) ?string { + cache_key := '${target}\n${method}' + mut cache := g.alias_method_cache + if !isnil(cache) { + if cached := cache.get(cache_key) { + if cached.len > 0 { + return cached + } + return none + } + } + mut fallback := '' + for alias, alias_target in g.tc.type_aliases { + if alias_target != target { + continue + } + alias_method := '${alias}.${method}' + if alias_method !in g.tc.fn_param_types { + if alias.contains('.') { + short_method := '${alias.all_after_last('.')}.${method}' + if short_method in g.tc.fn_param_types { + if !isnil(cache) { + cache.put(cache_key, alias_method) + } + return alias_method + } + } + continue + } + if alias.contains('.') { + if !isnil(cache) { + cache.put(cache_key, alias_method) + } + return alias_method + } + if fallback.len == 0 { + fallback = alias_method + } + } + if fallback.len > 0 { + if !isnil(cache) { + cache.put(cache_key, fallback) + } + return fallback + } + if !isnil(cache) { + cache.put(cache_key, '') + } + return none +} + +// gen_sum_variant_arg emits sum variant arg output for c. +fn (mut g FlatGen) gen_sum_variant_arg(arg_id flat.NodeId, expected types.Type) bool { + actual0 := types.unwrap_pointer(g.tc.resolve_type(arg_id)) + mut actual := actual0 + if actual0 is types.Alias { + actual = actual0.base_type + } + expected0 := expected + mut expected_type := expected0 + if expected0 is types.Alias { + expected_type = expected0.base_type + } + if expected_type is types.SumType { + return false + } + if actual !is types.SumType { + return false + } + sum_type := actual as types.SumType + sum_name := sum_type.name + variant := g.resolve_variant(sum_name, expected_type.name()) + variants := g.tc.sum_types[sum_name] or { return false } + if variant !in variants { + return false + } + is_ptr_arg := g.tc.resolve_type(arg_id) is types.Pointer + is_ref_variant := g.variant_references_sum(variant, sum_name) + if is_ref_variant { + g.write('(*') + } + g.gen_expr(arg_id) + if is_ptr_arg { + g.write('->') + } else { + g.write('.') + } + g.write(g.sum_field_name(variant)) + if is_ref_variant { + g.write(')') + } + return true +} + +// clone_parallel_type_checker builds a per-worker TypeChecker for parallel codegen. +// +// During codegen the checker's lookup tables are READ-ONLY: cgen only ever assigns the +// scalar `cur_file`/`cur_module` fields, and the read paths it uses (expr_type, c_type, +// parse_type, resolve_type, cached_resolved_call) never write into the big maps — the only +// memoizing write is into `type_cache`, which is left nil here so workers take the uncached +// path. V maps and arrays are reference types, so the read-only tables are SHARED by +// reference (no `.clone()`), exactly like the already-shared `a` FlatAst. This avoids +// deep-copying the program-wide `expr_type_*`/`structs`/signature tables once per worker, +// which was the bulk of parallel cgen's extra RAM and serial setup time. +// +// Only genuinely per-worker mutable state is given its own copy: the scope chain (gen pushes +// child scopes) and `errors` (avoid a concurrent append race, though gen does not emit any). +fn (g &FlatGen) clone_parallel_type_checker() &types.TypeChecker { + return g.tc.fork_for_parallel_codegen() +} + +// forward_decls supports forward decls handling for FlatGen. +fn (mut g FlatGen) forward_decls() { + items := g.ensure_fn_gen_items() + mut forwarded_exports := []string{cap: g.a.export_fn_names.len} + if !g.scope_parallel_workers { + g.forward_decl_items(items, mut forwarded_exports) + if g.needs_no_main_runtime_init_caller() { + g.writeln('static void _vno_main_init_caller(void);') + } + if g.is_shared { + g.writeln('void _vcleanup(void);') + g.writeln('void _vinit_caller(void);') + g.writeln('void _vcleanup_caller(void);') + } + g.writeln('') + return + } + // Compiler-sized output is still only a few hundred KiB per 1024-item + // batch. Amortize checker forks and map snapshots across that larger unit. + for start := 0; start < items.len; start += 1024 { + end := if start + 1024 < items.len { start + 1024 } else { items.len } + // Map lookups below can return batch-owned string values. Do not retain them after the + // scratch arena is freed; duplicate compatible C prototypes across batches are harmless. + forwarded_exports.clear() + output_sb := g.sb + master_tc := g.tc + master_c_name_cache := g.c_name_cache + master_import_alias_cache := g.import_alias_cache + master_enum_selector_cache := g.enum_selector_cache + master_enum_method_cache := g.enum_method_cache + master_qualified_enum_method_cache := g.qualified_enum_method_cache + mut master_concrete_optional_params := g.cur_concrete_optional_params.move() + mut master_needed_optional_types := g.needed_optional_types.move() + mut master_fn_ptr_types := g.fn_ptr_types.move() + mut master_used_fn_ptr_types := g.used_fn_ptr_types.move() + scratch_scope := cgen_worker_scope_begin(true) + g.sb = strings.new_builder(16384) + g.line_start = true + g.tc = g.clone_parallel_type_checker() + g.c_name_cache = &CNameCache{} + // Context-cache entries can own strings allocated by this disposable + // batch. Disable them until the master caches are restored. + g.import_alias_cache = unsafe { nil } + g.enum_selector_cache = unsafe { nil } + g.enum_method_cache = unsafe { nil } + g.qualified_enum_method_cache = unsafe { nil } + g.cur_concrete_optional_params = map[string]bool{} + g.needed_optional_types = map[string]string{} + g.fn_ptr_types = master_fn_ptr_types.clone() + g.used_fn_ptr_types = map[string]bool{} + g.forward_decl_items(items[start..end], mut forwarded_exports) + mut batch_output := unsafe { g.sb.reuse_as_plain_u8_array() } + cgen_worker_scope_leave(scratch_scope) + g.sb = output_sb + g.line_start = true + g.tc = master_tc + g.c_name_cache = master_c_name_cache + g.import_alias_cache = master_import_alias_cache + g.enum_selector_cache = master_enum_selector_cache + g.enum_method_cache = master_enum_method_cache + g.qualified_enum_method_cache = master_qualified_enum_method_cache + g.cur_concrete_optional_params = master_concrete_optional_params.move() + g.needed_optional_types = master_needed_optional_types.move() + g.fn_ptr_types = master_fn_ptr_types.move() + g.used_fn_ptr_types = master_used_fn_ptr_types.move() + unsafe { g.sb.write_ptr(batch_output.data, batch_output.len) } + unsafe { batch_output.free() } + cgen_worker_scope_free(scratch_scope) + } + if g.needs_no_main_runtime_init_caller() { + g.writeln('static void _vno_main_init_caller(void);') + } + if g.is_shared { + g.writeln('void _vcleanup(void);') + g.writeln('void _vinit_caller(void);') + g.writeln('void _vcleanup_caller(void);') + } + g.writeln('') +} + +fn (mut g FlatGen) forward_decl_items(items []FlatFnGenItem, mut forwarded_exports []string) { + for item in items { + node := g.a.nodes[int(item.node_id)] + // The entry `main` becomes the C `main()` and needs no prototype, but under + // `-d no_main` it is renamed to an ordinary symbol (`main__main`) that other + // functions can call, so it must be forward-declared like any other function. + if is_main_fn_in_main_module(item.module, node.value) && g.test_files.len == 0 + && !g.suppress_main { + continue + } + qfn := item.c_name + if qfn in forwarded_exports { + continue + } + g.tc.cur_file = item.file + g.tc.cur_module = item.module + ret_type := g.fn_node_return_type(node, item.module) + concrete_optional := g.is_program_specialization_fn_node(node, int(item.node_id), + item.module) + if export_name := g.export_fn_name_in_module(item.module, node.value) { + if export_name == qfn { + g.write(g.exported_symbol_attribute()) + } + } + g.write(g.fn_return_type_name_for_context(ret_type, concrete_optional)) + g.write(' ') + g.write(qfn) + g.write('(') + g.write_fn_node_params(node) + g.writeln(');') + if !g.object_file_mode { + if export_name := g.export_fn_name_in_module(item.module, node.value) { + if export_name != qfn && export_name !in forwarded_exports { + forwarded_exports << export_name + g.write(g.exported_symbol_attribute()) + g.write(g.fn_return_type_name_for_context(ret_type, concrete_optional)) + g.write(' ') + g.write(export_name) + g.write('(') + g.write_fn_node_params(node) + g.writeln(');') + } + } + } + } +} + +fn (mut g FlatGen) cached_header_forward_decls() { + mut cur_file := '' + mut cur_module := '' + mut items := []FlatFnGenItem{} + for node_idx in g.top_level_nodes() { + node := g.a.nodes[node_idx] + if node.kind == .file { + cur_file = node.value + cur_module = '' + continue + } + if node.kind == .module_decl { + cur_module = node.value + continue + } + if node.kind != .fn_decl || !cur_file.ends_with('.vh') { + continue + } + if cur_module == 'builtin' && node.value == 'u8.vbytes' { + continue + } + // Most cached functions are declaration-only nodes (`is_mut` is the parser's + // header marker). A small subset retains its source body so the warm pass can + // recreate generic specializations. Their ordinary concrete symbol still lives + // in the cached object and can be called by a program-generated interface + // dispatch, so it needs the same extern prototype. Open generic declarations + // have no concrete C symbol of their own. + if !node.is_mut && (node.generic_params().len > 0 || node.value.contains('[')) { + continue + } + items << FlatFnGenItem{ + node_id: flat.NodeId(node_idx) + file: cur_file + module: cur_module + c_name: g.fn_c_name_in_module(cur_module, node.value) + } + } + items.sort(a.c_name < b.c_name) + mut forwarded := map[string]bool{} + for item in items { + qfn := item.c_name + if forwarded[qfn] { + continue + } + forwarded[qfn] = true + node := g.a.nodes[int(item.node_id)] + g.tc.cur_file = item.file + g.tc.cur_module = item.module + ret_type := g.fn_node_return_type(node, item.module) + concrete_optional := g.is_program_specialization_fn_node(node, int(item.node_id), + item.module) + g.write(g.fn_return_type_name_for_context(ret_type, concrete_optional)) + g.write(' ') + g.write(qfn) + g.write('(') + g.write_fn_node_params(node) + g.writeln(');') + } + if forwarded.len > 0 { + g.writeln('') + } +} + +fn (mut g FlatGen) c_extern_forward_decls() { + mut cur_module := '' + mut cur_file := '' + mut decls := map[string]string{} + mut decl_specificity := map[string]int{} + mut names := []string{} + referenced_c_externs := g.c_extern_referenced_symbols() + // file/module/c_fn_decl nodes only occur at the top level: iterate the + // checker's top-level index for the range it covers, then scan only the + // transform-appended tail. + use_idx := !isnil(g.tc) && g.tc.top_level_idx.len > 0 + idx_count := if use_idx { g.tc.top_level_idx.len } else { 0 } + tail_start := if use_idx { g.tc.top_level_idx_nodes_len } else { 0 } + total := idx_count + (g.a.nodes.len - tail_start) + for k in 0 .. total { + i := if k < idx_count { g.tc.top_level_idx[k] } else { tail_start + (k - idx_count) } + node := g.a.nodes[i] + kind_id := node_kind_id(node) + if kind_id == 77 { + cur_file = node.value + cur_module = '' + g.tc.cur_file = cur_file + g.tc.cur_module = cur_module + continue + } + if kind_id == 73 { + cur_module = node.value + g.tc.cur_file = cur_file + g.tc.cur_module = cur_module + continue + } + if kind_id != 76 { + continue + } + raw_name := if node.value.starts_with('C.') { node.value } else { 'C.${node.value}' } + raw_cfn := g.cname(raw_name) + mapped_cfn := g.c_decl_abi_names[raw_name] or { + g.c_decl_abi_names[qualify_name_in_module(cur_module, node.value.trim_string_left('C.'))] or { + raw_cfn + } + } + cfn := c_winapi_wide_export_name(mapped_cfn) + shared_runtime_extern := g.needs_shared_runtime && cfn in c_shared_runtime_extern_symbols + if g.has_used_fn_filter() && !(g.spawn_wrapper_defs.len > 0 + && cfn in c_spawn_runtime_extern_symbols) && !shared_runtime_extern + && !g.used_fn_contains(raw_name) && !g.used_fn_contains(raw_cfn) + && !g.used_fn_contains(cfn) && !referenced_c_externs[raw_name] + && !referenced_c_externs[raw_cfn] && !referenced_c_externs[cfn] { + continue + } + if !g.should_emit_c_extern_decl_from_file(cfn, cur_file) { + continue + } + if cfn == 'syscall' { + g.libc_compat_fns[c_libc_compat_syscall_decl_key] = true + } + g.tc.cur_file = cur_file + g.tc.cur_module = cur_module + if cfn !in decls { + names << cfn + } + program_decl_priority := if g.cache_program_files[cur_file] { + 1000 + } else { + 0 + } + specificity := program_decl_priority + c_extern_decl_specificity(g.a, node) + if cfn !in decls || specificity > decl_specificity[cfn] { + decls[cfn] = c_macro_safe_extern_decl(cfn, g.c_extern_decl_line(node, cfn)) + decl_specificity[cfn] = specificity + } + } + names.sort() + for name in names { + if g.c_extern_decl_is_cached_object_fallback(name) { + g.writeln('#ifndef V3CACHE_PROGRAM_UNIT') + g.writeln(decls[name]) + g.writeln('#endif') + } else if name == 'task_info' || name == 'mach_task_self' { + g.writeln('#ifndef __APPLE__') + g.writeln(decls[name]) + g.writeln('#endif') + } else { + g.writeln(decls[name]) + } + } + if names.len > 0 { + g.writeln('') + } +} + +fn c_extern_decl_specificity(a &flat.FlatAst, node flat.Node) int { + mut score := c_extern_type_specificity(node.typ) + for i in 0 .. node.children_count { + param := a.child_node(&node, i) + if param.kind == .param { + score += c_extern_type_specificity(param.typ) + } + } + return score +} + +fn c_extern_type_specificity(raw_type string) int { + mut clean := raw_type.trim_space() + if clean.starts_with('mut ') { + clean = clean['mut '.len..].trim_space() + } + if clean.len == 0 || clean == 'void' { + return 0 + } + if clean in ['voidptr', 'byteptr', 'charptr'] { + return 1 + } + if clean.starts_with('&') { + return 3 + } + return 2 +} + +fn (mut g FlatGen) c_extern_referenced_symbols() map[string]bool { + if g.c_extern_refs_ready { + return g.c_extern_refs + } + mut refs := map[string]bool{} + for item in g.ensure_fn_gen_items() { + g.collect_c_extern_referenced_symbols_from_node(item.node_id, mut refs) + } + if g.test_files.len == 0 && !g.has_entry_main() { + for stmt in g.top_level_stmts() { + g.collect_c_extern_referenced_symbols_from_node(stmt.id, mut refs) + } + } + g.c_extern_refs = refs.move() + g.c_extern_refs_ready = true + return g.c_extern_refs +} + +fn (g &FlatGen) collect_c_extern_referenced_symbols_from_node(id flat.NodeId, mut refs map[string]bool) { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return + } + node := g.a.node(id) + g.collect_c_extern_ref_from_node_into(node, mut refs) + for i in 0 .. node.children_count { + g.collect_c_extern_referenced_symbols_from_node(g.a.child(node, i), mut refs) + } +} + +fn (mut g FlatGen) collect_c_extern_ref_from_node(node &flat.Node) { + g.collect_c_extern_ref_from_node_into(node, mut g.c_extern_refs) +} + +fn (g &FlatGen) collect_c_extern_ref_from_node_into(node &flat.Node, mut refs map[string]bool) { + if node.kind == .ident + && (node.value.starts_with('C.') || node.value in ['v_filelock_lock', 'v_filelock_unlock']) { + raw_name := if node.value.starts_with('C.') { node.value } else { 'C.${node.value}' } + raw_cfn := g.cname(raw_name) + refs[raw_name] = true + refs[raw_cfn] = true + refs[c_winapi_wide_export_name(raw_cfn)] = true + } + if node.kind == .selector && node.children_count > 0 && node.value.len > 0 { + base_id := g.a.child(node, 0) + if int(base_id) >= 0 { + base := g.a.node(base_id) + if base.kind == .ident && base.value == 'C' { + raw_name := 'C.${node.value}' + raw_cfn := g.cname(raw_name) + refs[raw_name] = true + refs[raw_cfn] = true + refs[c_winapi_wide_export_name(raw_cfn)] = true + } + } + } +} + +fn (mut g FlatGen) preseed_c_extern_fn_ptr_types() { + referenced := g.c_extern_referenced_symbols() + g.preseed_c_extern_fn_ptr_types_with_filter(referenced, true) +} + +fn (mut g FlatGen) preseed_all_c_extern_fn_ptr_types() { + g.preseed_c_extern_fn_ptr_types_with_filter(map[string]bool{}, false) +} + +fn (mut g FlatGen) preseed_c_extern_fn_ptr_types_with_filter(referenced map[string]bool, filter_used bool) { + for i in g.top_level_nodes() { + node := g.a.nodes[i] + if node_kind_id(node) != 76 { + continue + } + raw_name := if node.value.starts_with('C.') { node.value } else { 'C.${node.value}' } + raw_cfn := g.cname(raw_name) + mapped_cfn := g.c_decl_abi_names[raw_name] or { + g.c_decl_abi_names[g.cname(raw_name)] or { raw_cfn } + } + cfn := c_winapi_wide_export_name(mapped_cfn) + shared_runtime_extern := g.needs_shared_runtime && cfn in c_shared_runtime_extern_symbols + if filter_used && g.has_used_fn_filter() && !(g.spawn_wrapper_defs.len > 0 + && cfn in c_spawn_runtime_extern_symbols) && !shared_runtime_extern + && !g.used_fn_contains(raw_name) && !g.used_fn_contains(raw_cfn) + && !g.used_fn_contains(cfn) && !referenced[raw_name] && !referenced[raw_cfn] + && !referenced[cfn] { + continue + } + if !g.should_emit_c_extern_decl(cfn) { + continue + } + ret_type := g.parse_node_type(&node) + g.preseed_fn_ptr_type(ret_type) + for j in 0 .. node.children_count { + param_id := g.a.child(&node, j) + p := g.a.node(param_id) + if p.kind != .param { + continue + } + raw_typ := if p.typ.len > 0 { p.typ } else { p.value } + if raw_typ.len == 0 || raw_typ.starts_with('...') { + continue + } + g.preseed_fn_ptr_type(g.tc.parse_type(raw_typ)) + } + } +} + +const c_spawn_runtime_extern_symbols = { + 'pthread_attr_destroy': true + 'pthread_attr_init': true + 'pthread_attr_setstacksize': true + 'pthread_create': true + 'pthread_join': true +} + +const c_shared_runtime_extern_symbols = { + 'pthread_rwlock_rdlock': true + 'pthread_rwlock_wrlock': true + 'pthread_rwlock_unlock': true + 'pthread_rwlockattr_init': true + 'pthread_rwlockattr_setkind_np': true + 'pthread_rwlock_init': true + 'pthread_rwlockattr_destroy': true +} + +const c_cache_macro_sensitive_extern_symbols = { + 'exp': true + 'exp2': true + 'log10': true + 'log1p': true + 'log2': true + 'logb': true + 'logf': true + 'powf': true + 'sqrtf': true + 'tanf': true +} + +// c_macro_safe_extern_decl parenthesizes the name of a math extern that +// turns into a function-like macro (`double exp(double);` -> `double (exp)(double);`). +// Any program that includes a header pulling in hits this, not just +// cache-split builds — notably gg's `gg_darwin.m` (Objective-C/Metal) brings it in, +// so the parenthesized form must be emitted unconditionally. `(exp)(x)` is plain C +// that calls the function and is inert when no such macro is present. +fn c_macro_safe_extern_decl(cfn string, declaration string) string { + if cfn !in c_cache_macro_sensitive_extern_symbols { + return declaration + } + return declaration.replace_once('${cfn}(', '(${cfn})(') +} + +fn (g &FlatGen) should_emit_c_extern_decl(cfn string) bool { + if cfn.contains('.') { + return false + } + if cfn in ['sem_destroy', 'sem_init', 'sem_post', 'sem_timedwait', 'sem_trywait', 'sem_wait'] + && g.target.os in ['linux', 'android', 'termux'] && g.c_directives_use_system_libc() { + return false + } + if cfn in c_system_libc_preamble_declared_fns && g.c_directives_use_system_libc() { + return false + } + if cfn in c_manual_stdlib_declared_fns && g.c_directives_use_system_libc() { + return false + } + if g.cache_split && cfn in c_cache_system_header_declared_fns { + return false + } + if cfn in c_preamble_declared_extern_symbols { + if g.needs_shared_runtime && cfn in c_shared_runtime_extern_symbols { + return true + } + return false + } + // `puts` is supplied by in system-libc units, but a warm + // header-only cache pass can legitimately use the standalone preamble. + // Keep the user's declaration in that headerless program unit. + if cfn == 'puts' && !g.c_directives_use_system_libc() { + return true + } + if cfn in c_libc_compat_extern_symbols { + return false + } + if cfn in c_static_helper_symbols { + return false + } + if cfn in g.inlined_c_fns { + if g.cache_split && cfn in g.cache_omitted_c_fns && cfn !in g.inlined_c_declared_fns { + return true + } + return false + } + if cfn in g.inlined_c_declared_fns { + return false + } + return true +} + +const c_manual_stdlib_declared_fns = { + '_aligned_free': true + '_aligned_malloc': true + '_aligned_realloc': true + '_fileno': true + '_fseeki64': true + '_pclose': true + '_vscprintf': true + '_vsnprintf_s': true + '_wfopen': true + '_wfreopen': true + '_wgetenv': true + '_wpopen': true + '_wputenv': true + '_wremove': true + 'abs': true + 'aligned_alloc': true + 'atexit': true + 'atof': true + 'atoi': true + 'calloc': true + 'clearerr': true + 'exit': true + 'fclose': true + 'fdopen': true + 'feof': true + 'ferror': true + 'fflush': true + 'fgetc': true + 'fgetpos': true + 'fgets': true + 'fileno': true + 'fopen': true + 'fprintf': true + 'fputs': true + 'fread': true + 'free': true + 'freopen': true + 'freopen_s': true + 'fseek': true + 'ftell': true + 'fwrite': true + 'getc': true + 'getchar': true + 'getenv': true + 'getline': true + 'malloc': true + 'memchr': true + 'memcmp': true + 'memcpy': true + 'memmove': true + 'memset': true + 'mkstemp': true + 'pclose': true + 'perror': true + 'popen': true + 'posix_memalign': true + 'printf': true + 'putchar': true + 'puts': true + 'qsort': true + 'rand': true + 'realloc': true + 'realpath': true + 'remove': true + 'rename': true + 'rewind': true + 'scanf': true + 'setenv': true + 'setvbuf': true + 'snprintf': true + 'sprintf': true + 'srand': true + 'sscanf': true + 'strcasecmp': true + 'strchr': true + 'strcmp': true + 'strdup': true + 'strerror': true + 'strlen': true + 'strncasecmp': true + 'strncmp': true + 'strrchr': true + 'strstr': true + 'system': true + 'ungetc': true + 'unsetenv': true + 'va_arg': true + 'va_copy': true + 'va_end': true + 'va_start': true + 'vfprintf': true + 'vsnprintf': true +} + +fn (g &FlatGen) c_extern_decl_is_cached_object_fallback(cfn string) bool { + return g.cache_split && cfn in g.inlined_c_fns && cfn in g.cache_omitted_c_fns + && cfn !in g.inlined_c_declared_fns +} + +fn (g &FlatGen) should_emit_c_extern_decl_from_file(cfn string, source_file string) bool { + // builtin/cfns.c.v declares the static vschannel helper supplied by its C header. + // A user C.request declaration is unrelated and still needs an extern prototype. + if cfn == 'request' && source_file.replace('\\', '/').ends_with('/builtin/cfns.c.v') { + return false + } + if g.target.os == 'vinix' { + normalized_file := source_file.replace('\\', '/') + normalized_root := g.compiler_vroot.replace('\\', '/').trim_right('/') + is_vlib_file := normalized_root.len > 0 + && normalized_file.starts_with('${normalized_root}/vlib/') + if !is_vlib_file { + if cfn.starts_with('__builtin_') { + return false + } + if cfn in ['text_start', 'text_end', 'rodata_start', 'rodata_end', 'data_start', + 'data_end', 'interrupt_thunks'] { + return false + } + } + } + return g.should_emit_c_extern_decl(cfn) +} + +// c_system_libc_preamble_declared_fns contains only symbols declared by the fixed +// header set in system_libc_headers(). Declarations from other system headers are +// tracked per include through inlined_c_declared_fns. +const c_system_libc_preamble_declared_fns = { + '__builtin_ctz': true + '__builtin_ctzll': true + 'abs': true + 'accept': true + 'atomic_thread_fence': true + 'bind': true + 'chdir': true + 'chmod': true + 'chown': true + 'clock_gettime_nsec_np': true + 'clock_gettime': true + 'closedir': true + 'connect': true + 'cosf': true + '_dyld_get_image_header': true + 'execve': true + 'exit': true + 'fdopen': true + 'feof': true + 'ferror': true + 'fgets': true + 'fputs': true + 'freeaddrinfo': true + 'gai_strerror': true + 'getaddrinfo': true + 'getcwd': true + 'getpeername': true + 'getegid': true + 'geteuid': true + 'getgid': true + 'gethostname': true + 'getpid': true + 'gettimeofday': true + 'getuid': true + 'gmtime_r': true + 'getline': true + 'inet_ntop': true + 'inet_pton': true + 'ioctl': true + 'isatty': true + 'link': true + 'localtime_r': true + 'log': true + 'lstat': true + 'mkstemp': true + 'mkdir': true + 'mmap': true + 'opendir': true + 'popen': true + 'printf': true + 'pthread_getspecific': true + 'pthread_key_delete': true + 'pthread_setspecific': true + 'pthread_mutex_trylock': true + 'pthread_cond_timedwait': true + 'pthread_rwlock_init': true + 'pthread_rwlock_rdlock': true + 'pthread_rwlock_unlock': true + 'pthread_rwlock_wrlock': true + 'pthread_rwlockattr_init': true + 'pthread_rwlockattr_setkind_np': true + 'kevent': true + 'kqueue': true + 'rand': true + 'readlink': true + 'readdir': true + 'recv': true + 'recvfrom': true + 'rewind': true + 'rmdir': true + 'send': true + 'sendto': true + 'setsockopt': true + 'shutdown': true + 'sigaddset': true + 'sigemptyset': true + 'sigprocmask': true + 'sin': true + 'sinf': true + 'socket': true + 'sscanf': true + 'strerror': true + 'symlink': true + 'sysconf': true + 'system': true + 'timegm': true + 'unlink': true + 'unsetenv': true + 'waitpid': true + 'write': true +} + +const c_preamble_declared_extern_symbols = { + '_exit': true + '_dyld_get_image_name': true + '__errno': true + '__errno_location': true + '__error': true + '_errno': true + 'abort': true + 'access': true + 'atexit': true + 'ceil': true + 'ceilf': true + 'close': true + 'cos': true + 'dup2': true + 'execlp': true + 'execvp': true + 'fabs': true + 'fcntl': true + 'floor': true + 'floorf': true + 'fmod': true + 'fork': true + 'getenv': true + 'ldexp': true + 'memchr': true + 'memcmp': true + 'memcpy': true + 'memmove': true + 'memset': true + 'open': true + 'perror': true + 'pipe': true + 'pow': true + 'pthread_attr_destroy': true + 'pthread_attr_init': true + 'pthread_cond_broadcast': true + 'pthread_cond_destroy': true + 'pthread_cond_init': true + 'pthread_cond_signal': true + 'pthread_cond_wait': true + 'pthread_create': true + 'pthread_detach': true + 'pthread_getspecific': true + 'pthread_join': true + 'pthread_key_create': true + 'pthread_mutex_destroy': true + 'pthread_mutex_init': true + 'pthread_mutex_lock': true + 'pthread_mutex_unlock': true + 'pthread_setspecific': true + 'malloc': true + 'calloc': true + 'realloc': true + 'free': true + 'clock': true + 'fprintf': true + 'fflush': true + 'qsort_r': true + 'mktime': true + 'localtime': true + 'utime': true + 'stat': true + 'fopen': true + 'freopen': true + 'fclose': true + 'fread': true + 'fwrite': true + 'fseek': true + 'ftell': true + 'remove': true + 'rename': true + 'time': true + 'fileno': true + 'ftruncate': true + 'pthread_rwlock_destroy': true + 'pthread_rwlock_init': true + 'pthread_rwlock_rdlock': true + 'pthread_rwlock_tryrdlock': true + 'pthread_rwlock_trywrlock': true + 'pthread_rwlock_unlock': true + 'pthread_rwlock_wrlock': true + 'pthread_rwlockattr_destroy': true + 'pthread_rwlockattr_init': true + 'pthread_rwlockattr_setkind_np': true + 'read': true + 'realpath': true + 'setenv': true + 'signal': true + 'snprintf': true + 'sqrt': true + 'strcmp': true + 'strlen': true + 'strncmp': true + 'strncpy': true + 'strrchr': true + 'strstr': true +} + +const c_libc_compat_extern_symbols = { + 'gettid': true + 'puts': true + // sendfile is declared by the platform header (/) + // that its declaring module (fasthttp/veb) already includes; the V-side + // prototype signature differs per platform and conflicts with that header. + 'sendfile': true +} + +const c_libc_compat_syscall_decl_key = 'syscall_decl' + +const c_static_helper_symbols = { + 'EV_SET': true + 'FD_ISSET': true + 'FD_SET': true + 'FD_ZERO': true + 'WEXITSTATUS': true + 'WIFEXITED': true + 'WIFSIGNALED': true + 'WTERMSIG': true + 'access': true + 'atexit': true + 'atomic_compare_exchange_strong_byte': true + 'atomic_compare_exchange_strong_ptr': true + 'atomic_compare_exchange_strong_u16': true + 'atomic_compare_exchange_strong_u32': true + 'atomic_compare_exchange_strong_u64': true + 'atomic_compare_exchange_weak_byte': true + 'atomic_compare_exchange_weak_ptr': true + 'atomic_compare_exchange_weak_u16': true + 'atomic_compare_exchange_weak_u32': true + 'atomic_compare_exchange_weak_u64': true + 'atomic_exchange_byte': true + 'atomic_exchange_ptr': true + 'atomic_exchange_u16': true + 'atomic_exchange_u32': true + 'atomic_exchange_u64': true + 'atomic_fetch_add_byte': true + 'atomic_fetch_add_ptr': true + 'atomic_fetch_add_u16': true + 'atomic_fetch_add_u32': true + 'atomic_fetch_add_u64': true + 'atomic_fetch_sub_byte': true + 'atomic_fetch_sub_ptr': true + 'atomic_fetch_sub_u16': true + 'atomic_fetch_sub_u32': true + 'atomic_fetch_sub_u64': true + 'atomic_load_byte': true + 'atomic_load_ptr': true + 'atomic_load_u16': true + 'atomic_load_u32': true + 'atomic_load_u64': true + 'atomic_store_byte': true + 'atomic_store_ptr': true + 'atomic_store_u16': true + 'atomic_store_u32': true + 'atomic_store_u64': true + 'close': true + 'cpu_relax': true + 'dup2': true + 'execlp': true + 'execvp': true + 'fcntl': true + 'fork': true + 'getenv': true + 'open': true + 'pipe': true + 'posix_spawn': true + 'posix_spawnp': true + 'read': true + 'realpath': true + 'setenv': true + 'signal': true + 'snprintf': true + 'strrchr': true + 'strstr': true + 'v_filelock_lock': true + 'v_filelock_unlock': true + 'v_os_exec_capture_start': true + 'v_os_execute_capture_start': true + 'vschannel_cleanup': true + 'vschannel_init': true + 'v_prealloc_atomic_add_i32': true + 'v_prealloc_atomic_add_i64': true + 'v_prealloc_atomic_cas_i32': true + 'v_prealloc_atomic_load_i32': true + 'v_prealloc_atomic_load_i64': true + 'v_prealloc_atomic_store_i32': true + 'v_signal_with_handler_cast': true + 'wyhash': true + 'wyhash64': true + '_exit': true + '_wymix': true + '_vcleanup': true + '_vinit': true +} + +fn (mut g FlatGen) c_extern_decl_line(node flat.Node, cfn string) string { + mut sb := strings.new_builder(96) + ret_type := g.parse_node_type(&node) + sb.write_string(g.fn_return_type_name(ret_type)) + sb.write_string(' ') + call_conv := c_extern_calling_convention(cfn) + if call_conv.len > 0 { + sb.write_string(call_conv) + sb.write_u8(` `) + } + sb.write_string(cfn) + sb.write_u8(`(`) + sb.write_string(g.c_extern_decl_params(node)) + sb.write_string(');') + return sb.str() +} + +fn c_extern_calling_convention(cfn string) string { + if cfn in c_winapi_extern_symbols { + return 'WINAPI' + } + return '' +} + +fn c_winapi_wide_export_name(cfn string) string { + match cfn { + 'CopyFile' { return 'CopyFileW' } + 'CreateDirectory' { return 'CreateDirectoryW' } + 'CreateFile' { return 'CreateFileW' } + 'CreateWindowEx' { return 'CreateWindowExW' } + 'DefWindowProc' { return 'DefWindowProcW' } + 'FindFirstFile' { return 'FindFirstFileW' } + 'FindNextFile' { return 'FindNextFileW' } + 'GetCommandLine' { return 'GetCommandLineW' } + 'GetFullPathName' { return 'GetFullPathNameW' } + 'GetLongPathName' { return 'GetLongPathNameW' } + 'GetModuleFileName' { return 'GetModuleFileNameW' } + 'LoadLibrary' { return 'LoadLibraryW' } + 'ReadConsole' { return 'ReadConsoleW' } + 'RegOpenKeyEx' { return 'RegOpenKeyExW' } + 'RegQueryValueEx' { return 'RegQueryValueExW' } + 'RegSetValueEx' { return 'RegSetValueExW' } + 'RegisterClassEx' { return 'RegisterClassExW' } + 'RemoveDirectory' { return 'RemoveDirectoryW' } + 'SendMessageTimeout' { return 'SendMessageTimeoutW' } + 'SetConsoleTitle' { return 'SetConsoleTitleW' } + 'WriteConsole' { return 'WriteConsoleW' } + else { return cfn } + } +} + +const c_winapi_extern_symbols = { + 'AddVectoredExceptionHandler': true + 'CaptureStackBackTrace': true + 'CloseClipboard': true + 'CloseHandle': true + 'CopyFile': true + 'CopyFileW': true + 'CreateDirectory': true + 'CreateDirectoryW': true + 'CreateFile': true + 'CreateFileW': true + 'CreateHardLinkW': true + 'CreatePipe': true + 'CreateProcessW': true + 'CreateSymbolicLinkW': true + 'CreateWindowEx': true + 'CreateWindowExW': true + 'DefWindowProc': true + 'DefWindowProcW': true + 'DeleteFileW': true + 'DestroyWindow': true + 'EmptyClipboard': true + 'ExpandEnvironmentStringsW': true + 'FindClose': true + 'FindFirstFile': true + 'FindFirstFileW': true + 'FindNextFile': true + 'FindNextFileW': true + 'FormatMessageW': true + 'FreeEnvironmentStringsW': true + 'GenerateConsoleCtrlEvent': true + 'GetClipboardData': true + 'GetClipboardOwner': true + 'GetCommandLine': true + 'GetCommandLineW': true + 'GetComputerNameW': true + 'GetConsoleMode': true + 'GetConsoleScreenBufferInfo': true + 'GetCurrentDirectoryW': true + 'GetCurrentProcess': true + 'GetCurrentThreadId': true + 'GetEnvironmentStringsW': true + 'GetExitCodeProcess': true + 'GetFinalPathNameByHandleW': true + 'GetFileAttributesW': true + 'GetFileSizeEx': true + 'GetFileType': true + 'GetFullPathName': true + 'GetFullPathNameW': true + 'GetLastError': true + 'GetLongPathName': true + 'GetLongPathNameW': true + 'GetModuleFileName': true + 'GetModuleFileNameW': true + 'GetModuleHandleA': true + 'GetNumberOfConsoleInputEvents': true + 'GetProcAddress': true + 'GetProcessHeap': true + 'GetShortPathNameW': true + 'GetStdHandle': true + 'GetSystemTimeAsFileTime': true + 'GetTempFileNameW': true + 'GetTempPathW': true + 'GetTickCount': true + 'GetUserNameW': true + 'GlobalAlloc': true + 'GlobalFree': true + 'GlobalLock': true + 'GlobalUnlock': true + 'HeapAlloc': true + 'HeapFree': true + 'InitializeConditionVariable': true + 'IsDebuggerPresent': true + 'LoadLibrary': true + 'LoadLibraryW': true + 'LocalFree': true + 'MoveFileW': true + 'MultiByteToWideChar': true + 'OpenClipboard': true + 'PeekNamedPipe': true + 'ReadConsole': true + 'ReadConsoleInput': true + 'ReadConsoleW': true + 'ReadFile': true + 'RegCloseKey': true + 'RegOpenKeyEx': true + 'RegOpenKeyExW': true + 'RegQueryValueEx': true + 'RegQueryValueExW': true + 'RegSetValueEx': true + 'RegSetValueExW': true + 'RegisterClassEx': true + 'RegisterClassExW': true + 'RemoveDirectory': true + 'RemoveDirectoryW': true + 'ScrollConsoleScreenBuffer': true + 'SetClipboardData': true + 'SetConsoleCursorPosition': true + 'SetConsoleMode': true + 'SetConsoleTitle': true + 'SetConsoleTitleW': true + 'SetCurrentDirectoryW': true + 'SetEndOfFile': true + 'SetFileAttributesW': true + 'SetFilePointerEx': true + 'SetHandleInformation': true + 'SetLastError': true + 'SetUnhandledExceptionFilter': true + 'Sleep': true + 'SleepConditionVariableSRW': true + 'SendMessageTimeout': true + 'SendMessageTimeoutW': true + 'SymCleanup': true + 'SymFromAddr': true + 'SymGetLineFromAddr64': true + 'SymInitialize': true + 'SymSetOptions': true + 'TerminateProcess': true + 'TlsAlloc': true + 'TlsFree': true + 'TlsGetValue': true + 'TlsSetValue': true + 'TryAcquireSRWLockExclusive': true + 'TryAcquireSRWLockShared': true + 'VirtualAlloc': true + 'VirtualFree': true + 'VirtualProtect': true + 'WSAAddressToStringA': true + 'WaitForSingleObject': true + 'WakeConditionVariable': true + 'WriteConsole': true + 'WriteConsoleW': true + 'WriteFile': true +} + +fn (mut g FlatGen) c_extern_decl_params(node flat.Node) string { + if node.children_count == 0 { + return 'void' + } + mut parts := []string{} + for i in 0 .. node.children_count { + param_id := g.a.child(&node, i) + p := g.a.node(param_id) + if p.kind != .param { + continue + } + raw_typ := if p.typ.len > 0 { p.typ } else { p.value } + if raw_typ.len == 0 { + continue + } + if raw_typ.starts_with('...') { + if parts.len > 0 { + parts << '...' + } + continue + } + pt := g.tc.parse_type(raw_typ) + mut ct := g.c_extern_param_c_type(pt) + if ct.starts_with('fn_ptr:') { + ct = g.resolve_fn_ptr_type(ct) + } + if c_extern_param_needs_const_prefix(p.value, raw_typ, pt) { + ct = 'const ${ct}' + } + if p.typ.len > 0 && p.value.len > 0 { + param_name := if p.value == '_' { '_${parts.len}' } else { g.cname(p.value) } + parts << '${ct} ${param_name}' + } else { + parts << ct + } + } + if parts.len == 0 { + return 'void' + } + return parts.join(', ') +} + +fn (mut g FlatGen) c_extern_param_c_type(pt types.Type) string { + if pt is types.OptionType || pt is types.ResultType { + return g.optional_type_name(pt) + } + if pt is types.Pointer { + base := pt.base_type + if base is types.Struct && base.name.starts_with('C.') { + base_ct := g.tc.c_type(base) + if base_ct.starts_with('struct ') { + return '${base_ct}*' + } + } + } + return g.tc.c_type(pt) +} + +fn c_extern_param_needs_const_prefix(param_name string, raw_typ string, pt types.Type) bool { + return param_name.starts_with('const_') && !trimmed_space(raw_typ).starts_with('mut ') + && pt is types.Pointer +} + +fn (mut g FlatGen) insert_cur_implicit_veb_ctx_param(node flat.Node) { + if !g.fn_needs_implicit_veb_ctx(node) { + return + } + insert_idx := g.fn_implicit_veb_ctx_insert_index(node) + ctx_type := g.implicit_veb_ctx_type() + mut names := []string{cap: g.cur_param_names.len + 1} + mut type_values := []types.Type{cap: g.cur_param_type_values.len + 1} + for i, name in g.cur_param_names { + if i == insert_idx { + names << 'ctx' + type_values << ctx_type + } + names << name + type_values << g.cur_param_type_values[i] + } + if insert_idx >= g.cur_param_names.len { + names << 'ctx' + type_values << ctx_type + } + g.cur_param_names = names + g.cur_param_type_values = type_values + g.cur_param_types['ctx'] = ctx_type + g.tc.cur_scope.insert('ctx', ctx_type) +} + +fn (mut g FlatGen) fn_param_types_with_implicit_veb_ctx(node flat.Node, params []types.Type) []types.Type { + if !g.fn_needs_implicit_veb_ctx(node) { + return params + } + insert_idx := g.fn_implicit_veb_ctx_insert_index(node) + ctx_type := g.implicit_veb_ctx_type() + mut result := []types.Type{cap: params.len + 1} + for i, param in params { + if i == insert_idx { + result << ctx_type + } + result << param + } + if insert_idx >= params.len { + result << ctx_type + } + return result +} + +fn (mut g FlatGen) fn_shared_params_with_implicit_veb_ctx(node flat.Node, flags []bool) []bool { + if !g.fn_needs_implicit_veb_ctx(node) { + return flags + } + insert_idx := g.fn_implicit_veb_ctx_insert_index(node) + mut result := []bool{cap: flags.len + 1} + for i, flag in flags { + if i == insert_idx { + result << false + } + result << flag + } + if insert_idx >= flags.len { + result << false + } + return result +} + +fn (g &FlatGen) fn_param_is_shared(fn_name string, idx int) bool { + if (!g.has_shared_params && g.tc.fn_shared_params.len == 0) || idx < 0 || fn_name.len == 0 { + return false + } + if flags := g.fn_shared_params_resolved[fn_name] { + return idx < flags.len && flags[idx] + } + cname := g.cname(fn_name) + if cname != fn_name { + if flags := g.fn_shared_params_resolved[cname] { + return idx < flags.len && flags[idx] + } + } + short_name := fn_name.all_after_last('.') + if short_name != fn_name { + if flags := g.fn_shared_params_resolved[short_name] { + return idx < flags.len && flags[idx] + } + } + short_cname := g.cname(short_name) + if short_cname != short_name && short_cname != fn_name && short_cname != cname { + if flags := g.fn_shared_params_resolved[short_cname] { + return idx < flags.len && flags[idx] + } + } + return false +} + +fn (g &FlatGen) fn_param_shared_exact(fn_name string, idx int) ?bool { + if fn_name.len == 0 { + return none + } + if flags := g.fn_shared_params_resolved[fn_name] { + return idx < flags.len && flags[idx] + } + cname := g.cname(fn_name) + if cname != fn_name { + if flags := g.fn_shared_params_resolved[cname] { + return idx < flags.len && flags[idx] + } + } + return none +} + +fn (g &FlatGen) fn_param_is_shared_for_call(idx int, name1 string, name2 string, name3 string, name4 string) bool { + if (!g.has_shared_params && g.tc.fn_shared_params.len == 0) || idx < 0 { + return false + } + mut found_exact := false + if flag := g.fn_param_shared_exact(name1, idx) { + found_exact = true + if flag { + return true + } + } + if name2 != name1 { + if flag := g.fn_param_shared_exact(name2, idx) { + found_exact = true + if flag { + return true + } + } + } + if name3 != name1 && name3 != name2 { + if flag := g.fn_param_shared_exact(name3, idx) { + found_exact = true + if flag { + return true + } + } + } + if name4 != name1 && name4 != name2 && name4 != name3 { + if flag := g.fn_param_shared_exact(name4, idx) { + found_exact = true + if flag { + return true + } + } + } + if found_exact { + return false + } + return g.fn_param_is_shared(name1, idx) || g.fn_param_is_shared(name2, idx) + || g.fn_param_is_shared(name3, idx) || g.fn_param_is_shared(name4, idx) +} + +fn (mut g FlatGen) precompute_shared_param_index() { + if !g.has_shared_params && g.tc.fn_shared_params.len == 0 { + return + } + for name, flags in g.tc.fn_shared_params { + g.fn_shared_params_resolved[name] = flags.clone() + } + for name, flags in g.fn_decl_shared_params { + // The name's own entry is authoritative (mirrors fn_param_is_shared's + // first-present-candidate rule); merging short-name variants here + // would smear another declaration's shared flags onto this one. + // Store empty/all-false results too. Presence in this map is what turns + // the very hot call-site query into one lookup. + g.fn_shared_params_resolved[name] = flags.clone() + } +} + +fn (mut g FlatGen) gen_shared_local_receiver_arg(base_id flat.NodeId) bool { + if int(base_id) < 0 || int(base_id) >= g.a.nodes.len { + return false + } + base := g.a.nodes[int(base_id)] + if base.kind == .paren && base.children_count > 0 { + return g.gen_shared_local_receiver_arg(g.a.child(&base, 0)) + } + if base.kind == .prefix && base.value == 'shared' && base.children_count > 0 { + return g.gen_shared_local_receiver_arg(g.a.child(&base, 0)) + } + if base.kind != .ident || !g.local_ident_is_shared_wrapper(base.value) { + return false + } + g.write(g.cname(base.value)) + return true +} + +fn (mut g FlatGen) fn_needs_implicit_veb_ctx(node flat.Node) bool { + return g.fn_returns_veb_result(node) && g.fn_has_receiver_param(node) + && !g.fn_receiver_type_is_context(node) && !g.fn_has_veb_context_param(node) + && g.type_name_known_in_current_module('Context') +} + +// is_implicit_veb_ctx_param reports whether a callee parameter is the hidden +// veb `Context` pointer that callers do not supply explicitly. +fn (g &FlatGen) is_implicit_veb_ctx_param(pt types.Type) bool { + if pt is types.Pointer { + return pt.base_type.name().all_after_last('.') == 'Context' + } + return pt.name().trim_string_left('mut ').trim_left('&').all_after_last('.') == 'Context' +} + +fn (g &FlatGen) call_has_implicit_veb_ctx(names []string) bool { + for name in names { + if name.len == 0 { + continue + } + if g.tc.fn_implicit_veb_ctx[name] { + return true + } + cname := g.cname(name) + if cname != name && g.tc.fn_implicit_veb_ctx[cname] { + return true + } + } + return false +} + +fn (g &FlatGen) cur_veb_ctx_name() ?string { + for i, name in g.cur_param_names { + if i < g.cur_param_type_values.len { + param_type := g.cur_param_type_values[i] + short_name := + param_type.name().trim_string_left('mut ').trim_left('&').all_after_last('.') + if short_name == 'Context' || g.tc.is_veb_context_type(param_type) { + return name + } + } + } + return none +} + +fn (g &FlatGen) type_name_known_in_current_module(name string) bool { + qname := g.tc.qualify_name(name) + return qname in g.struct_decl_infos || qname in g.tc.type_aliases || qname in g.tc.enum_names + || qname in g.tc.sum_types || qname in g.tc.interface_names +} + +fn (g &FlatGen) type_name_known(name string) bool { + if types.is_builtin_type_name(name) || name in ['C', 'JS'] { + return true + } + qname := g.tc.qualify_name(name) + return name in g.struct_decl_infos || qname in g.struct_decl_infos || name in g.tc.type_aliases + || qname in g.tc.type_aliases || name in g.tc.structs || qname in g.tc.structs + || name in g.tc.enum_names || qname in g.tc.enum_names || name in g.tc.sum_types + || qname in g.tc.sum_types || name in g.tc.interface_names || qname in g.tc.interface_names +} + +fn (mut g FlatGen) fn_returns_veb_result(node flat.Node) bool { + if node.typ == 'veb.Result' { + return true + } + ret := g.parse_node_type(&node) + return ret.name() == 'veb.Result' +} + +fn (mut g FlatGen) fn_has_veb_context_param(node flat.Node) bool { + for i in 0 .. node.children_count { + p := g.a.child_node(&node, i) + if p.kind == .param && g.tc.is_veb_context_type(g.tc.parse_type(p.typ)) { + return true + } + } + return false +} + +fn (g &FlatGen) fn_implicit_veb_ctx_insert_index(node flat.Node) int { + if g.fn_has_receiver_param(node) { + return 1 + } + return 0 +} + +fn (g &FlatGen) fn_has_receiver_param(node flat.Node) bool { + if !node.value.contains('.') || node.children_count == 0 { + return false + } + first := g.a.child_node(&node, 0) + if first.kind != .param || first.typ.len == 0 { + return false + } + receiver := node.value.all_before_last('.').all_after_last('.') + param_type := first.typ.trim_left('&').all_after_last('.') + return receiver == param_type +} + +fn (g &FlatGen) fn_receiver_type_is_context(node flat.Node) bool { + if !g.fn_has_receiver_param(node) { + return false + } + first := g.a.child_node(&node, 0) + return first.typ.trim_left('&').all_after_last('.') == 'Context' +} + +fn (mut g FlatGen) implicit_veb_ctx_type() types.Type { + return g.tc.parse_type('mut Context') +} + +fn (mut g FlatGen) fn_node_return_type(node flat.Node, module_name string) types.Type { + if g.tc.autofree_mode && module_name in ['', 'main'] { + for key in [node.value, dotted_fn_name_in_module(module_name, node.value)] { + if raw_return := g.tc.fn_ret_type_texts[key] { + clean_return := raw_return.trim_space() + if target := g.tc.type_aliases[clean_return] { + return types.Type(types.Alias{ + name: clean_return + base_type: g.tc.parse_type(target) + }) + } + } + } + declared := g.tc.parse_resolution_type(node.typ) + if declared is types.Alias { + return declared + } + } + if rt := g.fn_node_return_type_from_signatures(node, module_name) { + return rt + } + if info := g.generic_receiver_method_call_info(node.value) { + return info.return_type + } + return g.tc.parse_resolution_type(node.typ) +} + +fn (mut g FlatGen) fn_node_return_type_from_signatures(node flat.Node, module_name string) ?types.Type { + dotted_name := dotted_fn_name_in_module(module_name, node.value) + cname := g.fn_c_name_in_module(module_name, node.value) + if rt := g.fn_decl_ret_types[fn_decl_module_key(module_name, node.value)] { + return rt + } + if module_name.len == 0 { + if rt := g.fn_decl_ret_types[fn_decl_module_key('main', node.value)] { + return rt + } + } + if !node.value.contains('.') { + full_name := qualify_name_in_module(module_name, node.value) + if rt := g.fn_decl_ret_types[full_name] { + return rt + } + } + if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' { + if rt := g.tc.fn_ret_types[dotted_name] { + return rt + } + c_dotted_name := g.cname(dotted_name) + if c_dotted_name != dotted_name { + if rt := g.tc.fn_ret_types[c_dotted_name] { + return rt + } + } + if rt := g.tc.fn_ret_types[cname] { + return rt + } + if rt := g.tc.fn_ret_types[node.value] { + return rt + } + c_value := g.cname(node.value) + if c_value != node.value { + if rt := g.tc.fn_ret_types[c_value] { + return rt + } + } + return none + } + if rt := g.tc.fn_ret_types[node.value] { + return rt + } + c_value := g.cname(node.value) + if c_value != node.value { + if rt := g.tc.fn_ret_types[c_value] { + return rt + } + } + if dotted_name != node.value { + if rt := g.tc.fn_ret_types[dotted_name] { + return rt + } + c_dotted_name := g.cname(dotted_name) + if c_dotted_name != dotted_name { + if rt := g.tc.fn_ret_types[c_dotted_name] { + return rt + } + } + } + if cname != node.value && cname != c_value { + if rt := g.tc.fn_ret_types[cname] { + return rt + } + } + return none +} + +@[direct_array_access] +fn (mut g FlatGen) fn_node_param_types(node flat.Node, module_name string) []types.Type { + if g.fn_needs_implicit_veb_ctx(node) { + return []types.Type{} + } + mut explicit_params := 0 + for i in 0 .. node.children_count { + if g.a.child_node(&node, i).kind != .param { + if g.prefix_param_scan { + break + } + continue + } + explicit_params++ + } + // The module-scoped key first: a method name (`Recv.method`) is dotted but + // not module-qualified, and two modules declaring the same receiver/method + // pair must not share an entry. + if params := g.fn_decl_param_types[fn_decl_module_key(module_name, node.value)] { + if params.len == explicit_params { + return params + } + } + if !node.value.contains('.') { + full_name := qualify_name_in_module(module_name, node.value) + if params := g.fn_decl_param_types[full_name] { + if params.len == explicit_params { + return params + } + } + } + if params := g.fn_node_param_types_from_signatures(node, module_name, explicit_params) { + return params + } + if info := g.generic_receiver_method_call_info(node.value) { + if info.params.len == explicit_params { + return info.params.clone() + } + } + return []types.Type{} +} + +fn (mut g FlatGen) fn_node_param_types_from_signatures(node flat.Node, module_name string, explicit_params int) ?[]types.Type { + dotted_name := dotted_fn_name_in_module(module_name, node.value) + cname := g.fn_c_name_in_module(module_name, node.value) + if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' { + if params := g.matching_fn_param_types(dotted_name, explicit_params) { + return params + } + c_dotted_name := g.cname(dotted_name) + if c_dotted_name != dotted_name { + if params := g.matching_fn_param_types(c_dotted_name, explicit_params) { + return params + } + } + if params := g.matching_fn_param_types(cname, explicit_params) { + return params + } + if params := g.matching_fn_param_types(node.value, explicit_params) { + return params + } + c_value := g.cname(node.value) + if c_value != node.value { + if params := g.matching_fn_param_types(c_value, explicit_params) { + return params + } + } + return none + } + if params := g.matching_fn_param_types(node.value, explicit_params) { + return params + } + c_value := g.cname(node.value) + if c_value != node.value { + if params := g.matching_fn_param_types(c_value, explicit_params) { + return params + } + } + if dotted_name != node.value { + if params := g.matching_fn_param_types(dotted_name, explicit_params) { + return params + } + c_dotted_name := g.cname(dotted_name) + if c_dotted_name != dotted_name { + if params := g.matching_fn_param_types(c_dotted_name, explicit_params) { + return params + } + } + } + if cname != node.value && cname != c_value { + if params := g.matching_fn_param_types(cname, explicit_params) { + return params + } + } + return none +} + +fn (g &FlatGen) matching_fn_param_types(name string, explicit_params int) ?[]types.Type { + if name.len == 0 { + return none + } + params := g.tc.fn_param_types[name] or { return none } + if params.len != explicit_params { + return none + } + return params +} + +fn (g &FlatGen) generic_receiver_method_call_info(name string) ?types.CallInfo { + if g.skip_generics { + return none + } + if !name.contains('.') { + return none + } + receiver := name.all_before_last('.') + if !receiver.contains('[') || !receiver.contains(']') { + return none + } + return g.tc.resolve_generic_struct_method(receiver, name.all_after_last('.')) +} + +fn (mut g FlatGen) fn_node_signature_names(node flat.Node, module_name string) []string { + dotted_name := dotted_fn_name_in_module(module_name, node.value) + cname := g.fn_c_name_in_module(module_name, node.value) + mut names := []string{} + if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' { + names << dotted_name + names << g.cname(dotted_name) + names << cname + names << node.value + names << g.cname(node.value) + } else { + names << node.value + names << g.cname(node.value) + names << dotted_name + names << g.cname(dotted_name) + names << cname + } + mut deduped := []string{cap: names.len} + for name in names { + if name.len > 0 && name !in deduped { + deduped << name + } + } + return deduped +} + +fn (mut g FlatGen) fn_node_effective_param_type(param flat.Node, typed types.Type) types.Type { + if !param.is_mut || param.op != .amp || typed !is types.Pointer { + return typed + } + declared := g.tc.parse_resolution_type(param.typ) + if declared.name() != typed.name() { + // Generic specialization already records the mutable caller-slot pointer in + // its concrete signature. + return typed + } + return types.Type(types.Pointer{ + base_type: typed + }) +} + +// write_fn_node_params writes fn node params output for c. +fn (mut g FlatGen) write_fn_node_params(node flat.Node) { + mut params_len := 0 + for i in 0 .. node.children_count { + if g.a.child_node(&node, i).kind == .param { + params_len++ + } + } + needs_implicit_ctx := g.fn_needs_implicit_veb_ctx(node) + if needs_implicit_ctx { + params_len++ + } + if params_len == 0 { + g.write('void') + return + } + mut written := 0 + mut param_idx := 0 + mut implicit_ctx_written := false + insert_implicit_ctx_after_first := needs_implicit_ctx && g.fn_has_receiver_param(node) + typed_params := g.fn_node_param_types(node, g.tc.cur_module) + concrete_optional_params := g.is_specialized_generic_fn_node(node) + for i in 0 .. node.children_count { + param_id := g.a.child(&node, i) + p := g.a.node(param_id) + if p.kind != .param { + continue + } + if p.typ == '...' { + g.write('...') + written++ + if written < params_len { + g.write(', ') + } + continue + } + raw_pt := if param_idx < typed_params.len { + typed_params[param_idx] + } else { + g.tc.parse_resolution_type(p.typ) + } + effective_pt := g.fn_node_effective_param_type(p, raw_pt) + param_idx++ + if concrete_optional_params && type_is_optional_result(effective_pt) && p.value.len > 0 { + g.cur_concrete_optional_params[p.value] = true + } + ct := if shared_ct := g.shared_param_c_type(p.typ) { + shared_ct + } else if concrete_optional_params + && (effective_pt is types.OptionType || effective_pt is types.ResultType) { + g.concrete_optional_type_name(effective_pt) + } else if effective_pt is types.Pointer && (effective_pt.base_type is types.OptionType + || effective_pt.base_type is types.ResultType) { + g.optional_type_name(effective_pt) + } else if effective_pt is types.ArrayFixed { + '${g.fixed_array_elem_c_type(effective_pt.elem_type)}*' + } else if effective_pt is types.OptionType || effective_pt is types.ResultType { + g.optional_type_name(effective_pt) + } else { + g.tc.c_type(effective_pt) + } + if ct.starts_with('fn_ptr:') { + g.write(g.resolve_fn_ptr_type(ct)) + } else { + g.write(ct) + } + if p.value.len > 0 { + g.write(' ') + param_name := if p.value == '_' { + '_${written}' + } else { + g.local_decl_cname(p.value) + } + g.write(param_name) + } + written++ + if insert_implicit_ctx_after_first && !implicit_ctx_written { + if written < params_len { + g.write(', ') + } + g.write_implicit_veb_ctx_param() + written++ + implicit_ctx_written = true + } + if written < params_len { + g.write(', ') + } + } + if needs_implicit_ctx && !implicit_ctx_written { + g.write_implicit_veb_ctx_param() + } +} + +fn (mut g FlatGen) explicit_mut_pointer_param_type(param flat.Node, typ types.Type) types.Type { + if !param.is_mut || param.op != .amp || typ !is types.Pointer { + return typ + } + decl_type := g.tc.parse_resolution_type(param.typ) + mut decl_depth := 0 + mut decl_base := decl_type + for decl_base is types.Pointer { + decl_depth++ + decl_base = decl_base.base_type + } + mut actual_depth := 0 + mut actual_base := typ + for actual_base is types.Pointer { + actual_depth++ + actual_base = actual_base.base_type + } + if actual_depth > decl_depth { + return typ + } + return types.Type(types.Pointer{ + base_type: typ + }) +} + +fn (g &FlatGen) is_specialized_generic_fn_node(node flat.Node) bool { + return g.cur_fn_is_specialized || g.name_uses_specialized_generic_abi(node.value) +} + +fn (mut g FlatGen) concrete_optional_type_name(t types.Type) string { + clean_type := cgen_unalias_type(t) + mut base_type := types.Type(types.void_) + if clean_type is types.OptionType { + base_type = clean_type.base_type + } else if clean_type is types.ResultType { + base_type = clean_type.base_type + } else { + return g.tc.c_type(clean_type) + } + if base_type is types.Void { + return 'Optional' + } + if g.type_contains_generic_placeholder(base_type) { + return 'Optional' + } + mut inner_ct := g.value_c_type(base_type) + if inner_ct.starts_with('fn_ptr:') { + inner_ct = g.resolve_fn_ptr_type(inner_ct) + } + safe_name := inner_ct.replace('*', 'ptr').replace(' ', '_') + opt_name := 'Optional_${safe_name}' + g.needed_optional_types[opt_name] = inner_ct + return opt_name +} + +fn (mut g FlatGen) write_implicit_veb_ctx_param() { + pt := g.implicit_veb_ctx_type() + g.write(g.tc.c_type(pt)) + g.write(' ctx') +} + +// write_c_fn_node_params writes c fn node params output for c. +fn (mut g FlatGen) write_c_fn_node_params(node flat.Node) { + if node.children_count == 0 { + g.write('void') + return + } + mut written := 0 + for i in 0 .. node.children_count { + param_id := g.a.child(&node, i) + p := g.a.node(param_id) + if p.kind != .param { + continue + } + raw_typ := if p.typ.len > 0 { p.typ } else { p.value } + if raw_typ.len == 0 { + continue + } + pt := g.tc.parse_type(raw_typ) + ct := if pt is types.OptionType || pt is types.ResultType { + g.optional_type_name(pt) + } else { + g.tc.c_type(pt) + } + if written > 0 { + g.write(', ') + } + if ct.starts_with('fn_ptr:') { + g.write(g.resolve_fn_ptr_type(ct)) + } else { + g.write(ct) + } + if p.typ.len > 0 && p.value.len > 0 { + g.write(' ') + param_name := if p.value == '_' { '_${written}' } else { g.cname(p.value) } + g.write(param_name) + } + written++ + } + if written == 0 { + g.write('void') + } +} + +// fn_ptr_typedefs supports fn ptr typedefs handling for FlatGen. +fn (mut g FlatGen) fn_ptr_typedefs() { + // The emitted set persists on g so a second (post-region) call emits only + // typedefs the body workers registered beyond the pre-seeded set. + start_len := g.emitted_fn_ptr_typedefs.len + mut optional_tags := map[string]bool{} + for encoded, _ in g.fn_ptr_types { + if g.used_fn_ptr_types[encoded] && !g.emitted_fn_ptr_typedefs[encoded] { + for tag in fn_ptr_optional_struct_tags(encoded) { + optional_tags[tag] = true + } + } + } + mut sorted_optional_tags := optional_tags.keys() + sorted_optional_tags.sort() + for tag in sorted_optional_tags { + g.writeln('struct ${tag};') + } + if sorted_optional_tags.len > 0 { + g.writeln('') + } + for { + mut pending_encoded := []string{} + for encoded, _ in g.fn_ptr_types { + if !g.used_fn_ptr_types[encoded] || g.emitted_fn_ptr_typedefs[encoded] { + continue + } + pending_encoded << encoded + } + if pending_encoded.len == 0 { + break + } + pending_encoded.sort() + for encoded in pending_encoded { + g.emit_fn_ptr_typedef(encoded, g.fn_ptr_types[encoded], mut g.emitted_fn_ptr_typedefs) + } + } + if g.emitted_fn_ptr_typedefs.len > start_len { + g.writeln('') + } +} + +fn fn_ptr_optional_struct_tags(encoded string) []string { + mut tags := map[string]bool{} + mut i := 0 + for i < encoded.len { + if i + 'Optional'.len <= encoded.len && encoded[i..i + 'Optional'.len] == 'Optional' + && (i == 0 || !c_ident_char(encoded[i - 1])) { + mut end := i + 'Optional'.len + for end < encoded.len && c_ident_char(encoded[end]) { + end++ + } + tags[encoded[i..end]] = true + i = end + continue + } + i++ + } + return tags.keys() +} + +fn (mut g FlatGen) emit_fn_ptr_typedef(encoded string, name string, mut emitted map[string]bool) { + if emitted[encoded] { + return + } + if g.cached_support_identifiers[name] { + emitted[encoded] = true + return + } + emitted[encoded] = true + if !encoded.starts_with('fn_ptr:') { + return + } + ret, params := fn_ptr_typedef_parts(encoded) + if ret.starts_with('Array_fixed_') && ret !in g.fixed_array_ret_wrappers { + return + } + ret_ct := g.fn_ptr_return_ct(g.fn_ptr_typedef_type(ret, mut emitted)) + params_ct := g.fn_ptr_typedef_params(params, mut emitted) + g.writeln('typedef ${ret_ct} (*${name})(${params_ct});') +} + +fn fn_ptr_typedef_parts(encoded string) (string, string) { + payload := if encoded.starts_with('fn_ptr:') { encoded['fn_ptr:'.len..] } else { encoded } + if payload.starts_with('fn_ptr:') { + first_pipe_idx := payload.index('|') or { return payload, 'void' } + rest := payload[first_pipe_idx + 1..] + second_pipe_idx := rest.index('|') or { return payload, 'void' } + split_idx := first_pipe_idx + 1 + second_pipe_idx + return payload[..split_idx], payload[split_idx + 1..] + } + pipe_idx := payload.index('|') or { return payload, 'void' } + return payload[..pipe_idx], payload[pipe_idx + 1..] +} + +fn (mut g FlatGen) fn_ptr_typedef_params(params string, mut emitted map[string]bool) string { + clean := trimmed_space(params) + if clean.len == 0 || clean == 'void' { + return 'void' + } + mut out := []string{} + for param in clean.split(',') { + out << g.fn_ptr_typedef_type(param, mut emitted) + } + return out.join(', ') +} + +fn (mut g FlatGen) fn_ptr_typedef_type(typ string, mut emitted map[string]bool) string { + mut clean := trimmed_space(typ) + if clean.len == 0 { + return 'void' + } + if clean.starts_with('fn_ptr:') { + clean = fn_ptr_typedef_normalized(clean) + name := g.resolve_fn_ptr_type(clean) + g.emit_fn_ptr_typedef(clean, name, mut emitted) + return name + } + if clean == 'Optional' { + return 'struct Optional' + } + if clean.starts_with('Optional_') { + return 'struct ${clean}' + } + if tagged := fn_ptr_typedef_generic_placeholder_struct_tag(clean) { + return tagged + } + if g.fn_ptr_typedef_is_generic_placeholder(clean) { + return 'int' + } + return clean +} + +fn fn_ptr_typedef_normalized(typ string) string { + clean := trimmed_space(typ) + if !clean.starts_with('fn_ptr:') { + return clean + } + payload := clean['fn_ptr:'.len..] + if payload.contains('|') { + return clean + } + return 'fn_ptr:${payload}|void' +} + +fn (g &FlatGen) fn_ptr_typedef_is_generic_placeholder(typ string) bool { + mut clean := trimmed_space(typ) + for clean.ends_with('*') { + clean = clean[..clean.len - 1].trim_space() + } + if clean.starts_with('struct ') { + clean = clean['struct '.len..].trim_space() + } + if g.type_name_known(clean) + || (clean.contains('__') && g.type_name_known(clean.replace('__', '.'))) { + return false + } + short := if clean.contains('__') { + clean.all_after_last('__') + } else if clean.contains('.') { + clean.all_after_last('.') + } else { + clean + } + if !codegen_generic_placeholder_name(short) { + return false + } + if g.type_name_known(short) { + return false + } + return true +} + +fn fn_ptr_typedef_generic_placeholder_struct_tag(typ string) ?string { + mut clean := trimmed_space(typ) + mut ptr_suffix := '' + for clean.ends_with('*') { + clean = clean[..clean.len - 1].trim_space() + ptr_suffix += '*' + } + if clean.starts_with('struct ') { + clean = clean['struct '.len..].trim_space() + } + segment := if clean.contains('__') { + clean.all_after_last('__') + } else { + clean + } + if !segment.contains('_') { + return none + } + suffix := segment.all_after_last('_') + if suffix.len == 1 && suffix[0] >= `A` && suffix[0] <= `Z` { + return 'struct ${clean}${ptr_suffix}' + } + return none +} + +// multi_return_forward_decls forward-declares every multi-return struct that the +// generated C can reference by name. It must run before fn_ptr_typedefs(), because +// a function-pointer typedef may name a multi-return as its (by-value) return type +// — and a `typedef RET (*fp)(...)` only needs RET's tag declared, not its full +// layout. The full struct bodies are emitted later by multi_return_typedefs(), +// after the member struct definitions they depend on are available. +fn (mut g FlatGen) multi_return_forward_decls() { + mut emitted := map[string]bool{} + g.walk_multi_return_typedefs(mut emitted, true) + // Also cover multi-returns reachable only as a fn-pointer return type: parallel + // cgen preseeds fn-ptr types that the serial path never materializes, so their + // return multi-returns may not appear among the function/expression types above. + for encoded, _ in g.fn_ptr_types { + if !g.used_fn_ptr_types[encoded] { + continue + } + ret, _ := fn_ptr_typedef_parts(encoded) + if ret.starts_with('multi_return_') && ret !in emitted { + emitted[ret] = true + g.writeln('typedef struct ${ret} ${ret};') + } + } + if emitted.len > 0 { + g.writeln('') + } +} + +// multi_return_typedefs emits the full struct definitions for multi-return types. +fn (mut g FlatGen) multi_return_typedefs() { + mut emitted := map[string]bool{} + g.walk_multi_return_typedefs(mut emitted, false) + if emitted.len > 0 { + g.writeln('') + } +} + +// walk_multi_return_typedefs visits every multi-return type reachable from a +// function return type or an expression type and emits it via emit_multi_return_typedef. +// Shared by the forward-declaration and full-definition passes so both see the same +// set in the same (deterministic) order. +fn (mut g FlatGen) walk_multi_return_typedefs(mut emitted map[string]bool, forward_only bool) { + if !g.multi_return_types_ready { + g.collect_multi_return_types() + } + for typ in g.multi_return_types { + g.emit_concrete_multi_return_typedef(typ, mut emitted, forward_only) + } +} + +fn (mut g FlatGen) collect_multi_return_types() { + g.collect_declaration_signature_types() +} + +fn (mut g FlatGen) collect_concrete_multi_return_type(typ types.Type) { + if g.type_contains_generic_placeholder(typ) { + return + } + g.collect_known_concrete_multi_return_type(typ) +} + +fn (mut g FlatGen) collect_known_concrete_multi_return_type(typ types.Type) { + if typ is types.OptionType { + g.collect_known_concrete_multi_return_type(typ.base_type) + return + } + if typ is types.ResultType { + g.collect_known_concrete_multi_return_type(typ.base_type) + return + } + if typ is types.MultiReturn { + for part in typ.types { + // A discarded destructuring slot can be recorded as the synthetic + // type `_`. It has no C representation and the actual call tuple is + // already collected from the callee's return signature. + if part.name() == '_' || g.multi_return_field_c_type(part) == '_' { + return + } + } + name := g.multi_return_c_type_name(typ) + if name !in g.multi_return_type_names { + g.multi_return_type_names[name] = true + g.multi_return_types << types.Type(typ) + } + } +} + +fn (mut g FlatGen) emit_concrete_multi_return_typedef(ret types.Type, mut emitted map[string]bool, forward_only bool) { + if g.type_contains_generic_placeholder(ret) { + return + } + g.emit_multi_return_typedef(ret, mut emitted, forward_only) +} + +// emit_multi_return_typedef emits one multi-return type: a forward declaration +// (`typedef struct NAME NAME;`) when forward_only, otherwise the full struct body +// (`struct NAME { ... };`). The two forms are paired — the forward decl provides the +// typedef name, the body completes the tagged struct. +fn (mut g FlatGen) emit_multi_return_typedef(ret types.Type, mut emitted map[string]bool, forward_only bool) { + if ret is types.OptionType { + g.emit_multi_return_typedef(ret.base_type, mut emitted, forward_only) + return + } + if ret is types.ResultType { + g.emit_multi_return_typedef(ret.base_type, mut emitted, forward_only) + return + } + if ret is types.MultiReturn { + name := g.multi_return_c_type_name(ret) + if name in emitted { + return + } + emitted[name] = true + if forward_only { + for typ in ret.types { + ct := g.value_c_type(typ) + if ct.starts_with('fn_ptr:') { + g.resolve_fn_ptr_type(ct) + } + } + g.writeln('typedef struct ${name} ${name};') + } else { + g.emit_multi_return_field_option_typedefs(ret) + g.writeln('struct ${name} {') + for i, typ in ret.types { + mut ct := g.multi_return_field_c_type(typ) + if ct.starts_with('fn_ptr:') { + ct = g.resolve_fn_ptr_type(ct) + } + g.writeln('\t${ct} arg${i};') + } + g.writeln('};') + } + } +} + +fn (mut g FlatGen) emit_multi_return_field_option_typedefs(ret types.MultiReturn) { + for typ in ret.types { + if typ is types.OptionType || typ is types.ResultType { + opt_name := g.optional_type_name(typ) + if val_type := g.needed_optional_types[opt_name] { + g.emit_optional_typedef(opt_name, val_type) + } + } + } +} + +// resolve_fn_ptr_type resolves resolve fn ptr type information for c. +fn (mut g FlatGen) resolve_fn_ptr_type(typ string) string { + g.used_fn_ptr_types[typ] = true + return g.register_fn_ptr_type(typ) +} + +fn (mut g FlatGen) register_fn_ptr_type(typ string) string { + if typ in g.fn_ptr_types { + return g.fn_ptr_types[typ] + } + name := naming.fn_ptr_type_name(typ) + g.fn_ptr_types[typ] = name + return name +} + +// fn_ptr_type_key returns the normalized key used for function-pointer typedefs. +fn (mut g FlatGen) fn_ptr_type_key(typ types.FnType) string { + ret := if typ.return_type is types.Void { 'void' } else { g.tc.c_type(typ.return_type) } + if typ.params.len == 0 { + return 'fn_ptr:${ret}|void' + } + mut params := []string{} + for param in typ.params { + params << g.tc.c_type(param) + } + return 'fn_ptr:${ret}|${params.join(', ')}' +} diff --git a/vlib/v3/gen/fastc/fn_parallel_d_v3_no_parallel.v b/vlib/v3/gen/fastc/fn_parallel_d_v3_no_parallel.v new file mode 100644 index 00000000000000..f1681fdbe4edc8 --- /dev/null +++ b/vlib/v3/gen/fastc/fn_parallel_d_v3_no_parallel.v @@ -0,0 +1,72 @@ +module fastc + +import v3.flat +import time + +fn (mut g FlatGen) refine_fn_item_costs(_ bool, _ bool) {} + +fn par_cgen_prep_enabled() bool { + return false +} + +fn (mut g FlatGen) scan_collect_gen_info() CollectGenInfoScanCounts { + return g.scan_collect_gen_info_serial() +} + +fn (mut g FlatGen) apply_fn_signature_registrations(registrations []FnSignatureRegistration) { + for group in 0 .. 4 { + for registration in registrations { + g.apply_fn_signature_registration_group(registration, group) + } + } +} + +fn (mut g FlatGen) prepare_shared_sum_and_fixed_array_ret_wrappers(_ bool) bool { + mut sw := time.new_stopwatch() + g.collect_shared_type_names() + g.precompute_sum_name_lookup() + if !g.skip_generics { + g.precompute_generic_method_candidate_index() + } + g.timing_profile(' [ttime] wr shared+sum ${f64(sw.elapsed().microseconds()) / 1000.0:7.2f} ms') + sw.restart() + g.populate_fixed_array_ret_wrappers() + g.timing_profile(' [ttime] wr fixed ret ${f64(sw.elapsed().microseconds()) / 1000.0:7.2f} ms') + return false +} + +fn (mut g FlatGen) collect_gen_info_fn_preps(_ []int) []CollectGenFnPrep { + return []CollectGenFnPrep{} +} + +fn (mut g FlatGen) collect_fn_gen_candidates_parallel(direct_array_access_fns DirectArrayAccessFns, ignore_overflow_fns DirectArrayAccessFns, program_modules map[string]bool) []FlatFnGenCandidate { + nodes := g.top_level_nodes() + return g.collect_fn_gen_candidates_range(nodes, 0, nodes.len, '', '', direct_array_access_fns, + ignore_overflow_fns, program_modules) +} + +// gen_fns_dispatch emits all functions serially when v3 is built with the +// internal `v3_no_parallel` define. +fn (mut g FlatGen) gen_fns_dispatch(_ bool) { + g.gen_test_failure_global() + g.gen_fns() + g.gen_synthetic_main_after_fns() +} + +// prepare_serial_fn_tables is unnecessary when parallel cgen is compiled out. +fn (mut g FlatGen) prepare_serial_fn_tables() {} + +// run_pre_dispatch_parallel is serial-only in `v3_no_parallel` builds. +fn (mut g FlatGen) run_pre_dispatch_parallel(_ bool) bool { + return false +} + +// fn_item_cost_and_prep never pre-seeds in `v3_no_parallel` builds; it is only +// reachable with want_parallel_prep set, which nothing sets here. +fn (mut g FlatGen) fn_item_cost_and_prep(node_id flat.NodeId, mut _stack []flat.NodeId, mut _type_text_cache map[string]bool) int { + return flat_fn_gen_item_cost(g.a, node_id) +} + +fn (mut g FlatGen) fn_item_cost_and_c_extern_prep(node_id flat.NodeId, mut _stack []flat.NodeId) int { + return flat_fn_gen_item_cost(g.a, node_id) +} diff --git a/vlib/v3/gen/fastc/fn_parallel_notd_v3_no_parallel.v b/vlib/v3/gen/fastc/fn_parallel_notd_v3_no_parallel.v new file mode 100644 index 00000000000000..c902285ac99abd --- /dev/null +++ b/vlib/v3/gen/fastc/fn_parallel_notd_v3_no_parallel.v @@ -0,0 +1,2948 @@ +module fastc + +import os +import runtime +import strings +import time +import v3.flat +import v3.gen.fastc.naming +import v3.types +import v3.workers + +const max_flat_cgen_jobs = 18 +const max_flat_cgen_select_jobs = 15 +const min_flat_cgen_parallel_items = 128 +// Bound each worker's retained scratch while generating compiler-sized ASTs. +const scoped_cgen_worker_batches = 32 +const flat_cgen_chunks_per_job = 12 + +$if !windows { + // FlatCgenChunkArgs represents flat cgen chunk args data used by c. + struct FlatCgenChunkArgs { + worker voidptr + work_items_ptr voidptr + is_master bool + } + + struct FlatCgenCostArgs { + a &flat.FlatAst + items_ptr voidptr + start int + end int + g voidptr // &FlatGen, non-nil in fused prep mode (read-only access) + mut: + refs map[string]bool + cands []FlatCgenPrepCandidate + } + + struct FlatCgenDynamicArgs { + dispatcher voidptr + worker_id int + work_chunks_ptr voidptr + chunk_queue chan int + reserve_cost i64 + mut: + worker voidptr + setup_scope voidptr + } + + struct CollectGenInfoFnPrepArgs { + g voidptr // read-only &FlatGen master + node_ids_ptr voidptr // &[]int + preps_ptr voidptr // &[]CollectGenFnPrep; shards fill disjoint positions + start int + end int + file string + module_name string + } + + struct CollectGenInfoScanArgs { + g voidptr // read-only &FlatGen master + start int + end int + mut: + counts CollectGenInfoScanCounts + top_level_pos int + string_pos int + top_levels_ptr voidptr + strings_ptr voidptr + } + + struct FnSignatureRegistrationArgs { + g voidptr + registrations_ptr voidptr + group int + } + + fn fn_signature_registration_thread(arg voidptr) voidptr { + a := unsafe { &FnSignatureRegistrationArgs(arg) } + mut g := unsafe { &FlatGen(a.g) } + registrations := unsafe { &[]FnSignatureRegistration(a.registrations_ptr) } + for registration in registrations { + g.apply_fn_signature_registration_group(registration, a.group) + } + return unsafe { nil } + } + + struct FlatCgenSelectArgs { + g voidptr + nodes_ptr voidptr + start int + end int + file string + module_name string + direct_array_access_fns DirectArrayAccessFns + ignore_overflow_fns DirectArrayAccessFns + program_modules map[string]bool + mut: + candidates []FlatFnGenCandidate + scope voidptr + } + + fn flat_cgen_select_thread(arg voidptr) voidptr { + mut a := unsafe { &FlatCgenSelectArgs(arg) } + a.scope = cgen_worker_scope_begin(true) + master := unsafe { &FlatGen(a.g) } + mut view := master.new_collect_gen_info_view() + nodes := unsafe { &[]int(a.nodes_ptr) } + a.candidates = view.collect_fn_gen_candidates_range(*nodes, a.start, a.end, a.file, + a.module_name, a.direct_array_access_fns, a.ignore_overflow_fns, a.program_modules) + cgen_worker_scope_leave(a.scope) + return unsafe { nil } + } + + fn collect_gen_info_fn_prep_thread(arg voidptr) voidptr { + a := unsafe { &CollectGenInfoFnPrepArgs(arg) } + master := unsafe { &FlatGen(a.g) } + mut view := master.new_collect_gen_info_view() + view.tc.cur_file = a.file + view.tc.cur_module = a.module_name + node_ids := unsafe { &[]int(a.node_ids_ptr) } + mut preps := unsafe { &[]CollectGenFnPrep(a.preps_ptr) } + mut cur_file := a.file + mut cur_module := a.module_name + for pos in a.start .. a.end { + node_idx := unsafe { node_ids[pos] } + node := view.a.nodes[node_idx] + if node.kind == .file { + cur_file = node.value + cur_module = 'main' + } else if node.kind == .module_decl { + cur_module = node.value + } else if node.kind == .fn_decl && (!view.has_used_fn_filter() + || view.used_fn_contains_in_module(node.value, cur_module)) { + unsafe { + preps[pos] = view.compute_collect_gen_fn_prep(node, cur_module, cur_file) + } + } + } + return unsafe { nil } + } + + @[direct_array_access] + fn collect_gen_info_scan_count_thread(arg voidptr) voidptr { + mut a := unsafe { &CollectGenInfoScanArgs(arg) } + g := unsafe { &FlatGen(a.g) } + incremental := g.incremental_fn_names.len > 0 + for node_idx in a.start .. a.end { + node := g.a.nodes[node_idx] + if node.kind == .string_literal { + a.string_pos++ + } + if node.kind in [.file, .module_decl, .fn_decl, .c_fn_decl, .struct_decl, .type_decl, + .global_decl, .const_decl, .enum_decl, .interface_decl, .import_decl, .directive] { + a.top_level_pos++ + } + match node.kind { + .fn_decl { + if !incremental || g.incremental_fn_names[node.value] { + a.counts.fn_count++ + } + } + .struct_decl { + a.counts.struct_count++ + } + .global_decl { + a.counts.global_count += int(node.children_count) + } + .const_decl { + a.counts.const_count += int(node.children_count) + } + .enum_decl { + a.counts.enum_field_count += int(node.children_count) + } + .interface_decl { + a.counts.interface_count++ + } + .import_decl { + a.counts.import_count++ + } + else {} + } + } + return unsafe { nil } + } + + @[direct_array_access] + fn collect_gen_info_scan_fill_thread(arg voidptr) voidptr { + mut a := unsafe { &CollectGenInfoScanArgs(arg) } + g := unsafe { &FlatGen(a.g) } + mut top_levels := unsafe { &[]int(a.top_levels_ptr) } + mut literals := unsafe { &[]string(a.strings_ptr) } + mut top_level_pos := a.top_level_pos + mut string_pos := a.string_pos + for node_idx in a.start .. a.end { + node := g.a.nodes[node_idx] + if node.kind == .string_literal { + unsafe { + literals[string_pos] = node.value + } + string_pos++ + } + if node.kind in [.file, .module_decl, .fn_decl, .c_fn_decl, .struct_decl, .type_decl, + .global_decl, .const_decl, .enum_decl, .interface_decl, .import_decl, .directive] { + unsafe { + top_levels[top_level_pos] = node_idx + } + top_level_pos++ + } + } + return unsafe { nil } + } + + fn flat_cgen_cost_thread(arg voidptr) voidptr { + mut a := unsafe { &FlatCgenCostArgs(arg) } + mut items := unsafe { &[]FlatFnGenItem(a.items_ptr) } + mut stack := []flat.NodeId{cap: 256} + if !isnil(a.g) { + // Fused prep mode: also collect the fn-ptr preseed candidates the + // master replays in order after the join (see refine_fn_item_costs). + g := unsafe { &FlatGen(a.g) } + mut text_cache := &PrepTypTextCache{} + mut type_seen := &PreseedTypeSeen{} + mut cur_file := '' + mut cur_module := '' + for idx in a.start .. a.end { + unsafe { + item_file := items[idx].file + item_module := items[idx].module + if item_file != cur_file || item_module != cur_module { + cur_file = item_file + cur_module = item_module + text_cache.generation++ + } + cost, needs_prelude_scan := exact_flat_fn_gen_item_cost_and_prep(g, + items[idx].node_id, idx, mut a.refs, mut stack, mut a.cands, mut + text_cache, mut type_seen) + items[idx].cost = cost + items[idx].skip_prelude_scan = !needs_prelude_scan + } + } + return unsafe { nil } + } + for idx in a.start .. a.end { + unsafe { + cost, needs_prelude_scan := exact_flat_fn_gen_item_cost(a.a, items[idx].node_id, mut + a.refs, mut stack) + items[idx].cost = cost + items[idx].skip_prelude_scan = !needs_prelude_scan + } + } + return unsafe { nil } + } + + fn parallel_type_decls_thread(arg voidptr) voidptr { + mut w := unsafe { &FlatGen(arg) } + tdsw := time.new_stopwatch() + defer { + w.timing_profile(' [ttime] cg typedecls ${f64(tdsw.elapsed().microseconds()) / 1000.0:7.2f} ms (task)') + } + // This task uses the master generator from a pool thread. Keep caches + // disabled because their entries would otherwise borrow that thread's + // disposable arena. + w.import_alias_cache = unsafe { nil } + w.enum_selector_cache = unsafe { nil } + w.enum_method_cache = unsafe { nil } + w.qualified_enum_method_cache = unsafe { nil } + w.local_typedef_shadow_facts = unsafe { nil } + w.local_global_shadow_facts = unsafe { nil } + // Self-host declaration output is several MiB. Reserve it once instead of + // repeatedly copying a geometrically growing builder. + w.sb.ensure_cap(4 * 1024 * 1024) + mut tdpsw := time.new_stopwatch() + w.parallel_const_code = w.precompute_consts() + w.timing_profile(' [ttime] td consts ${f64(tdpsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + tdpsw.restart() + w.gen_translation_unit_prefix() + w.gen_type_declaration_block() + w.timing_profile(' [ttime] td prefix+type ${f64(tdpsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + w.parallel_type_decls = w.sb.str() + unsafe { w.sb.free() } + w.sb = strings.new_builder(4096) + tdpsw.restart() + w.gen_global_declaration_block() + w.parallel_global_decls = w.sb.str() + unsafe { w.sb.free() } + w.sb = strings.new_builder(4096) + w.timing_profile(' [ttime] td globals ${f64(tdpsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + tdpsw.restart() + w.forward_decls() + w.timing_profile(' [ttime] td fwd decls ${f64(tdpsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + w.parallel_forward_decls = w.sb.str() + unsafe { w.sb.free() } + w.sb = strings.new_builder(4096) + tdpsw.restart() + w.gen_pre_body_support_declarations() + w.parallel_support_decls = w.sb.str() + unsafe { w.sb.free() } + w.sb = strings.new_builder(4096) + w.timing_profile(' [ttime] td support ${f64(tdpsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + tdpsw.restart() + if !w.skip_enum_autostr { + w.enum_str_defs() + w.parallel_enum_str_defs = w.sb.str() + unsafe { w.sb.free() } + w.sb = strings.new_builder(4096) + } + w.timing_profile(' [ttime] td enum str ${f64(tdpsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + tdpsw.restart() + // Tail generation uses a full private worker. The master generator is also + // the declaration task, while body lanes concurrently read its frozen + // tables; running these emitters on the master would mutate shared name and + // type caches. The private worker keeps those writes isolated while its + // already-complete const/global metadata is read-only. + mut tail := w.new_parallel_tail_worker(max_flat_cgen_jobs + 1) + if !w.cache_split { + tail.interface_method_stubs() + w.parallel_interface_stubs = tail.sb.str() + unsafe { tail.sb.free() } + tail.sb = strings.new_builder(4096) + } + w.timing_profile(' [ttime] td iface defs ${f64(tdpsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + tdpsw.restart() + if w.print_fn_names.len == 0 { + tail.gen_vinit() + tail.gen_vcleanup() + w.parallel_init_defs = tail.sb.str() + unsafe { tail.sb.free() } + tail.sb = strings.new_builder(0) + } + w.timing_profile(' [ttime] td init defs ${f64(tdpsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + w.parallel_support_ready = true + return unsafe { nil } + } + + // fixed_storage_scan_thread runs the fixed-storage-const use scan (a full + // post-transform AST pass) on a private fork while the master collects the + // fn work items and pre-seeds the parallel tables. + fn fixed_storage_scan_thread(arg voidptr) voidptr { + mut w := unsafe { &FlatGen(arg) } + mut fssw := time.new_stopwatch() + scope := cgen_worker_scope_begin(w.scope_parallel_workers) + w.collect_fixed_storage_consts(true) + w.timing_profile(' [ttime] fs consts ${f64(fssw.elapsed().microseconds()) / 1000.0:7.2f} ms') + fssw.restart() + w.precompute_param_type_index() + w.timing_profile(' [ttime] fs param idx ${f64(fssw.elapsed().microseconds()) / 1000.0:7.2f} ms') + fssw.restart() + w.precompute_concrete_optional_abi_fns() + w.timing_profile(' [ttime] fs opt abi ${f64(fssw.elapsed().microseconds()) / 1000.0:7.2f} ms') + w.worker_scope = scope + cgen_worker_scope_leave(scope) + return unsafe { nil } + } + + // fixed_array_support_thread moves the independent whole-AST fixed-array + // discovery out of the serial type-declaration task. + fn fixed_array_support_thread(arg voidptr) voidptr { + mut w := unsafe { &FlatGen(arg) } + fsw := time.new_stopwatch() + scope := cgen_worker_scope_begin(w.scope_parallel_workers) + _ = w.collect_fixed_array_typedefs_needed() + w.timing_profile(' [ttime] fs fixed types ${f64(fsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + w.worker_scope = scope + cgen_worker_scope_leave(scope) + return unsafe { nil } + } + + // optional_support_thread fuses the declaration-signature, multi-return and + // unresolved-call optional scans on a helper while the other predispatch + // workers traverse the same immutable AST. + fn optional_support_thread(arg voidptr) voidptr { + mut w := unsafe { &FlatGen(arg) } + osw := time.new_stopwatch() + scope := cgen_worker_scope_begin(w.scope_parallel_workers) + w.collect_optional_typedefs() + w.timing_profile(' [ttime] fs opt types ${f64(osw.elapsed().microseconds()) / 1000.0:7.2f} ms') + w.worker_scope = scope + cgen_worker_scope_leave(scope) + return unsafe { nil } + } + + // interface_impl_scan_thread builds the structural-interface dispatch tables + // while the master pre-seeds independent declaration metadata. + fn interface_impl_scan_thread(arg voidptr) voidptr { + mut w := unsafe { &FlatGen(arg) } + scope := cgen_worker_scope_begin(w.scope_parallel_workers) + w.collect_interface_impls() + w.worker_scope = scope + cgen_worker_scope_leave(scope) + return arg + } + + fn fixed_array_ret_wrappers_thread(arg voidptr) voidptr { + mut w := unsafe { &FlatGen(arg) } + scope := cgen_worker_scope_begin(w.scope_parallel_workers) + w.populate_fixed_array_ret_wrappers() + w.worker_scope = scope + cgen_worker_scope_leave(scope) + return unsafe { nil } + } + + fn cgen_support_precompute_thread(arg voidptr) voidptr { + mut w := unsafe { &FlatGen(arg) } + scope := cgen_worker_scope_begin(w.scope_parallel_workers) + w.precompute_ownership_recursive_drop_helpers() + w.precompute_fixed_array_map_key_types() + w.worker_scope = scope + cgen_worker_scope_leave(scope) + return unsafe { nil } + } + + fn pre_dispatch_master_thread(arg voidptr) voidptr { + mut g := unsafe { &FlatGen(arg) } + g.prepare_pre_dispatch_master() + return unsafe { nil } + } + + // flat_cgen_chunk_thread supports flat cgen chunk thread handling for c. + fn flat_cgen_chunk_thread(arg voidptr) voidptr { + a := unsafe { &FlatCgenChunkArgs(arg) } + mut w := unsafe { &FlatGen(a.worker) } + items := unsafe { &[]FlatFnGenItem(a.work_items_ptr) } + if w.scope_parallel_workers { + if a.is_master { + w.gen_fn_items_scoped_master_batches(*items) + } else { + w.gen_fn_items_scoped_batches(*items) + } + } else { + w.gen_fn_items(*items) + } + return unsafe { nil } + } + + fn flat_cgen_dynamic_thread(arg voidptr) voidptr { + mut a := unsafe { &FlatCgenDynamicArgs(arg) } + if isnil(a.worker) { + dispatcher := unsafe { &FlatGen(a.dispatcher) } + a.setup_scope = cgen_worker_scope_begin(dispatcher.scope_parallel_workers) + a.worker = voidptr(dispatcher.new_parallel_dispatch_worker(a.worker_id)) + cgen_worker_scope_leave(a.setup_scope) + } + mut w := unsafe { &FlatGen(a.worker) } + chunks := unsafe { &[][]FlatFnGenItem(a.work_chunks_ptr) } + w.gen_fn_chunks_scoped_dynamic(*chunks, a.chunk_queue, a.reserve_cost) + return unsafe { nil } + } +} + +fn (mut g FlatGen) collect_fn_gen_candidates_parallel(direct_array_access_fns DirectArrayAccessFns, ignore_overflow_fns DirectArrayAccessFns, program_modules map[string]bool) []FlatFnGenCandidate { + $if windows { + nodes := g.top_level_nodes() + return g.collect_fn_gen_candidates_range(nodes, 0, nodes.len, '', '', + direct_array_access_fns, ignore_overflow_fns, program_modules) + } $else { + nodes := g.top_level_nodes() + if isnil(g.a.worker_pool) || g.a.worker_pool.size() == 0 || nodes.len < 2048 { + return g.collect_fn_gen_candidates_range(nodes, 0, nodes.len, '', '', + direct_array_access_fns, ignore_overflow_fns, program_modules) + } + mut n_jobs := g.a.worker_pool.size() + 1 + if n_jobs > max_flat_cgen_select_jobs { + n_jobs = max_flat_cgen_select_jobs + } + if n_jobs > nodes.len { + n_jobs = nodes.len + } + mut files := []string{len: n_jobs} + mut modules := []string{len: n_jobs} + mut boundary := 0 + mut cur_file := '' + mut cur_module := '' + for pos in 0 .. nodes.len { + for boundary < n_jobs && pos == nodes.len * boundary / n_jobs { + files[boundary] = cur_file + modules[boundary] = cur_module + boundary++ + } + node := g.a.nodes[nodes[pos]] + if node.kind == .file { + cur_file = node.value + cur_module = '' + } else if node.kind == .module_decl { + cur_module = node.value + } + } + mut args := []FlatCgenSelectArgs{cap: n_jobs} + for job in 0 .. n_jobs { + args << FlatCgenSelectArgs{ + g: voidptr(g) + nodes_ptr: unsafe { voidptr(&nodes) } + start: nodes.len * job / n_jobs + end: nodes.len * (job + 1) / n_jobs + file: files[job] + module_name: modules[job] + direct_array_access_fns: direct_array_access_fns + ignore_overflow_fns: ignore_overflow_fns + program_modules: program_modules + candidates: []FlatFnGenCandidate{} + scope: unsafe { nil } + } + } + mut tasks := []workers.Task{cap: n_jobs} + for job in 0 .. n_jobs { + tasks << workers.Task{ + run: flat_cgen_select_thread + arg: unsafe { voidptr(&args[job]) } + force_sync: job == 0 + } + } + g.a.worker_pool.run(tasks) + mut candidates := []FlatFnGenCandidate{} + for arg in args { + candidates << arg.candidates + if arg.scope != unsafe { nil } { + g.parallel_worker_scopes << arg.scope + } + } + return candidates + } +} + +// scan_collect_gen_info partitions the read-only whole-AST sizing scan across +// the persistent pool. A count pass computes exact output offsets, then a fill +// pass writes disjoint ranges while preserving AST order. +fn (mut g FlatGen) scan_collect_gen_info() CollectGenInfoScanCounts { + $if windows { + return g.scan_collect_gen_info_serial() + } $else { + if isnil(g.a.worker_pool) || g.a.worker_pool.size() == 0 || g.a.nodes.len < 65_536 + || os.getenv('V3_NO_PAR_CGEN_INFO_SCAN') != '' { + return g.scan_collect_gen_info_serial() + } + mut n_jobs := g.a.worker_pool.size() + 1 + if n_jobs > max_flat_cgen_jobs { + n_jobs = max_flat_cgen_jobs + } + mut args := []CollectGenInfoScanArgs{cap: n_jobs} + mut tasks := []workers.Task{cap: n_jobs} + for job in 0 .. n_jobs { + args << CollectGenInfoScanArgs{ + g: voidptr(g) + start: g.a.nodes.len * job / n_jobs + end: g.a.nodes.len * (job + 1) / n_jobs + } + } + for job in 0 .. n_jobs { + tasks << workers.Task{ + run: collect_gen_info_scan_count_thread + arg: unsafe { voidptr(&args[job]) } + force_sync: job == 0 + } + } + g.a.worker_pool.run(tasks) + mut counts := CollectGenInfoScanCounts{} + mut top_level_count := 0 + mut string_count := 0 + for mut arg in args { + counts.fn_count += arg.counts.fn_count + counts.struct_count += arg.counts.struct_count + counts.global_count += arg.counts.global_count + counts.const_count += arg.counts.const_count + counts.enum_field_count += arg.counts.enum_field_count + counts.interface_count += arg.counts.interface_count + counts.import_count += arg.counts.import_count + counted_top_levels := arg.top_level_pos + counted_strings := arg.string_pos + arg.top_level_pos = top_level_count + arg.string_pos = string_count + top_level_count += counted_top_levels + string_count += counted_strings + } + g.top_level_node_ids = []int{len: top_level_count} + g.ast_string_literals = []string{len: string_count} + for mut arg in args { + arg.top_levels_ptr = unsafe { voidptr(&g.top_level_node_ids) } + arg.strings_ptr = unsafe { voidptr(&g.ast_string_literals) } + } + tasks.clear() + for job in 0 .. n_jobs { + tasks << workers.Task{ + run: collect_gen_info_scan_fill_thread + arg: unsafe { voidptr(&args[job]) } + force_sync: job == 0 + } + } + g.a.worker_pool.run(tasks) + return counts + } +} + +fn (mut g FlatGen) apply_fn_signature_registrations(registrations []FnSignatureRegistration) { + $if windows { + for group in 0 .. 4 { + for registration in registrations { + g.apply_fn_signature_registration_group(registration, group) + } + } + } $else { + if registrations.len < 128 || isnil(g.a.worker_pool) || g.a.worker_pool.size() < 4 + || os.getenv('V3_NO_PAR_CGEN_SIG_REG') != '' { + for group in 0 .. 4 { + for registration in registrations { + g.apply_fn_signature_registration_group(registration, group) + } + } + return + } + mut args := []FnSignatureRegistrationArgs{cap: 4} + mut tasks := []workers.Task{cap: 4} + for group in 0 .. 4 { + args << FnSignatureRegistrationArgs{ + g: voidptr(g) + registrations_ptr: unsafe { voidptr(®istrations) } + group: group + } + } + for group in 0 .. 4 { + tasks << workers.Task{ + run: fn_signature_registration_thread + arg: unsafe { voidptr(&args[group]) } + force_sync: group == 0 + } + } + g.a.worker_pool.run(tasks) + } +} + +fn (mut g FlatGen) prepare_shared_sum_and_fixed_array_ret_wrappers(parallel bool) bool { + mut sw := time.new_stopwatch() + $if windows { + g.collect_shared_type_names() + g.precompute_sum_name_lookup() + if !g.skip_generics { + g.precompute_generic_method_candidate_index() + } + g.timing_profile(' [ttime] wr shared+sum ${f64(sw.elapsed().microseconds()) / 1000.0:7.2f} ms') + sw.restart() + g.populate_fixed_array_ret_wrappers() + g.timing_profile(' [ttime] wr fixed ret ${f64(sw.elapsed().microseconds()) / 1000.0:7.2f} ms') + return false + } $else { + if !parallel || os.getenv('V3_NO_PAR_FIXED_RET') != '' { + g.collect_shared_type_names() + g.precompute_sum_name_lookup() + if !g.skip_generics { + g.precompute_generic_method_candidate_index() + } + g.timing_profile(' [ttime] wr shared+sum ${f64(sw.elapsed().microseconds()) / 1000.0:7.2f} ms') + sw.restart() + g.populate_fixed_array_ret_wrappers() + g.timing_profile(' [ttime] wr fixed ret ${f64(sw.elapsed().microseconds()) / 1000.0:7.2f} ms') + return false + } + mut worker := g.new_parallel_worker(3) + worker.fixed_array_ret_wrappers = map[string]bool{} + mut support_worker := g.new_parallel_worker(5) + support_worker.recursive_drop_helpers = map[string]string{} + support_worker.fixed_array_map_key_types = map[string]types.ArrayFixed{} + wrapper_thread := spawn fixed_array_ret_wrappers_thread(voidptr(worker)) + support_thread := spawn cgen_support_precompute_thread(voidptr(support_worker)) + g.collect_shared_type_names() + g.precompute_sum_name_lookup() + if !g.skip_generics { + g.precompute_generic_method_candidate_index() + } + g.timing_profile(' [ttime] wr shared+sum ${f64(sw.elapsed().microseconds()) / 1000.0:7.2f} ms') + sw.restart() + _ = wrapper_thread.wait() + _ = support_thread.wait() + g.fixed_array_ret_wrappers = worker.fixed_array_ret_wrappers.move() + g.recursive_drop_helpers = support_worker.recursive_drop_helpers.move() + g.fixed_array_map_key_types = support_worker.fixed_array_map_key_types.move() + if worker.worker_scope != unsafe { nil } { + g.parallel_worker_scopes << worker.worker_scope + worker.worker_scope = unsafe { nil } + } + if support_worker.worker_scope != unsafe { nil } { + g.parallel_worker_scopes << support_worker.worker_scope + support_worker.worker_scope = unsafe { nil } + } + g.timing_profile(' [ttime] wr fixed ret ${f64(sw.elapsed().microseconds()) / 1000.0:7.2f} ms (overlapped)') + return true + } +} + +// collect_gen_info_fn_preps resolves used function signatures on the persistent +// worker pool. Registration stays serial in collect_gen_info, preserving all +// source-order and duplicate-declaration semantics. +fn (mut g FlatGen) collect_gen_info_fn_preps(node_ids []int) []CollectGenFnPrep { + $if windows { + return []CollectGenFnPrep{} + } $else { + if isnil(g.a.worker_pool) || g.a.worker_pool.size() == 0 || node_ids.len < 2048 + || os.getenv('V3_NO_PAR_CGEN_INFO_FNS') != '' { + return []CollectGenFnPrep{} + } + mut n_jobs := g.a.worker_pool.size() + 1 + if n_jobs > max_flat_cgen_jobs { + n_jobs = max_flat_cgen_jobs + } + mut preps := []CollectGenFnPrep{len: node_ids.len} + mut context_files := []string{len: n_jobs} + mut context_modules := []string{len: n_jobs} + mut cur_file := '' + mut cur_module := 'main' + mut boundary := 0 + for pos in 0 .. node_ids.len { + for boundary < n_jobs && pos == node_ids.len * boundary / n_jobs { + context_files[boundary] = cur_file + context_modules[boundary] = cur_module + boundary++ + } + node := g.a.nodes[node_ids[pos]] + if node.kind == .file { + cur_file = node.value + cur_module = 'main' + } else if node.kind == .module_decl { + cur_module = node.value + } + } + for boundary < n_jobs { + context_files[boundary] = cur_file + context_modules[boundary] = cur_module + boundary++ + } + mut args := []CollectGenInfoFnPrepArgs{cap: n_jobs} + mut tasks := []workers.Task{cap: n_jobs} + for job in 0 .. n_jobs { + args << CollectGenInfoFnPrepArgs{ + g: voidptr(g) + node_ids_ptr: unsafe { voidptr(&node_ids) } + preps_ptr: unsafe { voidptr(&preps) } + start: node_ids.len * job / n_jobs + end: node_ids.len * (job + 1) / n_jobs + file: context_files[job] + module_name: context_modules[job] + } + } + for job in 0 .. n_jobs { + tasks << workers.Task{ + run: collect_gen_info_fn_prep_thread + arg: unsafe { voidptr(&args[job]) } + force_sync: job == 0 + } + } + g.a.worker_pool.run(tasks) + return preps + } +} + +// finish_pending_item_prep_serial is the fallback for the work item selection +// defers to the parallel exact-cost pass (C-extern refs, and in parallel-prep +// mode also costs and fn-ptr preseeds): when that pass cannot run, do the +// deferred work serially like the former fused prep walk did. +fn (mut g FlatGen) finish_pending_item_prep_serial() { + if !g.prep_externs_pending && !g.prep_costs_pending { + return + } + mut stack := []flat.NodeId{cap: 256} + if g.prep_costs_pending { + g.prep_costs_pending = false + // The selection-scope-allocated caches are gone by now; walk with + // fresh ones. + g.prep_typ_text_cache = &PrepTypTextCache{} + g.preseed_type_seen = &PreseedTypeSeen{} + mut type_text_cache := map[string]bool{} + for i in 0 .. g.fn_gen_items.len { + item := g.fn_gen_items[i] + if item.file != g.tc.cur_file || item.module != g.tc.cur_module { + type_text_cache.clear() + if !isnil(g.prep_typ_text_cache) { + g.prep_typ_text_cache.generation++ + } + } + g.tc.cur_file = item.file + g.tc.cur_module = item.module + g.fn_gen_items[i].cost = g.fn_item_cost_and_prep(item.node_id, mut stack, mut + type_text_cache) + } + } + if g.prep_externs_pending { + g.prep_externs_pending = false + items := g.fn_gen_items + for item in items { + _ = g.fn_item_cost_and_c_extern_prep(item.node_id, mut stack) + } + } +} + +fn (mut g FlatGen) refine_fn_item_costs(no_parallel bool, reserve_worker bool) { + if no_parallel || g.fn_gen_items.len < min_flat_cgen_parallel_items { + g.finish_pending_item_prep_serial() + return + } + $if windows { + g.finish_pending_item_prep_serial() + return + } $else { + if isnil(g.a.worker_pool) || g.a.worker_pool.size() == 0 { + g.finish_pending_item_prep_serial() + return + } + available_jobs := g.a.worker_pool.size() + 1 - if reserve_worker { 1 } else { 0 } + n_jobs := flat_cgen_job_count(available_jobs, g.fn_gen_items.len) + fused := g.prep_costs_pending + mut prep_g := unsafe { nil } + if fused { + if g.prep_alias_short_names.len == 0 { + for name, _ in g.tc.type_aliases { + g.prep_alias_short_names[name.all_after_last('.')] = true + } + } + prep_g = voidptr(g) + } + mut args := []FlatCgenCostArgs{cap: n_jobs} + mut tasks := []workers.Task{cap: n_jobs} + mut boundaries := []int{len: n_jobs + 1, init: g.fn_gen_items.len} + boundaries[0] = 0 + if os.getenv('V3_NO_CGEN_COST_BALANCE') == '' { + mut total_cost := i64(g.fn_gen_items.len) + for item in g.fn_gen_items { + total_cost += i64(item.cost) + } + mut consumed_cost := i64(0) + mut pos := 0 + for job in 1 .. n_jobs { + target_cost := total_cost * i64(job) / i64(n_jobs) + max_pos := g.fn_gen_items.len - (n_jobs - job) + for pos < max_pos && (consumed_cost < target_cost || pos == boundaries[job - 1]) { + consumed_cost += i64(g.fn_gen_items[pos].cost) + 1 + pos++ + } + boundaries[job] = pos + } + } else { + for job in 1 .. n_jobs { + boundaries[job] = g.fn_gen_items.len * job / n_jobs + } + } + for job in 0 .. n_jobs { + args << FlatCgenCostArgs{ + a: unsafe { g.a } + items_ptr: unsafe { voidptr(&g.fn_gen_items) } + start: boundaries[job] + end: boundaries[job + 1] + g: prep_g + } + } + for job in 0 .. n_jobs { + tasks << workers.Task{ + run: flat_cgen_cost_thread + arg: unsafe { voidptr(&args[job]) } + force_sync: job == 0 + } + } + rfsw := time.new_stopwatch() + g.a.worker_pool.run(tasks) + g.timing_profile(' [ttime] cg refine pool ${f64(rfsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + for arg in args { + for name, used in arg.refs { + if used { + g.c_extern_refs[name] = true + } + } + } + if fused { + rpsw := time.new_stopwatch() + mut n_cands := 0 + for arg in args { + n_cands += arg.cands.len + } + g.replay_prep_candidates(args) + g.timing_profile(' [ttime] cg replay ${f64(rpsw.elapsed().microseconds()) / 1000.0:7.2f} ms (cands: ${n_cands})') + g.prep_costs_pending = false + } + g.prep_externs_pending = false + } +} + +// replay_prep_candidates applies the fn-ptr preseeds collected by the parallel +// prep workers, in source order, so registrations land exactly as the former +// serial walk produced them. +fn (mut g FlatGen) replay_prep_candidates(args []FlatCgenCostArgs) { + mut type_text_cache := map[string]bool{} + // Fresh local dedup cache: g.preseed_type_seen was allocated inside the + // (already freed) selection scope and must not be touched here. + mut replay_seen := &PreseedTypeSeen{} + for arg in args { + for cand in arg.cands { + item := g.fn_gen_items[cand.item_idx] + if item.file != g.tc.cur_file || item.module != g.tc.cur_module { + type_text_cache.clear() + g.tc.cur_file = item.file + g.tc.cur_module = item.module + } + if cand.is_expr { + w0, w1, slot := preseed_type_words(cand.typ) + if !replay_seen.seen[slot] || replay_seen.w0[slot] != w0 + || replay_seen.w1[slot] != w1 { + replay_seen.w0[slot] = w0 + replay_seen.w1[slot] = w1 + replay_seen.seen[slot] = true + g.preseed_parallel_fn_ptr_type(cand.typ) + } + } else { + if g.should_preseed_parallel_type_text_cached(cand.text, mut type_text_cache) { + g.preseed_parallel_fn_ptr_type(g.tc.parse_type(cand.text)) + } + } + } + } +} + +@[direct_array_access] +fn (mut g FlatGen) preintern_ast_string_literals() { + if g.ast_string_literals_ready { + for value in g.ast_string_literals { + g.intern_string(value) + } + return + } + for i in 0 .. g.a.nodes.len { + node := unsafe { &g.a.nodes[i] } + if node.kind == .string_literal { + g.intern_string(node.value) + } + } +} + +fn (mut g FlatGen) prepare_pre_dispatch_master() { + mut n_items := 0 + if g.scope_parallel_workers { + mut pmsw := time.new_stopwatch() + selection_scope := cgen_worker_scope_begin(true) + retain_selection := os.getenv('V3_NO_RETAIN_CGEN_PREP_SCOPE') == '' + master_tc := g.tc + g.tc = g.clone_parallel_type_checker() + g.tc.verbose = master_tc.verbose + g.timing_profile(' [ttime] pm clone tc ${f64(pmsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + pmsw.restart() + // Fuse body-local function-pointer discovery into the item cost walk so + // parallel type declarations see every typedef before their task starts. + // The globally numbered string table must also be complete before output. + g.preintern_ast_string_literals() + g.timing_profile(' [ttime] pm str walk ${f64(pmsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + pmsw.restart() + g.want_parallel_prep = true + items := g.ensure_fn_gen_items() + g.want_parallel_prep = false + g.timing_profile(' [ttime] pm items ${f64(pmsw.elapsed().microseconds()) / 1000.0:7.2f} ms (n: ${items.len})') + pmsw.restart() + if _ := g.ierror_interface_name() { + g.intern_string('') + } + g.register_interface_strings() + g.tc = master_tc + cgen_worker_scope_leave(selection_scope) + if retain_selection { + // The selected items and predispatch tables are immutable from here on. + // Keep their arena through final output so they can move straight into + // cgen instead of cloning every item, string table, and lookup map only + // to free the originals immediately afterward. + g.parallel_worker_scopes << selection_scope + g.timing_profile(' [ttime] pm retain out ${f64(pmsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + n_items = items.len + } else { + items_scope := cgen_worker_scope_begin(true) + mut owned_items := []FlatFnGenItem{cap: items.len} + for item in items { + owned_items << FlatFnGenItem{ + node_id: item.node_id + file: item.file + module: item.module + c_name: item.c_name.clone() + cost: item.cost + is_program_specialization: item.is_program_specialization + is_program: item.is_program + direct_array_access: item.direct_array_access + ignore_overflow: item.ignore_overflow + } + } + g.fn_gen_items = owned_items + g.emitted_fns = clone_cgen_string_bool_map(g.emitted_fns) + cgen_worker_scope_leave(items_scope) + g.scoped_fn_items_scope = items_scope + // These tables remain live after release_scoped_fn_items, so promote them + // into the enclosing cgen arena rather than the retained item arena. + g.str_lits = clone_cgen_string_list(g.str_lits) + g.str_lit_ids = clone_cgen_string_int_map(g.str_lit_ids) + g.fn_ptr_types = clone_cgen_string_map(g.fn_ptr_types) + g.used_fn_ptr_types = clone_cgen_string_bool_map(g.used_fn_ptr_types) + g.c_extern_refs = clone_cgen_string_bool_map(g.c_extern_refs) + g.c_name_cache = clone_c_name_cache(g.c_name_cache) + g.generic_app_cache = clone_generic_app_cache(g.generic_app_cache) + cgen_worker_scope_free(selection_scope) + n_items = g.fn_gen_items.len + g.timing_profile(' [ttime] pm clone out ${f64(pmsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + } + } else { + g.want_parallel_prep = true + n_items = g.ensure_fn_gen_items().len + g.want_parallel_prep = false + } + if n_items >= min_flat_cgen_parallel_items { + // The fused item walk already interned and pre-seeded; only the + // epilogue remains. + if !g.scope_parallel_workers { + if _ := g.ierror_interface_name() { + g.intern_string('') + } + g.register_interface_strings() + } + if g.test_files.len == 0 && !g.has_entry_main() { + for stmt in g.top_level_stmts() { + g.collect_c_extern_referenced_symbols_from_node(stmt.id, mut g.c_extern_refs) + } + } + g.parallel_prepared = true + } + // Force the lazily-built const short-name index now: workers share it + // read-only, so it must be complete before any fork starts. + _ = g.unique_const_ref_name('__v3_prewarm__') or { '' } +} + +fn clone_cgen_string_map(values map[string]string) map[string]string { + mut cloned := map[string]string{} + for key, value in values { + cloned[key.clone()] = value.clone() + } + return cloned +} + +fn clone_cgen_string_bool_map(values map[string]bool) map[string]bool { + mut cloned := map[string]bool{} + for key, value in values { + cloned[key.clone()] = value + } + return cloned +} + +fn clone_cgen_string_int_map(values map[string]int) map[string]int { + mut cloned := map[string]int{} + for key, value in values { + cloned[key.clone()] = value + } + return cloned +} + +fn clone_c_name_cache(source &CNameCache) &CNameCache { + mut entries := map[string]string{} + if !isnil(source) { + for key, value in source.entries { + entries[key.clone()] = value.clone() + } + } + return &CNameCache{ + entries: entries + } +} + +fn clone_generic_app_cache(source &GenericAppCache) &GenericAppCache { + mut entries := map[string]GenericAppInfo{} + if !isnil(source) { + for key, value in source.entries { + entries[key.clone()] = GenericAppInfo{ + base: value.base.clone() + args: value.args.clone() + ok: value.ok + } + } + } + return &GenericAppCache{ + entries: entries + } +} + +// write_scoped_cgen_batch_output writes a batch builder while its disposable +// scope is still active, avoiding a second output copy in the parent arena. +fn (mut g FlatGen) write_scoped_cgen_batch_output(batch &FlatGen) bool { + mut file := os.open_append(g.scoped_fn_output_path) or { + g.output_error = err.msg() + return false + } + if batch.cache_split { + mut b := unsafe { batch } + source := b.sb.str() + stable_source := b.rewrite_cache_string_symbols(source) + file.write_string(stable_source) or { + g.output_error = err.msg() + file.close() + unsafe { + source.free() + stable_source.free() + } + return false + } + unsafe { + source.free() + stable_source.free() + } + } else { + unsafe { + file.write_full_buffer(batch.sb.data, usize(batch.sb.len)) or { + g.output_error = err.msg() + file.close() + return false + } + } + } + file.close() + return true +} + +// absorb_scoped_cgen_batch copies a finished batch's observable side tables +// and, when needed, output into the helper's result arena. +fn (mut g FlatGen) absorb_scoped_cgen_batch(batch &FlatGen, output_streamed bool) { + mut b := unsafe { batch } + if !output_streamed { + output := b.sb.str() + if output.len > 0 { + g.fn_segs << output + } else { + unsafe { output.free() } + } + } + unsafe { b.sb.free() } + // Preserve worker-only literals at the IDs already written into batch output. + for literal in batch.str_lits[g.str_lits.len..] { + g.intern_string(literal.clone()) + } + for opt_name, val_type in batch.needed_optional_types { + if opt_name !in g.needed_optional_types { + g.needed_optional_types[opt_name.clone()] = val_type.clone() + } + } + for encoded, name in batch.fn_ptr_types { + if encoded !in g.fn_ptr_types { + g.fn_ptr_types[encoded.clone()] = name.clone() + } + } + for encoded, used in batch.used_fn_ptr_types { + if used { + g.used_fn_ptr_types[encoded.clone()] = true + } + } + for name, used in batch.c_extern_refs { + if used { + g.c_extern_refs[name.clone()] = true + } + } + for name, enabled in batch.libc_compat_fns { + if enabled { + g.libc_compat_fns[name.clone()] = true + } + } + for key, name in batch.spawn_wrapper_names { + if key !in g.spawn_wrapper_names { + g.spawn_wrapper_names[key.clone()] = name.clone() + } + } + for def in batch.spawn_wrapper_defs { + if batch.cache_split { + stable_def := b.rewrite_cache_string_symbols(def) + g.add_spawn_wrapper_def(stable_def) + } else { + g.add_spawn_wrapper_def(def.clone()) + } + } + for key, name in batch.callback_wrapper_names { + if key !in g.callback_wrapper_names { + g.callback_wrapper_names[key.clone()] = name.clone() + } + } + for def in batch.callback_wrapper_defs { + if batch.cache_split { + stable_def := b.rewrite_cache_string_symbols(def) + g.add_callback_wrapper_def(stable_def) + } else { + g.add_callback_wrapper_def(def.clone()) + } + } + for wrappers in batch.parallel_chunk_wrapper_defs { + g.parallel_chunk_wrapper_defs << ParallelChunkWrapperDefs{ + chunk_idx: wrappers.chunk_idx + spawn: clone_cgen_string_list(wrappers.spawn) + callback: clone_cgen_string_list(wrappers.callback) + } + } +} + +// gen_fn_items_scoped_batches bounds helper scratch without adding worker-pool +// barriers. Each batch gets fresh mutable generator/checker caches while its C +// output is accumulated in a much smaller result arena. +fn (mut g FlatGen) gen_fn_items_scoped_batches(items []FlatFnGenItem) { + result_scope := cgen_worker_scope_begin(true) + mut total_cost := i64(items.len) + for item in items { + total_cost += item.cost + } + n_batches := if items.len < scoped_cgen_worker_batches { + items.len + } else { + scoped_cgen_worker_batches + } + mut start := 0 + mut consumed_cost := i64(0) + for batch_idx in 0 .. n_batches { + mut end := start + target_cost := total_cost * i64(batch_idx + 1) / i64(n_batches) + for end < items.len + && (batch_idx == n_batches - 1 || consumed_cost < target_cost || end == start) { + consumed_cost += i64(items[end].cost) + 1 + end++ + } + scratch_scope := cgen_worker_scope_begin(true) + mut batch := g.new_parallel_worker(batch_idx) + // Weighted AST cost tracks generated body bytes closely enough to avoid + // the 64 KiB builder growing and copying five or six times per worker. + batch.sb = strings.new_builder(int(total_cost * 5) + 65_536) + batch.gen_fn_items(items[start..end]) + cgen_worker_scope_leave(scratch_scope) + g.absorb_scoped_cgen_batch(batch, false) + cgen_worker_scope_free(scratch_scope) + start = end + } + g.worker_scope = result_scope + cgen_worker_scope_leave(result_scope) +} + +fn (mut g FlatGen) gen_fn_chunks_scoped_dynamic( + chunks [][]FlatFnGenItem, + chunk_queue chan int, + _reserve_cost i64) { + wsw := time.new_stopwatch() + mut n_chunks := 0 + result_scope := cgen_worker_scope_begin(true) + // Chunks assigned to one dispatcher run sequentially. Keep the dense + // generation-tagged expression-type memo in the result arena so each scratch + // chunk does not allocate and zero another 192 KiB table. + reuse_expr_type_memo := os.getenv('V3_NO_REUSE_CGEN_EXPR_TYPE_MEMO') == '' + if reuse_expr_type_memo { + g.begin_usable_expr_type_memo() + g.end_usable_expr_type_memo() + } + for { + chunk_idx := <-chunk_queue or { break } + n_chunks++ + mut chunk_cost := i64(chunks[chunk_idx].len) + for item in chunks[chunk_idx] { + chunk_cost += item.cost + } + scratch_scope := cgen_worker_scope_begin(true) + mut batch := g.new_parallel_worker(chunk_idx) + if reuse_expr_type_memo { + batch.usable_expr_type_memo = g.usable_expr_type_memo + } + batch.sb = strings.new_builder(int(chunk_cost * 5) + 65_536) + batch.parallel_chunk_wrapper_defs << ParallelChunkWrapperDefs{ + chunk_idx: chunk_idx + } + batch.parallel_chunk_wrapper_capture = batch.parallel_chunk_wrapper_defs.len - 1 + batch.gen_fn_items(chunks[chunk_idx]) + batch.parallel_chunk_wrapper_capture = -1 + cgen_worker_scope_leave(scratch_scope) + segment_start := g.fn_segs.len + g.absorb_scoped_cgen_batch(batch, false) + if g.fn_segs.len > segment_start { + g.fn_seg_chunk_indexes << chunk_idx + } + cgen_worker_scope_free(scratch_scope) + } + g.timing_profile(' [ttime] cg wkr busy ${f64(wsw.elapsed().microseconds()) / 1000.0:7.2f} ms (chunks: ${n_chunks})') + g.worker_scope = result_scope + cgen_worker_scope_leave(result_scope) +} + +// gen_fn_items_scoped_master_batches publishes each caller-thread batch +// directly into the already-scoped master generator, so its temporary caches +// do not remain resident for the rest of cgen. +fn (mut g FlatGen) gen_fn_items_scoped_master_batches(items []FlatFnGenItem) { + mut total_cost := i64(items.len) + for item in items { + total_cost += item.cost + } + n_batches := if items.len < scoped_cgen_worker_batches { + items.len + } else { + scoped_cgen_worker_batches + } + mut start := 0 + mut consumed_cost := i64(0) + for batch_idx in 0 .. n_batches { + mut end := start + target_cost := total_cost * i64(batch_idx + 1) / i64(n_batches) + for end < items.len + && (batch_idx == n_batches - 1 || consumed_cost < target_cost || end == start) { + consumed_cost += i64(items[end].cost) + 1 + end++ + } + scratch_scope := cgen_worker_scope_begin(true) + mut batch := g.new_parallel_worker(batch_idx) + batch.gen_fn_items(items[start..end]) + cgen_worker_scope_leave(scratch_scope) + g.absorb_scoped_cgen_batch(batch, false) + cgen_worker_scope_free(scratch_scope) + start = end + } +} + +fn clone_embedded_fields_by_type(values map[string][]types.StructField) map[string][]types.StructField { + mut cloned := map[string][]types.StructField{} + for name, fields in values { + mut owned_fields := []types.StructField{cap: fields.len} + for field in fields { + owned_fields << types.StructField{ + name: field.name.clone() + typ: types.clone_owned_type(field.typ) + has_default: field.has_default + is_embed: field.is_embed + is_mut: field.is_mut + } + } + cloned[name.clone()] = owned_fields + } + return cloned +} + +fn (mut g FlatGen) publish_fixed_storage_scan(mut fs_worker FlatGen) { + for opt_name, val_type in fs_worker.needed_optional_types { + g.needed_optional_types[opt_name.clone()] = val_type.clone() + } + g.fixed_storage_consts = fs_worker.fixed_storage_consts.move() + g.param_types_by_short = fs_worker.param_types_by_short.move() + g.concrete_optional_abi_fns = fs_worker.concrete_optional_abi_fns.move() + if fs_worker.worker_scope != unsafe { nil } { + // These tables stay live through function emission. Retaining the small + // helper arena is cheaper than deep-cloning their type/string payloads and + // matches the optional/fixed-array support publishers below. + g.parallel_worker_scopes << fs_worker.worker_scope + fs_worker.worker_scope = unsafe { nil } + } +} + +fn (mut g FlatGen) publish_fixed_array_support(mut worker FlatGen) { + g.fixed_array_typedefs_needed = worker.fixed_array_typedefs_needed.move() + g.fixed_array_typedefs_ready = worker.fixed_array_typedefs_ready + if worker.worker_scope != unsafe { nil } { + g.parallel_worker_scopes << worker.worker_scope + worker.worker_scope = unsafe { nil } + } +} + +fn (mut g FlatGen) publish_optional_support(mut worker FlatGen) { + g.needed_optional_types = worker.needed_optional_types.move() + g.optional_types_ready = worker.optional_types_ready + g.multi_return_types = worker.multi_return_types + g.multi_return_type_names = worker.multi_return_type_names.move() + g.multi_return_types_ready = worker.multi_return_types_ready + g.decl_types_ready = worker.decl_types_ready + if worker.worker_scope != unsafe { nil } { + g.parallel_worker_scopes << worker.worker_scope + worker.worker_scope = unsafe { nil } + } +} + +fn (mut g FlatGen) publish_interface_impl_scan(mut worker FlatGen) { + g.interface_boxed_types = worker.interface_boxed_types.move() + g.interface_boxed_types_done = worker.interface_boxed_types_done + g.iface_impls = worker.iface_impls.move() + g.iface_type_ids = worker.iface_type_ids.move() + g.ierror_method_emit_names = worker.ierror_method_emit_names.move() + if worker.worker_scope != unsafe { nil } { + g.parallel_worker_scopes << worker.worker_scope + worker.worker_scope = unsafe { nil } + } +} + +// gen_fns_dispatch emits fns dispatch output for c. +fn (mut g FlatGen) gen_fns_dispatch(no_parallel bool) { + g.gen_test_failure_global() + if no_parallel { + if g.scope_parallel_workers { + items := g.ensure_fn_gen_items() + g.reset_context_lookup_caches() + if items.len < min_flat_cgen_parallel_items { + g.gen_fn_items(items) + } else { + g.gen_fn_items_scoped_master_batches(items) + } + } else { + g.gen_fns() + } + g.gen_synthetic_main_after_fns() + return + } + items := g.ensure_fn_gen_items() + g.reset_context_lookup_caches() + n_items := items.len + $if windows { + g.gen_fn_items(items) + g.gen_synthetic_main_after_fns() + return + } $else { + if isnil(g.a.worker_pool) { + g.a.worker_pool = workers.new(runtime.nr_jobs() - 1) + } + available_jobs := g.a.worker_pool.size() + 1 + // Type declarations use one pool task. Once it finishes, that same worker + // can drain a queued body task instead of staying reserved for the whole + // function-generation phase. + parallel_type_decls := available_jobs > 2 && g.scope_parallel_workers + && !g.program_body_only && g.incremental_fn_names.len == 0 + n_jobs := flat_cgen_job_count(available_jobs, n_items) + if n_items < min_flat_cgen_parallel_items || n_jobs <= 1 { + if g.scope_parallel_workers { + if n_items < min_flat_cgen_parallel_items { + g.gen_fn_items(items) + } else { + g.gen_fn_items_scoped_master_batches(items) + } + } else { + g.gen_fn_items(items) + } + g.gen_synthetic_main_after_fns() + return + } + // Freeze the checker's warm type cache (fully populated by the check and + // transform phases) as the shared read-only base for every worker's + // fresh cache; the master's own memoization writes go to a private + // overlay for the duration of the region. + mut stsw := time.new_stopwatch() + g.tc.freeze_type_cache_for_forks() + g.freeze_parallel_lookup_caches() + if !g.parallel_prepared { + g.prepare_parallel_items(items) + } + chunk_jobs := if parallel_type_decls { + n_jobs * flat_cgen_chunks_per_job + } else { + n_jobs + } + mut chunk_items := split_flat_cgen_items(items, chunk_jobs) + chunk_count := chunk_items.len + g.timing_profile(' [ttime] cg freeze+split ${f64(stsw.elapsed().microseconds()) / 1000.0:7.2f} ms') + stsw.restart() + if parallel_type_decls { + fail := os.getenv('V3_TEST_PTHREAD_CREATE_FAIL') + static_dispatch := fail.len > 0 + lazy_worker_setup := !static_dispatch && os.getenv('V3_NO_PAR_CGEN_WORKER_SETUP') == '' + worker_count := if static_dispatch { chunk_count } else { n_jobs } + mut cgen_workers := []voidptr{len: worker_count, init: unsafe { nil }} + mut worker_setup_scopes := []voidptr{len: worker_count, init: unsafe { nil }} + mut ordered_chunk_outputs := []string{} + mut ordered_wrapper_defs := []ParallelChunkWrapperDefs{} + mut worker_setup_scope := unsafe { nil } + if !lazy_worker_setup { + worker_setup_scope = cgen_worker_scope_begin(true) + for ci := 0; ci < worker_count; ci++ { + cgen_workers[ci] = voidptr(g.new_parallel_dispatch_worker(ci)) + } + cgen_worker_scope_leave(worker_setup_scope) + } + g.timing_profile(' [ttime] cg wkr setup ${f64(stsw.elapsed().microseconds()) / 1000.0:7.2f} ms (workers: ${worker_count})') + if static_dispatch { + mut args := []FlatCgenChunkArgs{cap: chunk_count} + mut tasks := []workers.Task{cap: chunk_count + 1} + for ci in 0 .. chunk_count { + args << FlatCgenChunkArgs{ + worker: cgen_workers[ci] + work_items_ptr: unsafe { voidptr(&chunk_items[ci]) } + } + tasks << workers.Task{ + run: flat_cgen_chunk_thread + arg: unsafe { voidptr(&args[ci]) } + force_sync: fail == 'cgen:all' || fail == 'cgen:body:all' + || fail == 'cgen:body:${ci}' + } + } + tasks << workers.Task{ + run: parallel_type_decls_thread + arg: voidptr(g) + force_sync: true + } + g.parallel_used = g.a.worker_pool.run(tasks) + } else { + // Long-lived workers pull small source-contiguous chunks from a shared + // queue. This balances expression-cost and scheduler variation without + // rebuilding the generator caches for every chunk. + mut dsw := time.new_stopwatch() + ordered_chunk_outputs = []string{len: chunk_count} + ordered_wrapper_defs = []ParallelChunkWrapperDefs{len: chunk_count} + chunk_queue := chan int{cap: chunk_count} + for ci in 0 .. chunk_count { + chunk_queue <- ci + } + chunk_queue.close() + mut total_cost := i64(items.len) + for item in items { + total_cost += item.cost + } + reserve_cost := total_cost / i64(worker_count) + 1 + mut args := []FlatCgenDynamicArgs{cap: worker_count} + mut tasks := []workers.Task{cap: worker_count + 1} + tasks << workers.Task{ + run: parallel_type_decls_thread + arg: voidptr(g) + } + for ci in 0 .. worker_count { + args << FlatCgenDynamicArgs{ + dispatcher: voidptr(g) + worker_id: ci + worker: cgen_workers[ci] + work_chunks_ptr: unsafe { voidptr(&chunk_items) } + chunk_queue: chunk_queue + reserve_cost: reserve_cost + } + tasks << workers.Task{ + run: flat_cgen_dynamic_thread + arg: unsafe { voidptr(&args[ci]) } + force_sync: ci == 0 + } + } + g.parallel_used = g.a.worker_pool.run(tasks) + if lazy_worker_setup { + for ci in 0 .. worker_count { + cgen_workers[ci] = args[ci].worker + worker_setup_scopes[ci] = args[ci].setup_scope + } + } + g.timing_profile(' [ttime] cg pool.run ${f64(dsw.elapsed().microseconds()) / 1000.0:7.2f} ms (chunks: ${chunk_count}, workers: ${worker_count})') + } + // The declaration thread disables the master's caches while body + // workers use their private copies. Restore them for synthetic output. + mut msw := time.new_stopwatch() + g.reset_context_lookup_caches() + for ci, worker_ptr in cgen_workers { + mut w := unsafe { &FlatGen(worker_ptr) } + if ordered_chunk_outputs.len > 0 { + g.merge_parallel_worker_ordered(w, mut ordered_chunk_outputs, mut + ordered_wrapper_defs) + } else { + g.merge_parallel_worker(w) + } + g.finish_parallel_worker_scope(mut w) + if worker_setup_scopes[ci] != unsafe { nil } { + cgen_worker_scope_free(worker_setup_scopes[ci]) + } + } + g.replay_ordered_parallel_wrapper_defs(ordered_wrapper_defs) + g.timing_profile(' [ttime] cg merge ${f64(msw.elapsed().microseconds()) / 1000.0:7.2f} ms') + for output in ordered_chunk_outputs { + if output.len > 0 { + g.fn_segs << output + } + } + if worker_setup_scope != unsafe { nil } { + cgen_worker_scope_free(worker_setup_scope) + } + // Cgen's cache is reset by the driver after this stage. Discard its + // overlay so worker-arena memo values cannot escape into the base. + g.tc.discard_type_cache_overlay_after_forks() + g.gen_synthetic_main_after_fns() + synthetic_output := g.sb.str() + unsafe { g.sb.free() } + g.sb = strings.new_builder(0) + if synthetic_output.len > 0 { + g.fn_segs << synthetic_output + } else { + unsafe { synthetic_output.free() } + } + return + } + // chunk[0] is emitted by the master directly into its own builder; the + // other chunks get helper threads. Function-local temporary names are reset + // for each item, so their spelling does not depend on chunk assignment. + thread_count := chunk_count - 1 + mut args := []FlatCgenChunkArgs{cap: chunk_count} + args << FlatCgenChunkArgs{ + worker: voidptr(g) + work_items_ptr: unsafe { voidptr(&chunk_items[0]) } + is_master: true + } + // Keep helper output in ordered result segments until the join so generated + // string IDs can be reconciled with literals emitted by the master chunk. + mut cgen_workers := []voidptr{cap: thread_count} + worker_setup_scope := cgen_worker_scope_begin(g.scope_parallel_workers) + for ci := 0; ci < thread_count; ci++ { + mut w := g.new_parallel_dispatch_worker(ci + 1) + cgen_workers << voidptr(w) + } + for ci := 0; ci < thread_count; ci++ { + args << FlatCgenChunkArgs{ + worker: cgen_workers[ci] + work_items_ptr: unsafe { voidptr(&chunk_items[ci + 1]) } + } + } + cgen_worker_scope_leave(worker_setup_scope) + fail := os.getenv('V3_TEST_PTHREAD_CREATE_FAIL') + mut tasks := []workers.Task{cap: chunk_count} + for ci in 0 .. chunk_count { + helper_idx := ci - 1 + tasks << workers.Task{ + run: flat_cgen_chunk_thread + arg: unsafe { voidptr(&args[ci]) } + force_sync: ci == 0 || fail == 'cgen:all' || fail == 'cgen:body:all' + || fail == 'cgen:body:${helper_idx}' + } + } + g.parallel_used = g.a.worker_pool.run(tasks) + master_output := g.sb.str() + unsafe { g.sb.free() } + g.sb = strings.new_builder(4096) + if master_output.len > 0 { + g.fn_segs << master_output + } else { + unsafe { master_output.free() } + } + for ci := 0; ci < thread_count; ci++ { + mut w := unsafe { &FlatGen(cgen_workers[ci]) } + g.merge_parallel_worker(w) + g.finish_parallel_worker_scope(mut w) + } + cgen_worker_scope_free(worker_setup_scope) + // Cgen's cache is reset by the driver after this stage. Discard its + // overlay so worker-arena memo values cannot escape into the base. + g.tc.discard_type_cache_overlay_after_forks() + // Synthetic main temps continue after the master's chunk[0] range. + g.gen_synthetic_main_after_fns() + synthetic_output := g.sb.str() + unsafe { g.sb.free() } + g.sb = strings.new_builder(0) + if synthetic_output.len > 0 { + g.fn_segs << synthetic_output + } else { + unsafe { synthetic_output.free() } + } + } +} + +// prepare_serial_fn_tables gives runtime `-no-parallel` generation the same +// deterministic preseed order as the parallel dispatcher, before constants +// can allocate string-literal IDs. +fn (mut g FlatGen) prepare_serial_fn_tables() { + if g.parallel_prepared { + return + } + g.want_parallel_prep = true + items := g.ensure_fn_gen_items() + g.want_parallel_prep = false + if items.len >= min_flat_cgen_parallel_items { + if _ := g.ierror_interface_name() { + g.intern_string('') + } + g.register_interface_strings() + g.parallel_prepared = true + } +} + +// freeze_parallel_lookup_caches keeps the warm pre-dispatch caches as immutable +// bases while the master and every body worker memoize into private overlays. +fn (mut g FlatGen) freeze_parallel_lookup_caches() { + shared_c_name_cache := g.c_name_cache + g.c_name_cache = &CNameCache{ + base: shared_c_name_cache + } + shared_generic_app_cache := g.generic_app_cache + g.generic_app_cache = &GenericAppCache{ + base: shared_generic_app_cache + } +} + +// flat_cgen_job_count supports flat cgen job count handling for c. +fn flat_cgen_job_count(n_runtime_jobs int, n_items int) int { + if n_runtime_jobs <= 0 || n_items <= 0 { + return 0 + } + mut n_jobs := n_runtime_jobs + if n_jobs > max_flat_cgen_jobs { + n_jobs = max_flat_cgen_jobs + } + if n_jobs > n_items { + n_jobs = n_items + } + return n_jobs +} + +// split_flat_cgen_items supports split flat cgen items handling for c. +fn split_flat_cgen_items(items []FlatFnGenItem, n_jobs int) [][]FlatFnGenItem { + if n_jobs <= 0 || items.len == 0 { + return [][]FlatFnGenItem{} + } + mut chunks := [][]FlatFnGenItem{} + mut total_cost := 0 + for item in items { + total_cost += item.cost + } + mut current := []FlatFnGenItem{} + mut consumed_cost := 0 + mut chunk_idx := 0 + mut chunks_left := n_jobs + for idx, item in items { + remaining_items := items.len - idx + next_target := total_cost * (chunk_idx + 1) / n_jobs + if current.len > 0 && consumed_cost >= next_target && chunks_left > 1 + && remaining_items >= chunks_left { + chunks << current + current = []FlatFnGenItem{} + chunk_idx++ + chunks_left-- + } + current << item + consumed_cost += item.cost + } + if current.len > 0 { + chunks << current + } + return chunks +} + +// stripe_flat_cgen_items mixes several narrow, cost-balanced source ranges +// into each worker. Cgen work varies by expression kind as well as AST size; +// striping prevents one worker from inheriting an entire expensive name range. +fn stripe_flat_cgen_items(chunks [][]FlatFnGenItem, n_jobs int) [][]FlatFnGenItem { + if n_jobs <= 0 || chunks.len == 0 { + return [][]FlatFnGenItem{} + } + mut striped := [][]FlatFnGenItem{len: n_jobs} + for idx, chunk in chunks { + striped[idx % n_jobs] << chunk + } + return striped +} + +// balance_flat_cgen_chunks assigns narrow, contiguous name ranges by cost, +// then restores source order within each worker. It keeps type/name caches hot +// while avoiding the modulo alignment sensitivity of simple striping. +fn balance_flat_cgen_chunks(chunks [][]FlatFnGenItem, n_jobs int) [][]FlatFnGenItem { + if n_jobs <= 0 || chunks.len == 0 { + return [][]FlatFnGenItem{} + } + mut chunk_costs := []i64{len: chunks.len} + mut assigned := []bool{len: chunks.len} + for idx, chunk in chunks { + for item in chunk { + chunk_costs[idx] += i64(item.cost) + 1 + } + } + mut worker_costs := []i64{len: n_jobs} + mut worker_chunks := [][]int{len: n_jobs} + for _ in chunks { + mut largest := -1 + for idx, cost in chunk_costs { + if !assigned[idx] && (largest < 0 || cost > chunk_costs[largest]) { + largest = idx + } + } + mut least_worker := 0 + for job in 1 .. n_jobs { + if worker_costs[job] < worker_costs[least_worker] { + least_worker = job + } + } + assigned[largest] = true + worker_chunks[least_worker] << largest + worker_costs[least_worker] += chunk_costs[largest] + } + mut balanced := [][]FlatFnGenItem{len: n_jobs} + for job, mut chunk_ids in worker_chunks { + chunk_ids.sort(a < b) + for chunk_id in chunk_ids { + balanced[job] << chunks[chunk_id] + } + } + return balanced +} + +// fn_item_cost_and_prep computes the split cost, collects C-extern refs, and +// pre-seeds function-pointer types in one subtree traversal. +fn (mut g FlatGen) fn_item_cost_and_prep(node_id flat.NodeId, mut stack []flat.NodeId, mut type_text_cache map[string]bool) int { + // Direct users of this helper (including small standalone generators) may + // not have run collect_gen_info's fused literal collection. + if !g.ast_string_literals_ready { + g.preintern_ast_string_literals() + } + mut cost := 0 + stack.clear() + stack << node_id + for stack.len > 0 { + current_id := stack.pop() + idx := int(current_id) + if idx < 0 || idx >= g.a.nodes.len { + continue + } + node := unsafe { &g.a.nodes[idx] } + cost++ + // String literals were already interned by the whole-AST literal walk in + // prepare_pre_dispatch_master, and C-extern refs are re-collected by the + // parallel exact-cost pass that always follows this prep (with a serial + // fallback, see refine_fn_item_costs). Keep this walk preseed-only. + if node.typ.len > 0 + && g.should_preseed_parallel_type_text_ptr_cached(node.typ, mut type_text_cache) { + g.preseed_parallel_fn_ptr_type(g.parse_node_type(node)) + } + if expr_type := g.parallel_cached_expr_type(current_id, node) { + // Checker-cached expression types repeat the same ~1K canonical + // values across hundreds of thousands of nodes; traverse each + // distinct value once instead of per node. + if g.preseed_type_first_seen(expr_type) { + g.preseed_parallel_fn_ptr_type(expr_type) + } + } + for i := node.children_count - 1; i >= 0; i-- { + child_id := g.a.children[node.children_start + i] + if int(child_id) >= 0 { + stack << child_id + } + } + } + return cost +} + +fn (mut g FlatGen) fn_item_cost_and_c_extern_prep(node_id flat.NodeId, mut stack []flat.NodeId) int { + mut cost := 0 + stack.clear() + stack << node_id + for stack.len > 0 { + current_id := stack.pop() + idx := int(current_id) + if idx < 0 || idx >= g.a.nodes.len { + continue + } + node := unsafe { &g.a.nodes[idx] } + cost++ + if node.kind == .selector { + g.collect_c_extern_ref_from_node(node) + } + for i := node.children_count - 1; i >= 0; i-- { + child_id := g.a.children[node.children_start + i] + if int(child_id) >= 0 { + stack << child_id + } + } + } + return cost +} + +// prepare_parallel_items supports prepare parallel items handling for FlatGen. +fn (mut g FlatGen) prepare_parallel_items(items []FlatFnGenItem) { + mut stack := []flat.NodeId{cap: 256} + // Function bodies can materialize literals from declaration metadata (for + // example struct-field defaults) that lives outside every function subtree. + // Intern all source literals before workers fork so their numeric IDs remain + // valid regardless of which chunk first references that metadata. + for node in g.a.nodes { + if node.kind == .string_literal { + g.intern_string(node.value) + } + } + // The cache is keyed by the bare type text and reset whenever the + // file/module context changes (items are grouped by file, so resets are + // rare); the old composite '${file}\n${module}\n${typ}' key allocated a + // string per visited node. + mut type_text_cache := map[string]bool{} + for item in items { + if item.file != g.tc.cur_file || item.module != g.tc.cur_module { + type_text_cache.clear() + } + g.tc.cur_file = item.file + g.tc.cur_module = item.module + g.prepare_parallel_node(item.node_id, mut stack, mut type_text_cache) + } + if _ := g.ierror_interface_name() { + g.intern_string('') + } + g.register_interface_strings() +} + +// prepare_parallel_node supports prepare parallel node handling for FlatGen. +fn (mut g FlatGen) prepare_parallel_node(id flat.NodeId, mut stack []flat.NodeId, mut type_text_cache map[string]bool) { + stack.clear() + stack << id + for stack.len > 0 { + current_id := stack.pop() + idx := int(current_id) + if idx < 0 || idx >= g.a.nodes.len { + continue + } + node := unsafe { &g.a.nodes[idx] } + if node.kind == .string_literal { + g.intern_string(node.value) + } + g.collect_c_extern_ref_from_node(node) + if node.typ.len > 0 + && g.should_preseed_parallel_type_text_cached(node.typ, mut type_text_cache) { + g.preseed_parallel_fn_ptr_type(g.parse_node_type(node)) + } + if expr_type := g.parallel_cached_expr_type(current_id, node) { + g.preseed_parallel_fn_ptr_type(expr_type) + } + for i := node.children_count - 1; i >= 0; i-- { + child_id := g.a.children[node.children_start + i] + if int(child_id) >= 0 { + stack << child_id + } + } + } +} + +fn (g &FlatGen) parallel_cached_expr_type(id flat.NodeId, node &flat.Node) ?types.Type { + idx := int(id) + if idx < 0 { + return none + } + if g.tc.parallel_check_sparse && (idx < g.tc.check_range_lo || idx > g.tc.check_range_hi) { + if t := g.tc.sparse_expr_type_values[idx] { + return t + } + if node.kind == .call { + if name := g.tc.sparse_resolved_call_names[idx] { + if t := g.tc.fn_ret_types[name] { + return t + } + } + } + return none + } + if idx < g.tc.expr_type_set.len && idx < g.tc.expr_type_values.len && g.tc.expr_type_set[idx] { + return g.tc.expr_type_values[idx] + } + if node.kind == .call && idx < g.tc.resolved_call_set.len && idx < g.tc.resolved_call_names.len + && g.tc.resolved_call_set[idx] { + name := g.tc.resolved_call_names[idx] + if t := g.tc.fn_ret_types[name] { + return t + } + } + return none +} + +// FlatCgenPrepCandidate is one deferred fn-ptr preseed action discovered by a +// parallel prep worker, replayed by the master in source order so the typedef +// registration order matches the former serial walk exactly. +struct FlatCgenPrepCandidate { + is_expr bool + text string // node.typ text (is_expr == false) + typ types.Type // checker-cached expr type (is_expr == true) + item_idx int +} + +// exact_flat_fn_gen_item_cost_and_prep is exact_flat_fn_gen_item_cost plus the +// candidate collection of the former serial fused prep walk: distinct type +// texts and distinct cached expression types, in encounter order. All FlatGen +// and checker access is read-only (nothing writes the dense expr caches during +// cgen; every remember_expr_type caller is a mut check-phase path). +@[direct_array_access] +fn exact_flat_fn_gen_item_cost_and_prep(g &FlatGen, node_id flat.NodeId, item_idx int, mut c_extern_refs map[string]bool, mut stack []flat.NodeId, mut cands []FlatCgenPrepCandidate, mut text_cache PrepTypTextCache, mut type_seen PreseedTypeSeen) (int, bool) { + a := g.a + mut cost := 0 + mut needs_prelude_scan := false + stack.clear() + stack << node_id + for stack.len > 0 { + id := stack.pop() + idx := int(id) + if idx < 0 || idx >= a.nodes.len { + continue + } + node := unsafe { &a.nodes[idx] } + cost += flat_cgen_node_cost(node.kind) + if node.kind == .lock_expr || node.kind == .label_stmt + || (node.kind == .defer_stmt && node.value == 'function') { + needs_prelude_scan = true + } + if node.kind == .selector && node.children_count > 0 && node.value.len > 0 { + base_id := a.children[node.children_start] + if int(base_id) >= 0 { + base := unsafe { &a.nodes[int(base_id)] } + if base.kind == .ident && base.value == 'C' { + raw_name := 'C.${node.value}' + raw_cfn := naming.c_name(raw_name) + c_extern_refs[raw_name] = true + c_extern_refs[raw_cfn] = true + c_extern_refs[c_winapi_wide_export_name(raw_cfn)] = true + } + } + } + if parallel_type_text_may_preseed(g, node.typ) { + slot := int((u64(voidptr(node.typ.str)) >> 4) & 4095) + if text_cache.gens[slot] != text_cache.generation + || text_cache.ptrs[slot] != voidptr(node.typ.str) + || text_cache.lens[slot] != node.typ.len { + text_cache.ptrs[slot] = voidptr(node.typ.str) + text_cache.gens[slot] = text_cache.generation + text_cache.lens[slot] = node.typ.len + cands << FlatCgenPrepCandidate{ + text: node.typ + item_idx: item_idx + } + } + } + if expr_type := g.parallel_cached_expr_type(id, node) { + w0, w1, slot := preseed_type_words(expr_type) + if !type_seen.seen[slot] || type_seen.w0[slot] != w0 || type_seen.w1[slot] != w1 { + type_seen.w0[slot] = w0 + type_seen.w1[slot] = w1 + type_seen.seen[slot] = true + cands << FlatCgenPrepCandidate{ + is_expr: true + typ: expr_type + item_idx: item_idx + } + } + } + for i := node.children_count - 1; i >= 0; i-- { + child_id := a.children[node.children_start + i] + if int(child_id) >= 0 { + stack << child_id + } + } + } + return cost, needs_prelude_scan +} + +// parallel_type_text_may_preseed cheaply rejects builtin/container type text +// before the exact-cost workers retain it for the ordered alias/fn-type replay. +// V alias declarations are capitalized; literal callback types are the only +// lowercase text that can require a function-pointer preseed. +fn parallel_type_text_may_preseed(g &FlatGen, typ string) bool { + if typ.len == 0 { + return false + } + mut start := 0 + for start < typ.len { + for start < typ.len && typ[start] in [` `, `\t`, `\n`, `\r`] { + start++ + } + if start + 7 <= typ.len && typ[start] == `s` && typ[start + 1] == `h` + && typ[start + 2] == `a` && typ[start + 3] == `r` && typ[start + 4] == `e` + && typ[start + 5] == `d` && typ[start + 6] == ` ` { + start += 7 + continue + } + if start + 3 <= typ.len && typ[start] == `.` && typ[start + 1] == `.` + && typ[start + 2] == `.` { + start += 3 + continue + } + if start + 2 <= typ.len && typ[start] == `[` && typ[start + 1] == `]` { + start += 2 + continue + } + if start < typ.len && typ[start] in [`&`, `?`, `!`] { + start++ + continue + } + break + } + if start >= typ.len { + return false + } + if start + 1 < typ.len && typ[start] == `f` && typ[start + 1] == `n` { + return true + } + mut name_start := start + for i := start; i < typ.len; i++ { + if typ[i] == `.` { + name_start = i + 1 + } + } + if name_start >= typ.len || !typ[name_start].is_capital() { + return false + } + mut end := typ.len + for end > name_start && typ[end - 1] in [` `, `\t`, `\n`, `\r`] { + end-- + } + if end <= name_start { + return false + } + short := unsafe { tos(typ.str + name_start, end - name_start) } + return short in g.prep_alias_short_names +} + +@[inline] +fn preseed_type_words(typ &types.Type) (u64, u64, int) { + words := unsafe { &u64(voidptr(typ)) } + w0 := unsafe { words[0] } + w1 := unsafe { words[1] } + return w0, w1, int((w0 >> 4 ^ w1) & 4095) +} + +// par_cgen_prep_enabled gates the parallel fused item prep, so a single binary +// can A/B or disable it (`V3_NO_PAR_CGEN_PREP=1`). +fn par_cgen_prep_enabled() bool { + return os.getenv('V3_NO_PAR_CGEN_PREP') == '' +} + +fn (mut g FlatGen) should_preseed_parallel_type_text_ptr_cached(typ string, mut cache map[string]bool) bool { + mut tcache := g.prep_typ_text_cache + if isnil(tcache) { + return g.should_preseed_parallel_type_text_cached(typ, mut cache) + } + slot := int((u64(voidptr(typ.str)) >> 4) & 4095) + // Same pointer + same length + live generation means same text in the same + // context (the length guards unsafe zero-copy slices sharing a base). + if tcache.gens[slot] == tcache.generation && tcache.ptrs[slot] == voidptr(typ.str) + && tcache.lens[slot] == typ.len { + return tcache.verdicts[slot] + } + verdict := g.should_preseed_parallel_type_text_cached(typ, mut cache) + tcache.ptrs[slot] = voidptr(typ.str) + tcache.gens[slot] = tcache.generation + tcache.lens[slot] = typ.len + tcache.verdicts[slot] = verdict + return verdict +} + +fn (g &FlatGen) should_preseed_parallel_type_text_cached(typ string, mut cache map[string]bool) bool { + if typ.len == 0 { + return false + } + if cached := cache[typ] { + return cached + } + should_preseed := g.should_preseed_parallel_type_text(typ) + cache[typ] = should_preseed + return should_preseed +} + +// should_preseed_parallel_type_text reports whether should preseed parallel type text applies in c. +fn (g &FlatGen) should_preseed_parallel_type_text(typ string) bool { + if typ.len == 0 { + return false + } + clean := g.parallel_base_type_text(typ) + if clean.contains('fn(') || clean.contains('fn (') { + return true + } + if clean in g.tc.type_aliases { + return true + } + qtyp := g.tc.qualify_name(clean) + return qtyp in g.tc.type_aliases +} + +// parallel_base_type_text supports parallel base type text handling for FlatGen. +fn (g &FlatGen) parallel_base_type_text(typ string) string { + mut clean := trimmed_space(typ) + for clean.len > 0 { + if clean.starts_with('shared ') { + clean = trimmed_space(clean[7..]) + } else if clean[0] == `&` || clean[0] == `?` || clean[0] == `!` { + clean = trimmed_space(clean[1..]) + } else if clean.starts_with('...') { + clean = trimmed_space(clean[3..]) + } else if clean.starts_with('[]') { + clean = trimmed_space(clean[2..]) + } else { + break + } + } + return clean +} + +// preseed_parallel_fn_ptr_type supports preseed parallel fn ptr type handling for FlatGen. +fn (mut g FlatGen) preseed_parallel_fn_ptr_type(typ types.Type) { + if typ is types.FnType { + g.register_fn_ptr_type(g.fn_ptr_type_key(typ)) + for param in typ.params { + g.preseed_parallel_fn_ptr_type(param) + } + g.preseed_parallel_fn_ptr_type(typ.return_type) + } else if typ is types.Pointer { + g.preseed_parallel_fn_ptr_type(typ.base_type) + } else if typ is types.Array { + g.preseed_parallel_fn_ptr_type(typ.elem_type) + } else if typ is types.ArrayFixed { + g.preseed_parallel_fn_ptr_type(typ.elem_type) + } else if typ is types.Map { + g.preseed_parallel_fn_ptr_type(typ.key_type) + g.preseed_parallel_fn_ptr_type(typ.value_type) + } else if typ is types.OptionType { + g.preseed_parallel_fn_ptr_type(typ.base_type) + } else if typ is types.ResultType { + g.preseed_parallel_fn_ptr_type(typ.base_type) + } else if typ is types.Alias { + g.preseed_parallel_fn_ptr_type(typ.base_type) + } else if typ is types.MultiReturn { + for item in typ.types { + g.preseed_parallel_fn_ptr_type(item) + } + } +} + +// new_parallel_worker builds a per-worker FlatGen for parallel codegen. +// +// The lookup tables populated before gen_fns_dispatch (in collect_gen_info, +// collect_interface_impls and the precompute_* passes) are READ-ONLY during codegen, so +// they are SHARED by reference instead of cloned — V maps/arrays are reference types and +// concurrent readers are safe. Only the state a worker actually mutates while emitting is +// kept private: the output builder; the string-literal table (interned during gen); the +// fn_ptr_types / needed_optional_types / emitted_* sets and the param_types_cache / +// array_method_cache memoization caches (all written during gen); the per-function +// cur_param_* scratch; and runtime_inits (kept private out of caution). This drops the +// bulk of each worker's clone cost — previously the whole table set was duplicated per +// worker and, under -gc none, never freed. +fn (g &FlatGen) new_parallel_worker(worker_id int) &FlatGen { + return g.new_parallel_worker_config(worker_id, false) +} + +fn (g &FlatGen) new_parallel_tail_worker(worker_id int) &FlatGen { + mut w := g.new_parallel_worker(worker_id) + w.is_shared = g.is_shared + // gen_vinit pairs each initializer with its owning module. These arrays are + // declaration-task output and remain read-only while the tail is generated. + w.const_runtime_init_modules = g.const_runtime_init_modules.clone() + w.runtime_init_modules = g.runtime_init_modules.clone() + return w +} + +// new_parallel_dispatch_worker selects the lightweight accumulator only when +// scoped batching keeps all actual emission in fresh full workers. +fn (g &FlatGen) new_parallel_dispatch_worker(worker_id int) &FlatGen { + if g.scope_parallel_workers { + return g.new_parallel_result_worker(worker_id) + } + return g.new_parallel_worker(worker_id) +} + +// new_parallel_result_worker creates a non-emitting helper accumulator. Caches +// that it only passes to fresh batch generators stay shared with the frozen +// master snapshot; result tables remain private. Its string tables are copied +// eagerly because the master can extend its own table after tasks start, before +// a helper's first copy-on-write intern. +fn (g &FlatGen) new_parallel_result_worker(worker_id int) &FlatGen { + return g.new_parallel_worker_config(worker_id, true) +} + +fn (g &FlatGen) new_parallel_worker_config(worker_id int, result_only bool) &FlatGen { + mut w := &FlatGen{ + sb: strings.new_builder(if result_only { 0 } else { 64_000 }) + a: unsafe { g.a } + used_fns: g.used_fns + used_fn_names: g.used_fn_names + fn_gen_items: g.fn_gen_items + top_level_node_ids: g.top_level_node_ids + test_files: if result_only { g.test_files } else { g.test_files.clone() } + is_prod: g.is_prod + check_overflow: g.check_overflow + force_bounds_checking: g.force_bounds_checking + object_file_mode: g.object_file_mode + cache_program_files: g.cache_program_files + incremental_fn_names: g.incremental_fn_names + cached_support_identifiers: g.cached_support_identifiers + str_lits: if result_only { + clone_cgen_string_list(g.str_lits) + } else if g.scope_parallel_workers { + g.str_lits + } else { + g.str_lits.clone() + } + str_lit_ids: if result_only { + clone_cgen_string_int_map(g.str_lit_ids) + } else if g.scope_parallel_workers { + g.str_lit_ids + } else { + g.str_lit_ids.clone() + } + str_lits_shared: g.scope_parallel_workers && !result_only + global_types: g.global_types + global_raw_type_texts: g.global_raw_type_texts + enum_vals: g.enum_vals + enum_value_exprs: g.enum_value_exprs + interfaces: g.interfaces + const_vals: g.const_vals + const_modules: g.const_modules + const_init_order: g.const_init_order + fixed_storage_consts: g.fixed_storage_consts + global_modules: g.global_modules + global_inits: g.global_inits + global_init_order: g.global_init_order + c_decl_abi_names: g.c_decl_abi_names + c_extern_global_names: g.c_extern_global_names + enum_backing_infos: g.enum_backing_infos + iface_impls: g.iface_impls + interface_dispatch_required: g.interface_dispatch_required + iface_type_ids: g.iface_type_ids + interface_boxed_types: g.interface_boxed_types + interface_boxed_types_done: g.interface_boxed_types_done + ierror_method_emit_names: g.ierror_method_emit_names + recursive_drop_helpers: g.recursive_drop_helpers + sum_name_lookup: g.sum_name_lookup + module_init_fns: g.module_init_fns + module_init_fn_modules: g.module_init_fn_modules + module_cleanup_fns: g.module_cleanup_fns + module_cleanup_fn_modules: g.module_cleanup_fn_modules + module_imports: g.module_imports + preserved_header_files_seen: g.preserved_header_files_seen + libc_compat_fns: g.libc_compat_fns.clone() + tc: if result_only { + unsafe { g.tc } + } else { + g.clone_parallel_type_checker() + } + has_builtins: g.has_builtins + cache_split: g.cache_split + compile_values: g.compile_values + skip_generics: g.skip_generics + tmp_count: (worker_id + 1) * 100_000 + line_start: true + modules: g.modules + fn_ptr_types: g.fn_ptr_types.clone() + used_fn_ptr_types: if g.scope_parallel_workers { + map[string]bool{} + } else { + g.used_fn_ptr_types.clone() + } + fixed_array_ret_wrappers: g.fixed_array_ret_wrappers + concrete_optional_abi_fns: g.concrete_optional_abi_fns + fn_decl_param_types: g.fn_decl_param_types + fn_decl_variadic: g.fn_decl_variadic + fn_decl_variadic_short_counts: g.fn_decl_variadic_short_counts + fn_decl_shared_params: g.fn_decl_shared_params + fn_shared_params_resolved: g.fn_shared_params_resolved + has_shared_params: g.has_shared_params + fn_decl_mut_receivers: g.fn_decl_mut_receivers + fn_decl_ret_types: g.fn_decl_ret_types + non_generic_fn_names_by_module: g.non_generic_fn_names_by_module + generic_fn_keys_by_short: g.generic_fn_keys_by_short + generic_fn_keys_by_cname: g.generic_fn_keys_by_cname + generic_fn_key_ordinal: g.generic_fn_key_ordinal + struct_decl_infos: g.struct_decl_infos + struct_decl_short_infos: g.struct_decl_short_infos + decl_attrs: g.decl_attrs + shared_type_names: g.shared_type_names + shared_alias_pointer_shorts: g.shared_alias_pointer_shorts + const_runtime_inits: if result_only { + g.const_runtime_inits + } else { + g.const_runtime_inits.clone() + } + runtime_inits: if result_only { + g.runtime_inits + } else { + g.runtime_inits.clone() + } + compiler_vroot: g.compiler_vroot + compiler_vexe: g.compiler_vexe + compiler_vexe_env_setup: g.compiler_vexe_env_setup + ccompiler: g.ccompiler + suppress_main: g.suppress_main + cur_param_names: if result_only { + g.cur_param_names + } else { + g.cur_param_names.clone() + } + cur_param_type_values: if result_only { + g.cur_param_type_values + } else { + g.cur_param_type_values.clone() + } + cur_param_types: if result_only { + g.cur_param_types + } else { + g.cur_param_types.clone() + } + cur_concrete_optional_params: if result_only { + g.cur_concrete_optional_params + } else { + g.cur_concrete_optional_params.clone() + } + cur_mut_params: if result_only { + g.cur_mut_params + } else { + g.cur_mut_params.clone() + } + cur_mut_pointer_params: if result_only { + g.cur_mut_pointer_params + } else { + g.cur_mut_pointer_params.clone() + } + cur_mut_param_owners: if result_only { + g.cur_mut_param_owners + } else { + g.cur_mut_param_owners.clone() + } + cur_fn_ret: g.cur_fn_ret + cur_fn_ret_is_optional: g.cur_fn_ret_is_optional + cur_fn_ret_base: g.cur_fn_ret_base + memo_usable_expr_types: g.memo_usable_expr_types + cache_struct_fields: g.cache_struct_fields + dedup_fn_decl_aliases: g.dedup_fn_decl_aliases + prefix_param_scan: g.prefix_param_scan + lean_parallel_worker_init: g.lean_parallel_worker_init + lazy_param_abi_merge: g.lazy_param_abi_merge + expected_expr_type: g.expected_expr_type + expected_enum: g.expected_enum + needed_optional_types: g.needed_optional_types.clone() + optional_types_ready: g.optional_types_ready + emitted_optional_types: if result_only { + g.emitted_optional_types + } else { + g.emitted_optional_types.clone() + } + // Function selection is complete before workers are created; body + // generation only reads this set. + emitted_fns: g.emitted_fns + array_method_cache: if result_only { + g.array_method_cache + } else { + g.array_method_cache.clone() + } + param_types_cache: if result_only { + g.param_types_cache + } else { + g.param_types_cache.clone() + } + interface_receiver_cache: &StringLookupCache{} + normalize_call_cache: &StringLookupCache{} + flattened_generic_name_cache: &StringLookupCache{} + generic_struct_context_ct_cache: &StringLookupCache{} + struct_cname_cache: &StringLookupCache{} + unique_struct_ct_cache: &StringLookupCache{} + alias_method_cache: &StringLookupCache{} + import_alias_cache: &ContextStringLookupCache{} + enum_selector_cache: &ContextStringLookupCache{} + enum_method_cache: &ContextStringLookupCache{} + qualified_enum_method_cache: &ContextStringLookupCache{} + struct_decl_pref_cache: &StructDeclPrefCache{} + embedded_fields_by_type: g.embedded_fields_by_type + param_types_by_short: g.param_types_by_short + generic_method_candidates: g.generic_method_candidates + spawn_wrapper_names: g.spawn_wrapper_names.clone() + spawn_wrapper_defs: g.spawn_wrapper_defs.clone() + spawn_wrapper_defs_seen: g.spawn_wrapper_defs_seen.clone() + callback_wrapper_names: g.callback_wrapper_names.clone() + callback_wrapper_defs: g.callback_wrapper_defs.clone() + callback_wrapper_defs_seen: g.callback_wrapper_defs_seen.clone() + c_extern_refs: g.c_extern_refs.clone() + c_extern_refs_ready: g.c_extern_refs_ready + scope_parallel_workers: g.scope_parallel_workers + c_name_cache: &CNameCache{ + base: if !isnil(g.c_name_cache.base) { g.c_name_cache.base } else { g.c_name_cache } + } + // The const short-name index is read-only after its first build (the + // master queries it during the const precompute, before the forks); + // sharing it avoids a rebuild per worker. + const_short_index: g.const_short_index + mut_recv_facts: &FnNameFactCache{} + local_typedef_shadow_facts: &FnNameFactCache{} + local_global_shadow_facts: &ContextNameFactCache{} + local_global_suffix_names: g.local_global_suffix_names + local_global_suffix_names_ready: g.local_global_suffix_names_ready + generic_app_cache: &GenericAppCache{ + base: if !isnil(g.generic_app_cache.base) { + g.generic_app_cache.base + } else { + g.generic_app_cache + } + } + } + if !g.lean_parallel_worker_init { + w.ierror_stack_pointer_aliases = []map[string]bool{} + w.ierror_owned_pointer_by_owner = map[string]bool{} + w.local_pointer_storage_by_owner = map[string]bool{} + w.local_c_type_by_owner = map[string]string{} + w.local_raw_type_by_owner = map[string]string{} + w.local_shared_storage_by_owner = map[string]bool{} + w.local_fn_value_c_name_by_owner = map[string]string{} + w.default_value_stack = map[string]bool{} + w.loop_label_depths = map[string]int{} + w.loop_defer_starts = []int{} + w.loop_label_defer_starts = map[string]int{} + w.goto_label_c_names = map[string]string{} + } + return w +} + +fn (g &FlatGen) clone_parallel_type_checker_legacy() &types.TypeChecker { + // Cgen only reads file-level bindings. Give each worker an empty child scope + // over the immutable checked scope instead of cloning the full symbol table. + fs := types.new_scope(g.tc.file_scope) + mut wtc := &types.TypeChecker{ + a: unsafe { g.tc.a } + fast_parse_recent: g.tc.fast_parse_recent + fast_type_text_refs: g.tc.fast_type_text_refs + fast_c_type_recent: g.tc.fast_c_type_recent + memo_call_info: g.tc.memo_call_info + fn_ret_types: g.tc.fn_ret_types + fn_param_types: g.tc.fn_param_types + c_fn_module_ret_types: g.tc.c_fn_module_ret_types + c_fn_module_param_types: g.tc.c_fn_module_param_types + c_fn_module_variadic: g.tc.c_fn_module_variadic + fn_ret_type_texts: g.tc.fn_ret_type_texts + fn_param_type_texts: g.tc.fn_param_type_texts + fn_type_files: g.tc.fn_type_files + fn_type_modules: g.tc.fn_type_modules + fn_generic_params: g.tc.fn_generic_params + specialized_generic_fns: g.tc.specialized_generic_fns + fn_variadic: g.tc.fn_variadic + fn_implicit_veb_ctx: g.tc.fn_implicit_veb_ctx + c_variadic_fns: g.tc.c_variadic_fns + structs: g.tc.structs + struct_modules: g.tc.struct_modules + struct_files: g.tc.struct_files + soa_structs: g.tc.soa_structs + struct_error_embeds_shadow_builtin: g.tc.struct_error_embeds_shadow_builtin + struct_generic_params: g.tc.struct_generic_params + struct_field_c_abi_fns: g.tc.struct_field_c_abi_fns + unions: g.tc.unions + type_aliases: g.tc.type_aliases + type_alias_modules: g.tc.type_alias_modules + type_alias_generic_params: g.tc.type_alias_generic_params + type_alias_c_abi_fns: g.tc.type_alias_c_abi_fns + sum_types: g.tc.sum_types + sum_generic_params: g.tc.sum_generic_params + enum_names: g.tc.enum_names + enum_fields: g.tc.enum_fields + flag_enums: g.tc.flag_enums + interface_names: g.tc.interface_names + interface_generic_params: g.tc.interface_generic_params + interface_fields: g.tc.interface_fields + interface_embeds: g.tc.interface_embeds + interface_abstract_methods: g.tc.interface_abstract_methods + interface_impl_name_snapshots: g.tc.interface_impl_name_snapshots + interface_impl_candidates_at_snapshot: g.tc.interface_impl_candidates_at_snapshot + c_globals: g.tc.c_globals + const_types: g.tc.const_types + const_exprs: g.tc.const_exprs + const_modules: g.tc.const_modules + const_files: g.tc.const_files + const_suffixes: g.tc.const_suffixes + imports: g.tc.imports + file_imports: g.tc.file_imports + file_selective_imports: g.tc.file_selective_imports + file_modules: g.tc.file_modules + file_scope: g.tc.file_scope + cur_scope: fs + scope_pool: []&types.Scope{} + has_builtins: g.tc.has_builtins + resolution_type_mode: g.tc.resolution_type_mode + trust_checked_expr_types: g.tc.trust_checked_expr_types + cur_module: g.tc.cur_module + cur_file: g.tc.cur_file + errors: g.tc.errors.clone() + resolved_call_names: g.tc.resolved_call_names + resolved_call_set: g.tc.resolved_call_set + resolved_fn_value_names: g.tc.resolved_fn_value_names + resolved_fn_value_set: g.tc.resolved_fn_value_set + statement_nodes: g.tc.statement_nodes + expr_type_values: g.tc.expr_type_values + expr_type_set: g.tc.expr_type_set + checking_nodes: g.tc.checking_nodes + parallel_check_sparse: g.tc.parallel_check_sparse + check_range_lo: g.tc.check_range_lo + check_range_hi: g.tc.check_range_hi + sparse_resolved_call_names: g.tc.sparse_resolved_call_names + sparse_resolved_fn_values: g.tc.sparse_resolved_fn_values + sparse_statement_nodes: g.tc.sparse_statement_nodes + sparse_expr_type_values: g.tc.sparse_expr_type_values + sparse_checking_nodes: g.tc.sparse_checking_nodes + diagnose_unknown_calls: g.tc.diagnose_unknown_calls + reject_unlowered_map_mutation: g.tc.reject_unlowered_map_mutation + diagnostic_files: g.tc.diagnostic_files + selected_file_called_fns: g.tc.selected_file_called_fns + smartcasts: g.tc.smartcasts + // Read-only map cgen uses to recover substituted signatures for generic-receiver + // method values (`Box[int].method` as a callback); without it a parallel worker + // sees an empty map and gen_method_value_closure falls through. + generic_method_value_info: g.tc.generic_method_value_info + params_structs: g.tc.params_structs + c_typedef_structs: g.tc.c_typedef_structs + } + wtc.inherit_ownership_codegen_metadata_from(g.tc) + // A private empty TypeCache lets the worker use the lazily-built lookup + // indexes (short type names, local fn decls) and the field/IError + // memoizations instead of their uncached full-scan fallbacks. It shares no + // state with other threads. + wtc.set_fresh_type_cache_based_on(g.tc, g.tc.type_cache_parse_enabled()) + wtc.reset_resolution_type_view_cache() + return wtc +} + +fn (mut g FlatGen) publish_worker_string_literals(w &FlatGen) map[int]int { + mut remap := map[int]int{} + mut common_len := 0 + for common_len < g.str_lits.len && common_len < w.str_lits.len + && g.str_lits[common_len] == w.str_lits[common_len] { + common_len++ + } + for local_id in common_len .. w.str_lits.len { + literal := w.str_lits[local_id] + global_id := if existing_id := g.str_lit_ids[literal] { + existing_id + } else { + g.intern_string(literal.clone()) + } + if global_id != local_id { + remap[local_id] = global_id + } + } + return remap +} + +fn remap_scoped_worker_string_symbols(source string, remap map[int]int, user_c_symbols map[string]bool) string { + if remap.len == 0 { + return source.clone() + } + mut out := strings.new_builder(source.len) + mut i := 0 + for i < source.len { + if source[i] in [`"`, `'`] { + quote := source[i] + start := i + i++ + for i < source.len { + if source[i] == `\\` && i + 1 < source.len { + i += 2 + continue + } + i++ + if source[i - 1] == quote { + break + } + } + out.write_string(source[start..i]) + continue + } + if i + 1 < source.len && source[i] == `/` && source[i + 1] == `/` { + start := i + i += 2 + for i < source.len && source[i] != `\n` { + i++ + } + out.write_string(source[start..i]) + continue + } + if i + 1 < source.len && source[i] == `/` && source[i + 1] == `*` { + start := i + i += 2 + for i + 1 < source.len && !(source[i] == `*` && source[i + 1] == `/`) { + i++ + } + if i + 1 < source.len { + i += 2 + } else { + i = source.len + } + out.write_string(source[start..i]) + continue + } + if c_identifier_start(source[i]) { + start := i + i++ + for i < source.len && c_identifier_continue(source[i]) { + i++ + } + identifier := source[start..i] + if cache_numbered_string_symbol(identifier) && !user_c_symbols[identifier] { + mut local_id := 0 + for digit in identifier[5..].bytes() { + local_id = local_id * 10 + int(digit - `0`) + } + if global_id := remap[local_id] { + out.write_string('_str_${global_id}') + continue + } + } + out.write_string(identifier) + continue + } + out.write_u8(source[i]) + i++ + } + return out.str() +} + +// merge_parallel_worker supports merge parallel worker handling for FlatGen. +fn (mut g FlatGen) merge_parallel_worker(w &FlatGen) { + mut unordered := []string{} + mut unordered_wrapper_defs := []ParallelChunkWrapperDefs{} + g.merge_parallel_worker_into(w, mut unordered, mut unordered_wrapper_defs) +} + +fn (mut g FlatGen) merge_parallel_worker_ordered(w &FlatGen, mut ordered []string, mut ordered_wrapper_defs []ParallelChunkWrapperDefs) { + g.merge_parallel_worker_into(w, mut ordered, mut ordered_wrapper_defs) +} + +fn (mut g FlatGen) merge_parallel_worker_into(w &FlatGen, mut ordered []string, mut ordered_wrapper_defs []ParallelChunkWrapperDefs) { + mut ww := unsafe { w } + if g.output_error.len == 0 && w.output_error.len > 0 { + g.output_error = w.output_error.clone() + } + string_id_remap := g.publish_worker_string_literals(w) + borrow_worker_segments := os.getenv('V3_NO_RETAIN_CGEN_RESULT_SCOPES') == '' + && w.worker_scope != unsafe { nil } && !g.cache_split && string_id_remap.len == 0 + user_c_symbols := if string_id_remap.len > 0 { + g.cache_user_c_string_symbols() + } else { + map[string]bool{} + } + worker_output := ww.sb.str() + if worker_output.len > 0 { + if g.cache_split { + stable_output := ww.rewrite_cache_string_symbols(worker_output) + g.fn_segs << stable_output + unsafe { worker_output.free() } + } else if string_id_remap.len > 0 { + g.fn_segs << remap_scoped_worker_string_symbols(worker_output, string_id_remap, + user_c_symbols) + unsafe { worker_output.free() } + } else { + g.fn_segs << worker_output + } + } else { + unsafe { worker_output.free() } + } + // The ordered segment owns the copied output; release the worker builder. + unsafe { ww.sb.free() } + for segment_idx, segment in w.fn_segs { + normalized := if g.cache_split { + ww.rewrite_cache_string_symbols(segment) + } else if string_id_remap.len > 0 { + remap_scoped_worker_string_symbols(segment, string_id_remap, user_c_symbols) + } else if borrow_worker_segments { + // The immutable segment already lives in this worker's retained result + // arena. Its lifetime now extends through final file output, so moving + // the string view avoids cloning the complete generated function body. + segment + } else { + segment.clone() + } + if ordered.len > 0 && segment_idx < w.fn_seg_chunk_indexes.len { + chunk_idx := w.fn_seg_chunk_indexes[segment_idx] + if chunk_idx >= 0 && chunk_idx < ordered.len { + ordered[chunk_idx] = normalized + continue + } + } + g.fn_segs << normalized + } + if g.cache_split { + for literal in w.str_lits { + g.intern_string(literal.clone()) + } + } + for opt_name, val_type in w.needed_optional_types { + // The worker starts with the master's optional-type table. Do not read its + // borrowed values again: only entries discovered by the worker need merging. + if opt_name !in g.needed_optional_types { + g.needed_optional_types[opt_name.clone()] = val_type.clone() + } + } + for encoded, name in w.fn_ptr_types { + if encoded !in g.fn_ptr_types { + g.fn_ptr_types[encoded.clone()] = name.clone() + } + } + for encoded, used in w.used_fn_ptr_types { + if used { + g.used_fn_ptr_types[encoded.clone()] = true + } + } + for name, used in w.c_extern_refs { + if used { + g.c_extern_refs[name.clone()] = true + } + } + for name, enabled in w.libc_compat_fns { + if enabled { + g.libc_compat_fns[name.clone()] = true + } + } + // Spawn wrappers (thread arg structs + trampoline fns) are generated on demand + // inside fn bodies, so a worker that emits a `spawn` produces wrapper defs the + // master must also emit. Deduplicate by their deterministic key/def. + for key, name in w.spawn_wrapper_names { + if key !in g.spawn_wrapper_names { + g.spawn_wrapper_names[key.clone()] = name.clone() + } + } + if ordered.len > 0 { + for wrappers in w.parallel_chunk_wrapper_defs { + if wrappers.chunk_idx < 0 || wrappers.chunk_idx >= ordered_wrapper_defs.len { + continue + } + for def in wrappers.spawn { + normalized := if g.cache_split { + ww.rewrite_cache_string_symbols(def) + } else if string_id_remap.len > 0 { + remap_scoped_worker_string_symbols(def, string_id_remap, user_c_symbols) + } else { + def.clone() + } + ordered_wrapper_defs[wrappers.chunk_idx].spawn << normalized + } + } + } else { + for def in w.spawn_wrapper_defs { + if g.cache_split { + g.add_spawn_wrapper_def(ww.rewrite_cache_string_symbols(def)) + } else if string_id_remap.len > 0 { + g.add_spawn_wrapper_def(remap_scoped_worker_string_symbols(def, string_id_remap, + user_c_symbols)) + } else { + g.add_spawn_wrapper_def(def.clone()) + } + } + } + for key, name in w.callback_wrapper_names { + if key !in g.callback_wrapper_names { + g.callback_wrapper_names[key.clone()] = name.clone() + } + } + if ordered.len > 0 { + for wrappers in w.parallel_chunk_wrapper_defs { + if wrappers.chunk_idx < 0 || wrappers.chunk_idx >= ordered_wrapper_defs.len { + continue + } + for def in wrappers.callback { + normalized := if g.cache_split { + ww.rewrite_cache_string_symbols(def) + } else if string_id_remap.len > 0 { + remap_scoped_worker_string_symbols(def, string_id_remap, user_c_symbols) + } else { + def.clone() + } + ordered_wrapper_defs[wrappers.chunk_idx].callback << normalized + } + } + } else { + for def in w.callback_wrapper_defs { + if g.cache_split { + g.add_callback_wrapper_def(ww.rewrite_cache_string_symbols(def)) + } else if string_id_remap.len > 0 { + g.add_callback_wrapper_def(remap_scoped_worker_string_symbols(def, string_id_remap, + user_c_symbols)) + } else { + g.add_callback_wrapper_def(def.clone()) + } + } + } +} + +// finish_parallel_worker_scope either releases a joined result arena +// immediately (oracle fallback) or retains it until FlatGen has written every +// borrowed function segment. +fn (mut g FlatGen) finish_parallel_worker_scope(mut w FlatGen) { + if w.worker_scope == unsafe { nil } { + return + } + if os.getenv('V3_NO_RETAIN_CGEN_RESULT_SCOPES') == '' { + g.parallel_worker_scopes << w.worker_scope + } else { + cgen_worker_scope_free(w.worker_scope) + } + w.worker_scope = unsafe { nil } +} + +fn (mut g FlatGen) replay_ordered_parallel_wrapper_defs(wrapper_defs []ParallelChunkWrapperDefs) { + for wrappers in wrapper_defs { + for def in wrappers.spawn { + g.add_spawn_wrapper_def(def) + } + for def in wrappers.callback { + g.add_callback_wrapper_def(def) + } + } +} + +// run_pre_dispatch_parallel overlaps the serial pre-dispatch work: the +// fixed-storage-const scan runs on a helper thread while the master collects +// the fn work items and pre-seeds the string/fn-ptr tables the workers need. +// Returns false when the parallel path is not applicable (the caller then +// runs the serial order). +fn (mut g FlatGen) run_pre_dispatch_parallel(no_parallel bool) bool { + $if windows { + return false + } $else { + if no_parallel { + return false + } + if isnil(g.a.worker_pool) { + g.a.worker_pool = workers.new(runtime.nr_jobs() - 1) + } + mut fs_worker := g.new_parallel_worker(0) + fs_worker.tc.verbose = g.tc.verbose + mut fixed_array_worker := g.new_parallel_worker(1) + fixed_array_worker.tc.verbose = g.tc.verbose + mut optional_worker := g.new_parallel_worker(2) + optional_worker.tc.verbose = g.tc.verbose + fail := os.getenv('V3_TEST_PTHREAD_CREATE_FAIL') + if fail.len > 0 { + g.a.worker_pool.run([ + workers.Task{ + run: fixed_storage_scan_thread + arg: voidptr(fs_worker) + force_sync: fail == 'cgen:all' || fail == 'cgen:pre:all' || fail == 'cgen:pre:0' + }, + workers.Task{ + run: fixed_array_support_thread + arg: voidptr(fixed_array_worker) + force_sync: fail == 'cgen:all' || fail == 'cgen:pre:all' + }, + workers.Task{ + run: optional_support_thread + arg: voidptr(optional_worker) + force_sync: fail == 'cgen:all' || fail == 'cgen:pre:all' + }, + workers.Task{ + run: pre_dispatch_master_thread + arg: voidptr(g) + force_sync: true + }, + ]) + g.refine_fn_item_costs(no_parallel, false) + } else { + // Item selection only reads the AST and immutable checker tables, so let + // its exact-cost pass use the otherwise-idle pool while the independent + // fixed-storage scan finishes on a helper thread. + mut psw := time.new_stopwatch() + fixed_storage_thread := spawn fixed_storage_scan_thread(voidptr(fs_worker)) + fixed_array_thread := spawn fixed_array_support_thread(voidptr(fixed_array_worker)) + optional_thread := spawn optional_support_thread(voidptr(optional_worker)) + g.prepare_pre_dispatch_master() + g.timing_profile(' [ttime] cg prep master ${f64(psw.elapsed().microseconds()) / 1000.0:7.2f} ms') + psw.restart() + g.refine_fn_item_costs(no_parallel, true) + g.timing_profile(' [ttime] cg cost refine ${f64(psw.elapsed().microseconds()) / 1000.0:7.2f} ms') + psw.restart() + _ = fixed_storage_thread.wait() + _ = fixed_array_thread.wait() + _ = optional_thread.wait() + g.timing_profile(' [ttime] cg fs wait ${f64(psw.elapsed().microseconds()) / 1000.0:7.2f} ms') + } + g.publish_fixed_storage_scan(mut fs_worker) + g.publish_fixed_array_support(mut fixed_array_worker) + g.publish_optional_support(mut optional_worker) + if g.parallel_prepared && !g.prep_externs_pending { + // Item-body and top-level C-extern refs are fully collected (fused + // prep + exact-cost pass or its serial fallback); the pre-dispatch + // preseed can reuse them instead of re-walking every selected body. + g.c_extern_refs_ready = true + } + return true + } +} diff --git a/vlib/v3/gen/fastc/for.v b/vlib/v3/gen/fastc/for.v new file mode 100644 index 00000000000000..e273d9b9699a1c --- /dev/null +++ b/vlib/v3/gen/fastc/for.v @@ -0,0 +1,746 @@ +module fastc + +import v3.flat +import v3.types + +fn (mut g FlatGen) take_pending_loop_label() string { + label := g.pending_loop_label + g.pending_loop_label = '' + return label +} + +fn (mut g FlatGen) push_loop_label_depth(label string, defer_start int) LoopLabelState { + if label.len == 0 { + return LoopLabelState{} + } + mut state := LoopLabelState{ + label: label + } + if prev_depth := g.loop_label_depths[label] { + state.had_prev = true + state.prev_depth = prev_depth + } + if prev_defer_start := g.loop_label_defer_starts[label] { + state.had_prev_defer_start = true + state.prev_defer_start = prev_defer_start + } + g.loop_label_depths[label] = g.loop_depth + 1 + g.loop_label_defer_starts[label] = defer_start + return state +} + +fn (mut g FlatGen) pop_loop_label_depth(state LoopLabelState) { + if state.label.len == 0 { + return + } + if state.had_prev { + g.loop_label_depths[state.label] = state.prev_depth + } else { + g.loop_label_depths.delete(state.label) + } + if state.had_prev_defer_start { + g.loop_label_defer_starts[state.label] = state.prev_defer_start + } else { + g.loop_label_defer_starts.delete(state.label) + } +} + +fn (mut g FlatGen) user_goto_c_label(label string) string { + if label.starts_with('__for_post_') { + return g.cname(label) + } + for suffix in ['_continue', '_break'] { + if label.ends_with(suffix) { + base := label[..label.len - suffix.len] + if base_label := g.goto_label_c_names[base] { + return '${base_label}_${suffix}' + } + } + } + if c_label := g.goto_label_c_names[label] { + return c_label + } + c_label := '__v_user_goto_${g.goto_label_count}' + g.goto_label_count++ + g.goto_label_c_names[label] = c_label + return c_label +} + +fn (mut g FlatGen) labelled_continue_skip_drops_var(label string) string { + return '${g.user_goto_c_label(label)}__continue_flag' +} + +fn (mut g FlatGen) loop_control_c_label(label string, is_continue bool) string { + suffix := if is_continue { '__continue' } else { '__break' } + return g.user_goto_c_label(label) + suffix +} + +fn (mut g FlatGen) gen_labelled_continue_skip_drops_var(label string) { + if label.len > 0 { + g.writeln('bool ${g.labelled_continue_skip_drops_var(label)} = false;') + } +} + +fn (mut g FlatGen) gen_loop_iteration_ownership_drops_for_label(label string) { + if label.len == 0 { + g.gen_loop_iteration_ownership_drops() + return + } + skip_drops := g.labelled_continue_skip_drops_var(label) + g.writeln('if (!${skip_drops}) {') + g.indent++ + g.gen_loop_iteration_ownership_drops() + g.indent-- + g.writeln('}') + g.writeln('${skip_drops} = false;') +} + +fn (g &FlatGen) is_loop_continue_label(id flat.NodeId, label string) bool { + if label.len == 0 || int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(id)] + return node.kind == .label_stmt && node.value == '${label}_continue' +} + +fn (mut g FlatGen) gen_loop_body_node(id flat.NodeId, label string) bool { + if g.is_loop_continue_label(id, label) { + g.gen_loop_continue_label(label) + return true + } + g.gen_node(id) + return false +} + +fn (mut g FlatGen) gen_loop_continue_label(label string) { + if label.len > 0 { + if g.tc.autofree_mode { + g.writeln('${g.loop_control_c_label(label, true)}: {}') + } else { + g.writeln('${g.loop_control_c_label(label, true)}: ;') + } + } +} + +// gen_for emits for output for c. +@[direct_array_access] +fn (mut g FlatGen) gen_for(node flat.Node) { + g.push_scope() + defer_start := g.defers.len + label_state := g.push_loop_label_depth(g.take_pending_loop_label(), defer_start) + g.loop_defer_starts << defer_start + init_node := g.a.child_node(&node, 0) + cond_id := g.a.child(&node, 1) + cond_node := g.a.nodes[int(cond_id)] + post_node := g.a.child_node(&node, 2) + wrap_init := init_node.kind != .empty + + if wrap_init { + g.writeln('{') + g.indent++ + if init_node.kind == .block && init_node.value == 'for_init_expanded' { + for i in 0 .. init_node.children_count { + g.gen_node(g.a.child(init_node, i)) + } + } else { + g.gen_node(g.a.child(&node, 0)) + } + } + + if init_node.kind == .empty && cond_node.kind == .empty && post_node.kind == .empty { + g.writeln('for (;;) {') + } else if init_node.kind == .empty && post_node.kind == .empty { + g.write('while (') + g.gen_expr(cond_id) + g.writeln(') {') + } else { + g.write('for (; ') + if cond_node.kind != .empty { + g.gen_expr(cond_id) + } + g.write('; ') + if post_node.kind != .empty { + g.gen_node_inline(g.a.child(&node, 2)) + } + g.writeln(') {') + } + g.indent++ + g.gen_labelled_continue_skip_drops_var(label_state.label) + g.loop_depth++ + mut emitted_continue_label := false + for i in 3 .. node.children_count { + emitted_continue_label = g.gen_loop_body_node(g.a.child(&node, i), label_state.label) + || emitted_continue_label + } + g.loop_depth-- + g.gen_defers_from(defer_start) + if !emitted_continue_label { + g.gen_loop_continue_label(label_state.label) + } + if !node.skip_ownership_drops { + g.gen_loop_iteration_ownership_drops_for_label(label_state.label) + } + g.trim_defers(defer_start) + g.indent-- + g.writeln('}') + if wrap_init { + if g.tc.autofree_mode && label_state.label.len > 0 { + g.writeln('${g.loop_control_c_label(label_state.label, false)}: {}') + g.emitted_loop_break_labels[label_state.label] = true + } + if !node.skip_ownership_drops { + g.gen_scope_ownership_drops() + } + g.indent-- + g.writeln('}') + } + g.pop_scope() + g.loop_defer_starts.delete_last() + g.pop_loop_label_depth(label_state) +} + +// gen_for_in emits for in output for c. +fn (mut g FlatGen) gen_for_in(node flat.Node) { + defer_start := g.defers.len + label_state := g.push_loop_label_depth(g.take_pending_loop_label(), defer_start) + g.loop_defer_starts << defer_start + defer { + g.loop_defer_starts.delete_last() + g.pop_loop_label_depth(label_state) + } + g.push_scope() + header_count := node.value.int() + val_id := g.a.child(&node, 1) + var_node := if int(val_id) >= 0 { + g.a.child_node(&node, 1) + } else { + g.a.child_node(&node, 0) + } + has_index := int(val_id) >= 0 + idx_binding_name := if has_index { g.a.child_node(&node, 0).value } else { '' } + elem_binding_name := var_node.value + var_name := g.c_loop_local_name(var_node.value) + var_owner := g.tc.cur_scope.insert_with_owner(var_node.value, types.Type(types.int_)) + g.declare_local_pointer_storage(var_owner, false) + body_start := header_count + + if header_count == 4 { + low_id := g.a.child(&node, 2) + high_id := g.a.child(&node, 3) + g.gen_range_for_in(node, g.a.child(&node, 0), low_id, high_id, body_start, + label_state.label) + return + } else if header_count == 3 { + container := g.a.child_node(&node, 2) + if container.kind == .range { + if container.children_count >= 2 { + g.gen_range_for_in(node, g.a.child(&node, 0), g.a.child(container, 0), g.a.child(container, + 1), body_start, label_state.label) + return + } + } else { + container_id := g.a.child(&node, 2) + container_type := g.for_in_container_type(node, container_id) + mut idx_var := '' + if has_index { + if idx_binding_name == '_' { + idx_var = '__for_idx_${g.tmp_count}' + g.tmp_count++ + } else { + idx_var = g.c_loop_local_name(idx_binding_name) + } + } else { + idx_var = '__iter_${var_name}' + } + elem_var := if has_index { + g.c_loop_local_name(g.a.child_node(&node, 1).value) + } else { + var_name + } + mut clean_container_type := types.unwrap_pointer(container_type) + for clean_container_type is types.Alias { + clean_container_type = + types.unwrap_pointer((clean_container_type as types.Alias).base_type) + } + mut map_snapshot_var := '' + mut map_writeback_target := '' + mut map_writeback_key := '' + mut map_writeback_value := '' + mut map_writeback_stmt := '' + mut map_copyback_dirty_var := '' + mut map_copyback_guard := MapLoopCopybackGuard{} + if clean_container_type is types.Map { + c_key := g.map_key_temp_c_type(clean_container_type.key_type) + c_val := g.value_c_type(clean_container_type.value_type) + // A mutable pointer-valued map element already permits mutation through + // the pointee; taking its address again would add a bogus pointer level. + map_value_by_ref := node.op == .amp + && cgen_unalias_type(clean_container_type.value_type) !is types.Pointer + container_str := g.expr_to_string(g.a.child(&node, 2)) + storage_container_type := g.usable_expr_type(g.a.child(&node, 2)) + container_storage_is_pointer := storage_container_type is types.Pointer + original_map_ref := if container_storage_is_pointer { + container_str + } else { + '&${container_str}' + } + iter_var := '__mi_${g.tmp_count}' + g.tmp_count++ + key_var := if has_index { idx_var } else { '__mk_${g.tmp_count}' } + val_var_ := if has_index { elem_var } else { var_name } + use_snapshot := g.for_in_body_contains_delete_call(node, body_start, + g.a.child(&node, 2)) + mut key_ref := '&${key_var}' + key_values := if use_snapshot { + map_snapshot_var = '__for_map_${g.tmp_count}' + g.tmp_count++ + if container_storage_is_pointer { + g.writeln('map ${map_snapshot_var} = map__clone(${container_str});') + } else { + map_src := '__for_map_src_${g.tmp_count}' + g.tmp_count++ + g.writeln('map ${map_src} = ${container_str};') + g.writeln('map ${map_snapshot_var} = map__clone(&${map_src});') + } + '${map_snapshot_var}.key_values' + } else { + access := if container_storage_is_pointer { '->' } else { '.' } + '(${container_str})${access}key_values' + } + g.writeln('for (int ${iter_var} = 0; ${iter_var} < ${key_values}.len; ${iter_var}++) {') + g.indent++ + g.gen_labelled_continue_skip_drops_var(label_state.label) + g.writeln('if (${key_values}.all_deleted && ${key_values}.all_deleted[${iter_var}]) continue;') + key_slot := '${key_values}.keys + ${iter_var} * ${key_values}.key_bytes' + if key_fixed := array_fixed_type(clean_container_type.key_type) { + c_elem, dims := g.fixed_array_decl_parts(key_fixed) + g.writeln('${c_elem} ${key_var}${dims};') + g.writeln('memmove(${key_var}, ${key_slot}, sizeof(${key_var}));') + key_ref = key_var + } else { + g.writeln('${c_key} ${key_var} = *(${c_key}*)(${key_slot});') + if has_index && idx_binding_name != '_' + && clean_container_type.key_type is types.String { + g.writeln('${key_var} = string__clone(${key_var});') + } + } + snapshot_val_slot := '${key_values}.values + ${iter_var} * ${key_values}.value_bytes' + mut val_slot := snapshot_val_slot + mut val_is_fixed_copy := false + if use_snapshot && map_value_by_ref { + val_slot_var := '__for_map_val_${g.tmp_count}' + g.tmp_count++ + g.writeln('void* ${val_slot_var} = map__get_check(${original_map_ref}, &${key_var});') + g.writeln('if (${val_slot_var} == 0) ${val_slot_var} = (void*)(${snapshot_val_slot});') + val_slot = val_slot_var + } + if val_fixed := array_fixed_type(clean_container_type.value_type) { + c_elem, dims := g.fixed_array_decl_parts(val_fixed) + g.writeln('${c_elem} ${val_var_}${dims};') + g.writeln('memmove(${val_var_}, ${val_slot}, sizeof(${val_var_}));') + val_is_fixed_copy = true + } else if map_value_by_ref { + g.writeln('${c_val}* ${val_var_} = (${c_val}*)(${val_slot});') + } else { + g.writeln('${c_val} ${val_var_} = *(${c_val}*)(${val_slot});') + } + if has_index { + key_owner := g.tc.cur_scope.insert_with_owner(idx_binding_name, + clean_container_type.key_type) + g.declare_local_pointer_storage(key_owner, + clean_container_type.key_type is types.Pointer + || c_type_is_pointer_storage(c_key)) + } + val_scope_type := if map_value_by_ref && !val_is_fixed_copy { + types.Type(types.Pointer{ + base_type: clean_container_type.value_type + }) + } else { + clean_container_type.value_type + } + val_owner := g.tc.cur_scope.insert_with_owner(elem_binding_name, val_scope_type) + g.declare_local_pointer_storage(val_owner, val_scope_type is types.Pointer + || (!val_is_fixed_copy && clean_container_type.value_type is types.Pointer) + || c_type_is_pointer_storage(c_val)) + if node.op == .amp && val_is_fixed_copy { + map_writeback_target = if container_storage_is_pointer { + container_str + } else { + '&${container_str}' + } + map_writeback_key = key_var + map_writeback_value = val_var_ + map_writeback_stmt = 'map__set(${map_writeback_target}, &${map_writeback_key}, &${map_writeback_value});' + if use_snapshot { + map_copyback_dirty_var = '__for_map_dirty_${g.tmp_count}' + g.tmp_count++ + copyback_slot := '__for_map_copyback_${g.tmp_count}' + g.tmp_count++ + map_writeback_stmt = 'if (!${map_copyback_dirty_var}) { void* ${copyback_slot} = map__get_check(${map_writeback_target}, &${map_writeback_key}); if (${copyback_slot} != 0) { map__set(${map_writeback_target}, &${map_writeback_key}, &${map_writeback_value}); } }' + map_copyback_guard = MapLoopCopybackGuard{ + map_ref: original_map_ref + key_ref: key_ref + dirty_var: map_copyback_dirty_var + } + } + } + } else if container_type is types.Array { + c_elem := g.value_c_type(container_type.elem_type) + container_node := g.a.nodes[int(container_id)] + mut container_str := g.expr_to_string(container_id) + if container_node.kind == .ident { + if raw := g.local_storage_raw_type(container_node.value) { + clean_raw := raw.trim_space() + if clean_raw.starts_with('?') + && clean_raw[1..].trim_space().starts_with('[]') { + container_str = '(${container_str}).value' + } + } + } + if container_str.starts_with('*') && container_str.contains('->val') { + container_str = container_str[1..] + } + // A call-valued container (e.g. `threads.wait()`, `xs.map(..)`) is not + // idempotent and is referenced multiple times below; bind it to a temp so + // it runs exactly once. + if container_node.kind == .call { + arr_tmp := '__for_arr_${g.tmp_count}' + g.tmp_count++ + g.writeln('Array ${arr_tmp} = ${container_str};') + container_str = arr_tmp + } + g.writeln('for (int ${idx_var} = 0; ${idx_var} < ${container_str}.len; ${idx_var}++) {') + g.indent++ + if node.op == .amp { + g.writeln('${c_elem}* ${elem_var} = (${c_elem}*)array_get(${container_str}, ${idx_var});') + } else { + g.writeln('${c_elem} ${elem_var} = *(${c_elem}*)array_get(${container_str}, ${idx_var});') + } + elem_scope_type := if node.op == .amp { + types.Type(types.Pointer{ + base_type: container_type.elem_type + }) + } else { + container_type.elem_type + } + elem_owner := g.tc.cur_scope.insert_with_owner(elem_binding_name, elem_scope_type) + g.declare_local_pointer_storage(elem_owner, elem_scope_type is types.Pointer + || c_type_is_pointer_storage(c_elem)) + g.declare_ierror_pointer_alias(elem_var, + g.for_in_array_literal_element_needs_ierror_copy(container_node)) + } else if container_type is types.String { + container_str := g.expr_to_string(g.a.child(&node, 2)) + g.writeln('for (int ${idx_var} = 0; ${idx_var} < ${container_str}.len; ${idx_var}++) {') + g.indent++ + g.writeln('u8 ${elem_var} = ((u8*)${container_str}.str)[${idx_var}];') + elem_owner := g.tc.cur_scope.insert_with_owner(elem_binding_name, + types.Type(types.u8_)) + g.declare_local_pointer_storage(elem_owner, false) + } else if container_type is types.ArrayFixed { + af := container_type + c_elem := g.value_c_type(af.elem_type) + arr_len := g.fixed_array_len_value(af) + g.writeln('for (int ${idx_var} = 0; ${idx_var} < ${arr_len}; ${idx_var}++) {') + g.indent++ + if node.op == .amp { + g.write('${c_elem}* ${elem_var} = &') + } else { + g.write('${c_elem} ${elem_var} = ') + } + g.gen_expr(g.a.child(&node, 2)) + g.writeln('[${idx_var}];') + elem_scope_type := if node.op == .amp { + types.Type(types.Pointer{ + base_type: af.elem_type + }) + } else { + af.elem_type + } + elem_owner := g.tc.cur_scope.insert_with_owner(elem_binding_name, elem_scope_type) + g.declare_local_pointer_storage(elem_owner, elem_scope_type is types.Pointer + || c_type_is_pointer_storage(c_elem)) + } else { + g.writeln('for (int ${idx_var} = 0; ${idx_var} < 0; ${idx_var}++) {') + g.indent++ + g.writeln('int ${elem_var} = 0;') + elem_owner := g.tc.cur_scope.insert_with_owner(elem_binding_name, + types.Type(types.int_)) + g.declare_local_pointer_storage(elem_owner, false) + } + if has_index && container_type !is types.Map { + idx_owner := g.tc.cur_scope.insert_with_owner(idx_binding_name, + types.Type(types.int_)) + g.declare_local_pointer_storage(idx_owner, false) + } + if clean_container_type !is types.Map { + g.gen_labelled_continue_skip_drops_var(label_state.label) + } + g.loop_depth++ + if map_copyback_guard.dirty_var.len > 0 { + g.writeln('bool ${map_copyback_guard.dirty_var} = false;') + g.map_loop_copyback_guards << map_copyback_guard + } + if map_writeback_stmt.len > 0 { + g.loop_control_copybacks << LoopControlCopyback{ + loop_depth: g.loop_depth + stmt: map_writeback_stmt + } + } + mut emitted_continue_label := false + for i in body_start .. node.children_count { + emitted_continue_label = + g.gen_loop_body_node(g.a.child(&node, i), label_state.label) + || emitted_continue_label + } + if map_copyback_guard.dirty_var.len > 0 { + g.map_loop_copyback_guards.delete_last() + } + if map_writeback_stmt.len > 0 { + g.writeln(map_writeback_stmt) + g.loop_control_copybacks.delete_last() + } + g.gen_defers_from(defer_start) + if !emitted_continue_label { + g.gen_loop_continue_label(label_state.label) + } + if !node.skip_ownership_drops { + g.gen_loop_iteration_ownership_drops_for_label(label_state.label) + } + g.trim_defers(defer_start) + g.loop_depth-- + g.indent-- + g.writeln('}') + if map_snapshot_var.len > 0 { + g.writeln('map__free(&${map_snapshot_var});') + } + g.pop_scope() + return + } + } else { + g.pop_scope() + return + } + g.indent++ + g.gen_labelled_continue_skip_drops_var(label_state.label) + g.loop_depth++ + mut emitted_continue_label := false + for i in body_start .. node.children_count { + emitted_continue_label = g.gen_loop_body_node(g.a.child(&node, i), label_state.label) + || emitted_continue_label + } + g.gen_defers_from(defer_start) + if !emitted_continue_label { + g.gen_loop_continue_label(label_state.label) + } + if !node.skip_ownership_drops { + g.gen_loop_iteration_ownership_drops_for_label(label_state.label) + } + g.trim_defers(defer_start) + g.loop_depth-- + g.indent-- + g.writeln('}') + g.pop_scope() +} + +fn (g &FlatGen) for_in_container_type(node flat.Node, container_id flat.NodeId) types.Type { + if node.typ.starts_with('map[') { + typ := g.parse_node_type(&node) + if typ is types.Map { + return typ + } + } + return g.usable_expr_type(container_id) +} + +fn (g &FlatGen) for_in_array_literal_element_needs_ierror_copy(container flat.Node) bool { + if container.kind != .array_literal { + return false + } + for i in 0 .. container.children_count { + if g.ierror_pointer_payload_expr_needs_heap_copy(g.a.nodes[int(g.a.child(&container, i))]) { + return true + } + } + return false +} + +fn (mut g FlatGen) gen_range_for_in(node flat.Node, key_id flat.NodeId, low_id flat.NodeId, high_id flat.NodeId, body_start int, label string) { + key := g.a.node(key_id) + if key.kind != .ident || key.value.len == 0 { + g.pop_scope() + return + } + key_name := if key.value == '_' { + g.discard_name(key_id) + } else { + g.c_loop_local_name(key.value) + } + low_type := g.usable_expr_type(low_id) + range_type := if low_type is types.Primitive || low_type is types.ISize + || low_type is types.USize { + low_type + } else { + types.Type(types.int_) + } + ct := g.value_c_type(range_type) + low_name := '__range_low_${g.tmp_count}' + g.tmp_count++ + g.write('${ct} ${low_name} = ') + g.gen_expr(low_id) + g.writeln(';') + high_name := '__range_high_${g.tmp_count}' + g.tmp_count++ + g.write('${ct} ${high_name} = ') + g.gen_expr(high_id) + g.writeln(';') + g.tc.cur_scope.insert(key.value, range_type) + g.writeln('for (${ct} ${key_name} = ${low_name}; ${key_name} < ${high_name}; ${key_name}++) {') + g.indent++ + g.gen_labelled_continue_skip_drops_var(label) + g.loop_depth++ + mut emitted_continue_label := false + for i in body_start .. node.children_count { + emitted_continue_label = g.gen_loop_body_node(g.a.child(&node, i), label) + || emitted_continue_label + } + defer_start := if g.loop_defer_starts.len > 0 { + g.loop_defer_starts.last() + } else { + g.defers.len + } + g.gen_defers_from(defer_start) + if !emitted_continue_label { + g.gen_loop_continue_label(label) + } + if !node.skip_ownership_drops { + g.gen_loop_iteration_ownership_drops_for_label(label) + } + g.trim_defers(defer_start) + g.loop_depth-- + g.indent-- + g.writeln('}') + g.pop_scope() +} + +fn (g &FlatGen) for_in_body_contains_delete_call(node flat.Node, body_start int, container_id flat.NodeId) bool { + container_key := g.for_in_map_storage_key(container_id) + if container_key.len == 0 { + return false + } + for i in body_start .. node.children_count { + if g.node_contains_delete_call(g.a.child(&node, i), container_key) { + return true + } + } + return false +} + +fn (g &FlatGen) node_contains_delete_call(id flat.NodeId, container_key string) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(id)] + if node.kind in [.fn_literal, .lambda_expr, .fn_decl] { + return false + } + if node.kind == .call && node.children_count > 0 { + fn_node := g.a.child_node(&node, 0) + if fn_node.kind == .selector && fn_node.value == 'delete' && fn_node.children_count > 0 { + receiver_id := g.a.child(fn_node, 0) + if g.for_in_map_storage_key(receiver_id) == container_key { + return true + } + } + if fn_node.kind == .ident && fn_node.value in ['map.delete', 'map__delete'] + && node.children_count > 1 { + receiver_id := g.a.child(&node, 1) + if g.for_in_map_storage_key(receiver_id) == container_key { + return true + } + } + } + for i in 0 .. node.children_count { + if g.node_contains_delete_call(g.a.child(&node, i), container_key) { + return true + } + } + return false +} + +fn (mut g FlatGen) gen_map_loop_copyback_dirty_checks(map_ptr_expr string, key_ptr_expr string) { + for guard in g.map_loop_copyback_guards { + g.writeln('if (!${guard.dirty_var} && (${map_ptr_expr}) == (${guard.map_ref}) && (${guard.map_ref})->key_eq_fn(${key_ptr_expr}, ${guard.key_ref})) ${guard.dirty_var} = true;') + } +} + +fn (g &FlatGen) for_in_map_storage_key(id flat.NodeId) string { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return '' + } + node := g.a.nodes[int(id)] + if node.kind in [.paren, .expr_stmt, .cast_expr, .as_expr] && node.children_count > 0 { + return g.for_in_map_storage_key(g.a.child(&node, 0)) + } + if node.kind == .prefix && node.op in [.amp, .mul] && node.children_count > 0 { + return g.for_in_map_storage_key(g.a.child(&node, 0)) + } + return g.expr_key(id) +} + +fn (g &FlatGen) c_loop_local_name(name string) string { + if name.contains('.') { + return g.cname(name.all_after_last('.')) + } + if name.contains('__') { + prefix := name.all_before_last('__') + suffix := name.all_after_last('__') + if suffix == 'index' { + return g.cname(suffix) + } + if g.has_import_alias(prefix) { + return g.cname(suffix) + } + for _, mod_name in g.modules { + short_mod := if mod_name.contains('.') { mod_name.all_after_last('.') } else { mod_name } + if prefix == short_mod { + return g.cname(suffix) + } + } + } + return g.cname(name) +} + +// gen_node_inline emits node inline output for c. +fn (mut g FlatGen) gen_node_inline(id flat.NodeId) { + node := g.a.nodes[int(id)] + match node.kind { + .expr_stmt { + g.gen_expr(g.a.child(&node, 0)) + } + .decl_assign { + lhs_id := g.a.child(&node, 0) + rhs_id := g.a.child(&node, 1) + lhs := g.a.nodes[int(lhs_id)] + v_type := g.tc.resolve_type(rhs_id) + typ := g.tc.c_type(v_type) + g.write('${typ} ') + if lhs.kind == .ident { + g.write(g.c_loop_local_name(lhs.value)) + } else { + g.gen_expr(lhs_id) + } + g.write(' = ') + g.gen_expr(rhs_id) + if lhs.kind == .ident { + owner := g.tc.cur_scope.insert_with_owner(lhs.value, v_type) + g.track_local_pointer_storage_decl(lhs, owner, v_type, typ) + } + } + .assign { + g.gen_expr(g.a.child(&node, 0)) + g.write(' ${g.op_str(node.op)} ') + g.gen_expr(g.a.child(&node, 1)) + } + else {} + } +} diff --git a/vlib/v3/gen/fastc/if.v b/vlib/v3/gen/fastc/if.v new file mode 100644 index 00000000000000..3ebae66a5b7678 --- /dev/null +++ b/vlib/v3/gen/fastc/if.v @@ -0,0 +1,830 @@ +module fastc + +import v3.flat +import v3.types + +struct MultiReturnTailParts { + prefix_count int + values []flat.NodeId +} + +// gen_if emits if output for c. +@[direct_array_access] +fn (mut g FlatGen) gen_if(node flat.Node) { + // Iterate the `else if` chain rather than recursing through gen_if/gen_if_else for + // each link. A lowered match can produce hundreds of chained `if_expr` nodes (one + // per arm); recursing once per arm overflows the stack on big matches. The emitted + // C is identical — only the generation is flattened into a loop. + mut cur := node + for { + if cur.children_count < 2 { + return + } + cond_id := g.a.child(&cur, 0) + if !g.valid_node_id(cond_id) { + return + } + cond := g.a.nodes[int(cond_id)] + if cond.kind == .decl_assign { + g.gen_if_guard(cur, cond) + return + } + if cond.kind != .empty { + g.write('if (') + g.gen_expr(cond_id) + g.writeln(') {') + } else { + g.writeln('{') + } + g.push_scope() + defer_start := g.defers.len + g.indent++ + g.enter_conditional_branch(true) + if cond.kind == .is_expr { + g.smartcast_is_expr(&cond) + } + then_id := g.a.child(&cur, 1) + mut then_scope_drops_consumed := false + mut then_scope_drop_prefix_count := 0 + if g.valid_node_id(then_id) { + then_block := g.a.nodes[int(then_id)] + then_scope_drop_prefix_count = g.block_scope_drop_prefix_count(then_block) + then_scope_drops_consumed = g.gen_branch_block_children(then_block, 1 + + then_scope_drop_prefix_count) + if !g.block_consumes_scope_ownership_drops(then_block) { + then_scope_drops_consumed = true + } + } + g.gen_defers_from(defer_start) + if !cur.skip_ownership_drops && !then_scope_drops_consumed { + g.gen_scope_ownership_drop_count(then_scope_drop_prefix_count) + g.gen_scope_ownership_drops() + } + g.trim_defers(defer_start) + g.leave_conditional_branch() + g.indent-- + g.pop_scope() + // else handling — continue the loop for a plain `else if`, recurse only for the + // rare guard-else (`else if x := ...`). + if cur.children_count <= 2 { + g.writeln('}') + return + } + else_id := g.a.child(&cur, 2) + if !g.valid_node_id(else_id) { + g.writeln('}') + return + } + else_node := g.a.nodes[int(else_id)] + if else_node.kind == .if_expr { + else_cond_id := g.a.child(&else_node, 0) + else_cond_is_guard := if g.valid_node_id(else_cond_id) { + g.a.nodes[int(else_cond_id)].kind == .decl_assign + } else { + false + } + if else_cond_is_guard { + g.writeln('} else {') + g.push_scope() + g.indent++ + g.gen_if(else_node) + g.indent-- + g.pop_scope() + g.writeln('}') + return + } + g.write('} else ') + cur = else_node + continue + } else if else_node.kind == .block { + g.writeln('} else {') + g.push_scope() + else_defer_start := g.defers.len + g.indent++ + g.enter_conditional_branch(true) + mut else_scope_drops_consumed := false + else_scope_drop_prefix_count := g.block_scope_drop_prefix_count(else_node) + else_scope_drops_consumed = g.gen_branch_block_children(else_node, 1 + + else_scope_drop_prefix_count) + if !g.block_consumes_scope_ownership_drops(else_node) { + else_scope_drops_consumed = true + } + g.gen_defers_from(else_defer_start) + if !cur.skip_ownership_drops && !else_scope_drops_consumed { + g.gen_scope_ownership_drop_count(else_scope_drop_prefix_count) + g.gen_scope_ownership_drops() + } + g.trim_defers(else_defer_start) + g.leave_conditional_branch() + g.indent-- + g.pop_scope() + g.writeln('}') + return + } else { + g.writeln('}') + return + } + } +} + +fn (g &FlatGen) is_transformed_return_stmt(id flat.NodeId) bool { + if !g.valid_node_id(id) { + return false + } + node := g.a.nodes[int(id)] + if node.kind != .return_stmt { + return false + } + if _ := transformed_return_source_id(node.value) { + return true + } + return false +} + +fn (mut g FlatGen) gen_branch_block_children(block flat.Node, tail_scope_drop_count int) bool { + for i in 0 .. block.children_count { + child_id := g.a.child(&block, i) + if !g.valid_node_id(child_id) { + continue + } + if i == block.children_count - 1 + && g.gen_transformed_tail_return_with_scope_drop_count(child_id, tail_scope_drop_count) { + return true + } + g.gen_node(child_id) + } + return false +} + +fn (mut g FlatGen) gen_transformed_tail_return_with_scope_drop_count(id flat.NodeId, count int) bool { + if !g.is_transformed_return_stmt(id) { + return false + } + old_pending := g.pending_return_scope_drops.clone() + g.pending_return_scope_drops = g.take_scope_ownership_drop_count(count) + g.gen_node(id) + g.pending_return_scope_drops = old_pending + return true +} + +fn (mut g FlatGen) take_scope_ownership_drop_count(count int) []types.OwnershipDropEntry { + mut entries := []types.OwnershipDropEntry{} + for _ in 0 .. count { + for entry in g.take_scope_ownership_drops() { + entries << entry + } + } + return entries +} + +// smartcast_is_expr supports smartcast is expr handling for FlatGen. +fn (mut g FlatGen) smartcast_is_expr(cond &flat.Node) { + expr_id := g.a.child(cond, 0) + expr_node := g.a.nodes[int(expr_id)] + if expr_node.kind == .ident { + sum_type := g.tc.resolve_type(expr_id) + clean_sum0 := types.unwrap_pointer(sum_type) + mut clean_sum := clean_sum0 + if clean_sum0 is types.Alias { + clean_sum = clean_sum0.base_type + } + if clean_sum is types.SumType { + variant_name := g.resolve_variant(clean_sum.name, cond.value) + variant_type := g.tc.parse_type(variant_name) + if variant_type is types.Void { + return + } + variant_ct := g.tc.c_type(variant_type) + field_name := g.sum_field_name(variant_name) + is_ptr_variant := g.variant_references_sum(variant_name, clean_sum.name) + var_name := g.cname(expr_node.value) + tmp := g.tmp_name() + if is_ptr_variant { + g.writeln('${variant_ct} ${tmp} = *${var_name}.${field_name};') + } else { + g.writeln('${variant_ct} ${tmp} = ${var_name}.${field_name};') + } + g.writeln('${variant_ct} ${var_name} = ${tmp};') + g.tc.cur_scope.insert(expr_node.value, variant_type) + } + } +} + +// expr_key supports expr key handling for FlatGen. +fn (g &FlatGen) expr_key(id flat.NodeId) string { + node := g.a.nodes[int(id)] + if node.kind == .ident { + return node.value + } + if node.kind == .selector && node.children_count > 0 { + base_key := g.expr_key(g.a.child(&node, 0)) + if base_key.len > 0 { + return '${base_key}.${node.value}' + } + } + return '' +} + +// gen_if_guard emits if guard output for c. +fn (mut g FlatGen) gen_if_guard(node flat.Node, cond flat.Node) { + if cond.children_count < 2 { + return + } + lhs_id := g.a.child(&cond, 0) + rhs_id := g.a.child(&cond, 1) + if !g.valid_node_id(lhs_id) || !g.valid_node_id(rhs_id) { + return + } + lhs := g.a.nodes[int(lhs_id)] + lhs_ids := g.if_guard_lhs_ids(cond) + rhs := g.a.nodes[int(rhs_id)] + mut rhs_type := g.optional_source_type_for_expr(rhs_id, g.tc.resolve_type(rhs_id)) + mut rhs_needs_deref := false + if rhs.kind == .ident && g.current_param_is_mut(rhs.value) { + if param_type := g.current_param_type(rhs.value) { + if param_type is types.Pointer { + base_type := optional_result_unalias_type(param_type.base_type) + if base_type is types.OptionType || base_type is types.ResultType { + rhs_type = param_type.base_type + rhs_needs_deref = true + } + } + } + } + var_name := g.cname(lhs.value) + tmp := g.tmp_name() + defer_start := g.defers.len + if rhs.kind == .index { + base_id := g.a.child(rhs, 0) + base_type := g.usable_expr_type(base_id) + if base_type is types.Map { + c_val_type := g.tc.c_type(base_type.value_type) + c_key_type := g.map_key_temp_c_type(base_type.key_type) + g.write('void* ${tmp} = map__get_check(&') + g.gen_expr(base_id) + g.write(', &(${c_key_type}[]){') + g.gen_expr(g.a.child(rhs, 1)) + g.writeln('});') + g.writeln('if (${tmp} != NULL) {') + g.push_scope() + g.indent++ + g.writeln('${c_val_type} ${var_name} = *(${c_val_type}*)${tmp};') + g.tc.cur_scope.insert(lhs.value, base_type.value_type) + } else { + opt_ct := g.optional_type_name_for_expr(rhs_id, rhs_type) + val_ct, val_type := g.optional_value_info(rhs_type, opt_ct) + g.write('${opt_ct} ${tmp} = ') + if rhs_needs_deref { + g.write('*(') + g.gen_expr(rhs_id) + g.write(')') + } else { + g.gen_expr(rhs_id) + } + g.writeln(';') + g.writeln('if (${tmp}.ok) {') + g.push_scope() + g.indent++ + g.gen_if_guard_value_bindings(lhs_ids, val_type, val_ct, tmp) + } + } else { + opt_ct := g.optional_type_name_for_expr(rhs_id, rhs_type) + val_ct, val_type := g.optional_value_info(rhs_type, opt_ct) + g.write('${opt_ct} ${tmp} = ') + if rhs_needs_deref { + g.write('*(') + g.gen_expr(rhs_id) + g.write(')') + } else { + g.gen_expr(rhs_id) + } + g.writeln(';') + g.writeln('if (${tmp}.ok) {') + g.push_scope() + g.indent++ + g.gen_if_guard_value_bindings(lhs_ids, val_type, val_ct, tmp) + } + then_id := g.a.child(&node, 1) + mut then_scope_drops_consumed := false + mut then_scope_drop_prefix_count := 0 + if g.valid_node_id(then_id) { + then_block := g.a.nodes[int(then_id)] + then_scope_drop_prefix_count = g.block_scope_drop_prefix_count(then_block) + g.enter_conditional_branch(true) + then_scope_drops_consumed = g.gen_branch_block_children(then_block, 2 + + then_scope_drop_prefix_count) + g.leave_conditional_branch() + if !g.block_consumes_scope_ownership_drops(then_block) { + then_scope_drops_consumed = true + } + } + g.gen_defers_from(defer_start) + // The checker records the then block scope, then the outer guard-binding scope. + if !then_scope_drops_consumed { + g.gen_scope_ownership_drop_count(then_scope_drop_prefix_count) + g.gen_scope_ownership_drops() + g.gen_scope_ownership_drops() + } + g.trim_defers(defer_start) + g.indent-- + g.pop_scope() + g.gen_if_else(node) +} + +fn (g &FlatGen) if_guard_lhs_ids(cond flat.Node) []flat.NodeId { + if cond.children_count < 2 { + return []flat.NodeId{} + } + mut lhs_ids := []flat.NodeId{cap: int(cond.children_count) - 1} + lhs_ids << g.a.child(&cond, 0) + for i in 2 .. cond.children_count { + lhs_ids << g.a.child(&cond, i) + } + return lhs_ids +} + +fn (mut g FlatGen) gen_if_guard_value_bindings(lhs_ids []flat.NodeId, val_type types.Type, val_ct string, tmp string) { + if val_type is types.MultiReturn && lhs_ids.len > 1 { + for i, lhs_id in lhs_ids { + if i >= val_type.types.len || !g.valid_node_id(lhs_id) { + break + } + lhs := g.a.nodes[int(lhs_id)] + if lhs.kind != .ident || lhs.value.len == 0 || lhs.value == '_' { + continue + } + field_type := val_type.types[i] + lhs_name := g.cname(lhs.value) + if fixed := array_fixed_type(field_type) { + c_elem, dims := g.fixed_array_decl_parts(fixed) + g.writeln('${c_elem} ${lhs_name}${dims};') + g.writeln('memmove(${lhs_name}, ${tmp}.value.arg${i}, sizeof(${lhs_name}));') + owner := g.tc.cur_scope.insert_with_owner(lhs.value, field_type) + g.track_local_pointer_storage_decl(lhs, owner, field_type, '') + continue + } + field_ct := g.value_c_type(field_type) + g.writeln('${field_ct} ${lhs_name} = ${tmp}.value.arg${i};') + owner := g.tc.cur_scope.insert_with_owner(lhs.value, field_type) + g.track_local_pointer_storage_decl(lhs, owner, field_type, field_ct) + } + return + } + if lhs_ids.len == 0 || !g.valid_node_id(lhs_ids[0]) { + return + } + lhs := g.a.nodes[int(lhs_ids[0])] + if lhs.kind != .ident || lhs.value.len == 0 || lhs.value == '_' { + return + } + lhs_name := g.cname(lhs.value) + if fixed := array_fixed_type(val_type) { + c_elem, dims := g.fixed_array_decl_parts(fixed) + g.writeln('${c_elem} ${lhs_name}${dims};') + g.writeln('memmove(${lhs_name}, ${tmp}.value, sizeof(${lhs_name}));') + owner := g.tc.cur_scope.insert_with_owner(lhs.value, val_type) + g.track_local_pointer_storage_decl(lhs, owner, val_type, '') + return + } + g.writeln('${val_ct} ${lhs_name} = ${tmp}.value;') + owner := g.tc.cur_scope.insert_with_owner(lhs.value, val_type) + g.track_local_pointer_storage_decl(lhs, owner, val_type, val_ct) +} + +// gen_if_else emits if else output for c. +fn (mut g FlatGen) gen_if_else(node flat.Node) { + if node.children_count > 2 { + else_id := g.a.child(&node, 2) + if !g.valid_node_id(else_id) { + g.writeln('}') + return + } + else_node := g.a.nodes[int(else_id)] + if else_node.kind == .if_expr { + else_cond_id := g.a.child(&else_node, 0) + else_cond_is_guard := if g.valid_node_id(else_cond_id) { + g.a.nodes[int(else_cond_id)].kind == .decl_assign + } else { + false + } + if else_cond_is_guard { + g.writeln('} else {') + g.push_scope() + g.indent++ + g.gen_if(else_node) + g.indent-- + g.pop_scope() + g.writeln('}') + } else { + g.write('} else ') + g.gen_if(else_node) + } + } else if else_node.kind == .block { + g.writeln('} else {') + g.push_scope() + defer_start := g.defers.len + g.indent++ + g.enter_conditional_branch(true) + mut else_scope_drops_consumed := false + else_scope_drop_prefix_count := g.block_scope_drop_prefix_count(else_node) + else_scope_drops_consumed = g.gen_branch_block_children(else_node, 1 + + else_scope_drop_prefix_count) + if !g.block_consumes_scope_ownership_drops(else_node) { + else_scope_drops_consumed = true + } + g.gen_defers_from(defer_start) + if !else_scope_drops_consumed { + g.gen_scope_ownership_drop_count(else_scope_drop_prefix_count) + g.gen_scope_ownership_drops() + } + g.trim_defers(defer_start) + g.leave_conditional_branch() + g.indent-- + g.pop_scope() + g.writeln('}') + } else { + g.writeln('}') + } + } else { + g.writeln('}') + } +} + +// gen_if_expr emits if expr output for c. +fn (mut g FlatGen) gen_if_expr(node flat.Node) { + then_block := g.a.child_node(&node, 1) + mut needs_stmt_expr := g.expected_expr_type is types.MultiReturn + || then_block.children_count > 1 + if !needs_stmt_expr && node.children_count > 2 { + else_node := g.a.child_node(&node, 2) + if else_node.kind == .block && else_node.children_count > 1 { + needs_stmt_expr = true + } else if else_node.kind == .if_expr { + needs_stmt_expr = true + } + } + if needs_stmt_expr { + g.gen_if_expr_stmt(node) + return + } + g.write('(') + g.gen_expr(g.a.child(&node, 0)) + g.write(' ? ') + if then_block.children_count > 0 { + last := g.a.child_node(then_block, then_block.children_count - 1) + if last.kind == .expr_stmt { + g.gen_expr(g.a.child(last, 0)) + } else { + g.gen_expr(g.a.child(then_block, then_block.children_count - 1)) + } + } + g.write(' : ') + if node.children_count > 2 { + else_node := g.a.child_node(&node, 2) + if else_node.kind == .if_expr { + g.gen_if_expr(*else_node) + } else if else_node.kind == .block { + if else_node.children_count > 0 { + last := g.a.child_node(else_node, else_node.children_count - 1) + if last.kind == .expr_stmt { + g.gen_expr(g.a.child(last, 0)) + } else { + g.gen_expr(g.a.child(else_node, else_node.children_count - 1)) + } + } else { + g.write('0') + } + } + } else { + g.write('0') + } + g.write(')') +} + +// gen_if_expr_block emits if expr block output for c. +fn (mut g FlatGen) gen_if_expr_block(block &flat.Node, ret_type types.Type) { + g.enter_conditional_branch(false) + if ret_type is types.MultiReturn { + if g.gen_if_expr_multi_return_block(block, ret_type) { + g.gen_scope_ownership_drops() + g.leave_conditional_branch() + return + } + } + for i in 0 .. block.children_count { + child_id := g.a.child(block, i) + child := g.a.nodes[int(child_id)] + if i == block.children_count - 1 { + if child.kind == .expr_stmt { + inner_id := g.a.child(child, 0) + if g.if_expr_tail_has_no_value(inner_id) { + g.gen_node(child_id) + } else { + g.write('_ifexpr = ') + g.gen_expr(inner_id) + g.writeln(';') + } + } else if child.kind == .if_expr { + g.write('_ifexpr = ') + g.gen_if_expr(child) + g.writeln(';') + } else if g.is_expr_kind(child.kind) { + if g.if_expr_tail_has_no_value(child_id) { + g.gen_node(child_id) + } else { + g.write('_ifexpr = ') + g.gen_expr(child_id) + g.writeln(';') + } + } else { + g.gen_node(child_id) + } + } else { + g.gen_node(child_id) + } + } + g.gen_scope_ownership_drops() + g.leave_conditional_branch() +} + +fn (mut g FlatGen) if_expr_tail_has_no_value(id flat.NodeId) bool { + if !g.valid_node_id(id) { + return false + } + if g.is_noreturn_call(id) { + return true + } + return g.tc.resolve_type(id) is types.Void +} + +fn (g &FlatGen) multi_return_tail_parts(block &flat.Node, count int) ?MultiReturnTailParts { + if block.kind != .block || count <= 0 || block.children_count == 0 { + return none + } + last_id := g.a.child(block, block.children_count - 1) + last := g.a.nodes[int(last_id)] + if last.kind == .block { + if nested := g.multi_return_tail_parts(&last, count) { + if nested.prefix_count == 0 { + return MultiReturnTailParts{ + prefix_count: int(block.children_count) - 1 + values: nested.values.clone() + } + } + } + } + mut values := []flat.NodeId{} + for i := int(block.children_count) - 1; i >= 0; i-- { + child_id := g.a.child(block, i) + child := g.a.nodes[int(child_id)] + if child.kind != .expr_stmt || child.children_count == 0 { + break + } + for j := int(child.children_count) - 1; j >= 0; j-- { + values.prepend(g.a.child(&child, j)) + if values.len == count { + break + } + } + if values.len == count { + return MultiReturnTailParts{ + prefix_count: i + values: values.clone() + } + } + } + return none +} + +fn (mut g FlatGen) gen_if_expr_multi_return_block(block &flat.Node, ret_type types.MultiReturn) bool { + parts := g.multi_return_tail_parts(block, ret_type.types.len) or { return false } + for i in 0 .. parts.prefix_count { + g.gen_node(g.a.child(block, i)) + } + ct := g.value_c_type(types.Type(ret_type)) + if g.multi_return_types_have_fixed_array(ret_type.types) { + tmp := g.gen_multi_return_tail_temp(ct, ret_type.types, parts.values) + g.writeln('_ifexpr = ${tmp};') + return true + } + g.write('_ifexpr = (${ct}){') + for i, value_id in parts.values { + if i > 0 { + g.write(', ') + } + g.write('.arg${i} = ') + g.gen_expr_with_expected_type(value_id, ret_type.types[i]) + } + g.writeln('};') + return true +} + +fn (mut g FlatGen) gen_multi_return_block_expr(block &flat.Node, ret_type types.MultiReturn) bool { + parts := g.multi_return_tail_parts(block, ret_type.types.len) or { return false } + ct := g.value_c_type(types.Type(ret_type)) + if g.multi_return_types_have_fixed_array(ret_type.types) { + g.write('({') + for i in 0 .. parts.prefix_count { + g.gen_node(g.a.child(block, i)) + } + tmp := g.gen_multi_return_tail_temp(ct, ret_type.types, parts.values) + g.write('${tmp};})') + return true + } + if parts.prefix_count == 0 { + g.write('(${ct}){') + for i, value_id in parts.values { + if i > 0 { + g.write(', ') + } + g.write('.arg${i} = ') + g.gen_expr_with_expected_type(value_id, ret_type.types[i]) + } + g.write('}') + return true + } + g.write('({') + for i in 0 .. parts.prefix_count { + g.gen_node(g.a.child(block, i)) + } + g.write('${ct} _multi_ret = (${ct}){') + for i, value_id in parts.values { + if i > 0 { + g.write(', ') + } + g.write('.arg${i} = ') + g.gen_expr_with_expected_type(value_id, ret_type.types[i]) + } + g.write('}; _multi_ret;})') + return true +} + +fn (mut g FlatGen) gen_multi_return_tail_temp(ct string, ret_types []types.Type, values []flat.NodeId) string { + tmp := g.tmp_name() + g.writeln('${ct} ${tmp};') + for i, value_id in values { + field := '${tmp}.arg${i}' + if i < ret_types.len { + if fixed := array_fixed_type(ret_types[i]) { + g.gen_fixed_array_copy_from_node(field, value_id, fixed) + continue + } + g.write('${field} = ') + g.gen_expr_with_expected_type(value_id, ret_types[i]) + g.writeln(';') + continue + } + g.write('${field} = ') + g.gen_expr(value_id) + g.writeln(';') + } + return tmp +} + +// is_expr_kind reports whether is expr kind applies in c. +fn (g &FlatGen) is_expr_kind(kind flat.NodeKind) bool { + return match kind { + .int_literal, .float_literal, .bool_literal, .char_literal, .string_literal, + .string_interp, .ident, .infix, .prefix, .postfix, .paren, .call, .selector, .index, + .if_expr, .struct_init, .field_init, .array_literal, .array_init, .map_init, .fn_literal, + .or_expr, .cast_expr, .as_expr, .enum_val, .assoc, .range, .nil_literal, .none_expr, + .spawn_expr, .lock_expr, .lambda_expr, .sizeof_expr, .typeof_expr, .dump_expr, + .offsetof_expr, .is_expr, .in_expr { + true + } + else { + false + } + } +} + +// seed_scope_from_decl converts seed scope from decl data for c. +fn (mut g FlatGen) seed_scope_from_decl(node flat.Node) { + if node.kind != .decl_assign || node.children_count < 2 { + return + } + lhs := g.a.child_node(&node, 0) + if lhs.kind != .ident || lhs.value.len == 0 { + return + } + // The declared type annotation is authoritative; resolving the RHS can + // disagree for lowered temps (`__or_val := ` resolving to the + // element sum type) and would poison every later use of the binding. + if node.typ.len > 0 { + typ := g.parse_node_type(&node) + if !decl_annotation_is_unusable(typ, node.typ) { + g.tc.cur_scope.insert(lhs.value, typ) + return + } + } + rhs_id := g.a.child(&node, 1) + g.tc.cur_scope.insert(lhs.value, g.tc.resolve_type(rhs_id)) +} + +// if_expr_block_tail_type supports if expr block tail type handling for FlatGen. +fn (mut g FlatGen) if_expr_block_tail_type(block &flat.Node) types.Type { + if block.children_count == 0 { + return types.Type(types.void_) + } + g.push_scope() + for i in 0 .. block.children_count - 1 { + g.seed_scope_from_decl(*g.a.child_node(block, i)) + } + last := g.a.child_node(block, block.children_count - 1) + ret := if last.kind == .expr_stmt { + g.usable_expr_type(g.a.child(last, 0)) + } else { + g.usable_expr_type(g.a.child(block, block.children_count - 1)) + } + g.pop_scope() + return ret +} + +// if_expr_type supports if expr type handling for FlatGen. +fn (mut g FlatGen) if_expr_type(node &flat.Node) types.Type { + if node.children_count < 2 { + return types.Type(types.void_) + } + then_block := g.a.child_node(node, 1) + mut ret_type := g.if_expr_block_tail_type(then_block) + if ret_type is types.Primitive && node.children_count > 2 { + else_node := g.a.child_node(node, 2) + if else_node.kind == .block && else_node.children_count > 0 { + et := g.if_expr_block_tail_type(else_node) + if et !is types.Primitive { + ret_type = et + } + } else if else_node.kind == .if_expr && else_node.children_count > 2 { + inner_else := g.a.child_node(else_node, 2) + if inner_else.kind == .block && inner_else.children_count > 0 { + et := g.if_expr_block_tail_type(inner_else) + if et !is types.Primitive { + ret_type = et + } + } + } + } + return ret_type +} + +// gen_if_expr_stmt emits if expr stmt output for c. +fn (mut g FlatGen) gen_if_expr_stmt(node flat.Node) { + ret_type := if node.typ.len > 0 { + g.parse_node_type(&node) + } else if g.expected_expr_type !is types.Void { + g.expected_expr_type + } else { + g.if_expr_type(&node) + } + then_block := g.a.child_node(&node, 1) + ct := g.value_c_type(ret_type) + g.writeln('({${ct} _ifexpr;') + g.write('if (') + g.gen_expr(g.a.child(&node, 0)) + g.writeln(') {') + g.gen_if_expr_block(then_block, ret_type) + g.write('} else ') + if node.children_count > 2 { + else_node := g.a.child_node(&node, 2) + if else_node.kind == .if_expr { + g.gen_if_expr_else_if(*else_node, ret_type) + } else if else_node.kind == .block { + g.writeln('{') + g.gen_if_expr_block(else_node, ret_type) + g.writeln('}') + } + } else { + g.writeln('{ _ifexpr = (${ct}){0}; }') + } + g.write('_ifexpr;})') +} + +// gen_if_expr_else_if emits if expr else if output for c. The `else if` chain is +// iterated rather than recursed: a lowered match-expression can chain hundreds of +// `if_expr` nodes (one per arm), and recursing once per arm — each frame copying a +// `flat.Node` and a `Type` by value — overflows the stack. The emitted C is identical. +fn (mut g FlatGen) gen_if_expr_else_if(node flat.Node, ret_type types.Type) { + mut cur := node + for { + then_block := g.a.child_node(&cur, 1) + g.write('if (') + g.gen_expr(g.a.child(&cur, 0)) + g.writeln(') {') + g.gen_if_expr_block(then_block, ret_type) + g.write('} else ') + if cur.children_count > 2 { + else_node := g.a.child_node(&cur, 2) + if else_node.kind == .if_expr { + cur = *else_node + continue + } else if else_node.kind == .block { + g.writeln('{') + g.gen_if_expr_block(else_node, ret_type) + g.writeln('}') + } + return + } + g.writeln('{ _ifexpr = (${g.value_c_type(ret_type)}){0}; }') + return + } +} diff --git a/vlib/v3/gen/fastc/interface.v b/vlib/v3/gen/fastc/interface.v new file mode 100644 index 00000000000000..bd1370d8d37312 --- /dev/null +++ b/vlib/v3/gen/fastc/interface.v @@ -0,0 +1,2456 @@ +module fastc + +import strings +import v3.gen.fastc.naming +import v3.types +import v3.flat + +// emit_sum_type emits emit sum type output for c. +fn (mut g FlatGen) emit_sum_type(name string) { + variants := g.tc.sum_types[name] + g.writeln('struct ${g.cname(name)} {') + g.writeln('\tint typ;') + g.writeln('\tbool _pointer_variant_is_owned;') + g.writeln('\tunion {') + for v in variants { + variant_type := select_receive_unalias_type(g.tc.parse_canonical_type(v)) + ct := if variant_type is types.Pointer { + g.value_c_type(variant_type.base_type) + } else { + g.value_c_type(variant_type) + } + field := g.sum_field_name(v) + g.writeln('\t\t${ct}* ${field};') + } + g.writeln('\t};') + g.writeln('};') + g.writeln('') +} + +// sum_type_contains_struct reports whether sum type contains struct applies in c. +fn (g &FlatGen) sum_type_contains_struct(sum_name string, struct_name string) bool { + if sum_name in g.tc.sum_types { + for v in g.tc.sum_types[sum_name] { + if v == struct_name { + return true + } + } + } + return false +} + +// sum_type_index supports sum type index handling for FlatGen. +fn (g &FlatGen) sum_type_index(sum_name string, variant string) int { + mut resolved_sum := sum_name + if resolved_sum !in g.tc.sum_types && !resolved_sum.contains('.') { + // A bare sum name from a foreign-module lowering (auto-stringify + // expansions keep the declaring module's spelling): the precomputed + // short-name table maps it to the declared qualified sum. + short_resolved := g.resolve_sum_name(resolved_sum) + if short_resolved in g.tc.sum_types { + resolved_sum = short_resolved + } + } + if resolved_sum !in g.tc.sum_types && resolved_sum.contains('.') { + // Resolve an import-aliased sum name (`tast.Value` for module `sub.tast`) + // exactly first. Use suffix and bare-name fallbacks only when unique. + aliased_sum := g.tc.qualify_name(resolved_sum) + if aliased_sum in g.tc.sum_types { + resolved_sum = aliased_sum + } else { + suffix := '.' + resolved_sum + mut suffix_match := '' + mut suffix_ambiguous := false + for key, _ in g.tc.sum_types { + if key.ends_with(suffix) { + if suffix_match.len > 0 && suffix_match != key { + suffix_ambiguous = true + break + } + suffix_match = key + } + } + if !suffix_ambiguous && suffix_match.len > 0 { + resolved_sum = suffix_match + } else if suffix_match.len == 0 { + short_sum := resolved_sum.all_after_last('.') + mut short_match := '' + mut ambiguous := false + for key, _ in g.tc.sum_types { + if key == short_sum || key.all_after_last('.') == short_sum { + if short_match.len > 0 && short_match != key { + ambiguous = true + break + } + short_match = key + } + } + if !ambiguous && short_match.len > 0 { + resolved_sum = short_match + } + } + } + } + return g.sum_type_index_resolved(resolved_sum, variant) +} + +fn (g &FlatGen) sum_type_index_resolved(sum_name string, variant string) int { + if sum_name in g.tc.sum_types { + for i, v in g.tc.sum_types[sum_name] { + if v == variant { + return i + 1 + } + } + resolved_variant := g.tc.qualify_name(variant) + if resolved_variant != variant { + for i, v in g.tc.sum_types[sum_name] { + if v == resolved_variant { + return i + 1 + } + } + } + for i, v in g.tc.sum_types[sum_name] { + if v.all_after_last('.') == variant { + return i + 1 + } + } + // Container variants written through an import alias (`map[string]ast.Value` + // vs the registered `map[string]toml.ast.Value`) match on the + // module-stripped spelling only when that spelling is unambiguous. + if variant.contains('.') || variant.contains('[') { + short_variant := short_module_type_text(variant) + mut short_match := 0 + for i, v in g.tc.sum_types[sum_name] { + if short_module_type_text(v) == short_variant { + if short_match != 0 { + return 0 + } + short_match = i + 1 + } + } + return short_match + } + } + return 0 +} + +fn (mut g FlatGen) interface_tmp(prefix string) string { + name := '_${prefix}_${g.tmp_count}' + g.tmp_count++ + return name +} + +fn (g &FlatGen) interface_str_lit(text string) string { + return 'v3_c_lit("${c_escape(text)}", ${text.len})' +} + +fn (g &FlatGen) interface_str_plus(left string, right string) string { + return 'string__plus(${left}, ${right})' +} + +fn (mut g FlatGen) interface_dispatch_def_string(iface_name string, cn string, method string) string { + saved_sb := g.sb + saved_line_start := g.line_start + g.sb = strings.new_builder(2048) + g.line_start = true + g.gen_interface_dispatch_with_fallback(iface_name, cn, method, false) + body := g.sb.str() + unsafe { g.sb.free() } + g.sb = saved_sb + g.line_start = saved_line_start + return body +} + +fn (g &FlatGen) interface_dispatch_receiver_expr(concrete string, concrete_params []types.Type, wants_ptr bool) string { + cct := g.interface_concrete_storage_c_type(concrete) + if concrete_params.len == 0 { + return if wants_ptr { '(${cct}*)i->_object' } else { '*(${cct}*)i->_object' } + } + concrete_type := g.interface_concrete_type(concrete) + expected_type := concrete_params[0] + if path := g.embedded_receiver_path_for_expected(concrete_type, expected_type) { + base := '(${cct}*)i->_object' + mut access := base + mut access_is_ptr := true + for field in path { + op := if access_is_ptr { '->' } else { '.' } + access = '(${access})${op}${c_field_name(field.name)}' + access_is_ptr = field.typ is types.Pointer + } + if access_is_ptr == wants_ptr { + return access + } + return if wants_ptr { '&(${access})' } else { '*(${access})' } + } + return if wants_ptr { '(${cct}*)i->_object' } else { '*(${cct}*)i->_object' } +} + +fn (g &FlatGen) interface_arg_conversion_expr(name string, source_type types.Type, target_type types.Type) ?string { + if source_type is types.Pointer || target_type is types.Pointer { + return none + } + source_iface := g.interface_receiver_name(source_type) + target_iface := g.interface_receiver_name(target_type) + if source_iface.len == 0 || target_iface.len == 0 || source_iface == target_iface { + return none + } + mappings := g.interface_receiver_type_id_mappings(source_iface, target_iface) + if mappings.len == 0 { + return none + } + target_ct := g.tc.c_type(types.unwrap_pointer(target_type)) + mut expr := '(${target_ct}){._typ = ' + for mapping in mappings { + expr += '(${name}._typ == ${mapping.source_id} ? ${mapping.target_id} : ' + } + expr += '0' + for _ in mappings { + expr += ')' + } + expr += ', ._object = ${name}._object' + for field in g.tc.interface_fields[target_iface] or { []types.StructField{} } { + if interface_field_type_contains_self_by_value(field.typ, target_iface) { + continue + } + field_ct := g.tc.c_type(field.typ) + expr += ', .${g.cname(field.name)} = ' + for mapping in mappings { + field_expr := g.interface_impl_field_access_expr('${name}._object', mapping.impl, + field.name) + expr += '(${name}._typ == ${mapping.source_id} ? ${field_expr} : ' + } + expr += '(${field_ct}){0}' + for _ in mappings { + expr += ')' + } + } + expr += '}' + return expr +} + +fn (g &FlatGen) interface_method_signature_key(iface_name string, method string) ?string { + base, _, is_generic := g.shared_generic_app_parts(iface_name) + metadata_name := if is_generic { base } else { iface_name } + key := '${metadata_name}.${method}' + if key in g.tc.fn_ret_types || key in g.tc.fn_param_types { + return key + } + for embed in g.tc.interface_embeds[metadata_name] or { []string{} } { + if found := g.interface_method_signature_key(embed, method) { + return found + } + } + return none +} + +fn (g &FlatGen) interface_dispatch_target_is_emitted(concrete_key string) bool { + if !g.has_used_fn_filter() { + return true + } + if source_file := g.tc.fn_type_files[concrete_key] { + // Cache-header declarations are intentionally absent from the program's + // used-function set; their implementations live in linked module objects. + if source_file.ends_with('.vh') { + return true + } + } + if g.used_interface_dispatch_key(concrete_key) { + return true + } + if g.interface_dispatch_method_is_required(concrete_key) { + return true + } + receiver_name := concrete_key.all_before_last('.') + if receiver_name.contains('.') { + return false + } + method := concrete_key.all_after_last('.') + short_key := '${receiver_name.all_after_last('.')}.${method}' + return short_key != concrete_key + && g.interface_dispatch_target_short_name_is_unambiguous(short_key, method) + && g.used_interface_dispatch_key(short_key) +} + +fn (g &FlatGen) interface_dispatch_method_is_required(concrete_key string) bool { + return concrete_key in g.interface_dispatch_required +} + +fn (mut g FlatGen) precompute_required_interface_dispatch_methods() { + g.interface_dispatch_required.clear() + for iface_name, impls in g.iface_impls { + methods := g.interfaces[iface_name] or { g.tc.interface_abstract_method_names(iface_name) } + for method in methods { + if !g.should_emit_interface_dispatch(iface_name, method) { + continue + } + for concrete in impls { + concrete_method := '${concrete}.${method}' + if g.interface_dispatch_target_decl_is_used(concrete_method) { + g.interface_dispatch_required[concrete_method] = true + g.interface_dispatch_required[g.cname(concrete_method)] = true + } + expected := g.tc.concrete_method_signature_key(concrete, method) or { + concrete_method + } + if g.interface_dispatch_target_decl_is_used(expected) { + g.interface_dispatch_required[expected] = true + g.interface_dispatch_required[g.cname(expected)] = true + } + } + } + } +} + +fn (g &FlatGen) interface_dispatch_target_decl_is_used(name string) bool { + if g.used_interface_dispatch_key(name) { + return true + } + if source_file := g.tc.fn_type_files[name] { + return source_file.ends_with('.vh') + } + return false +} + +fn (g &FlatGen) interface_dispatch_target_short_name_is_unambiguous(short_name string, method string) bool { + mut seen := map[string]bool{} + mut matches := 0 + for _, impls in g.iface_impls { + for concrete in impls { + if seen[concrete] { + continue + } + seen[concrete] = true + if '${concrete.all_after_last('.')}.${method}' == short_name { + matches++ + if matches > 1 { + return false + } + } + } + } + return matches == 1 +} + +// variant_references_sum supports variant references sum handling for FlatGen. +fn (g &FlatGen) variant_references_sum(variant string, sum_name string) bool { + _ = variant + _ = sum_name + return true +} + +// variant_refs_sum_inner supports variant refs sum inner handling for FlatGen. +fn (g &FlatGen) variant_refs_sum_inner(variant string, sum_name string, mut visited map[string]bool) bool { + normalized_variant := g.normalize_variant_name(variant) + if normalized_variant == sum_name + || normalized_variant.all_after_last('.') == sum_name.all_after_last('.') { + return true + } + if normalized_variant in visited { + return false + } + visited[normalized_variant] = true + mut lookup := normalized_variant + if lookup !in g.tc.structs && !lookup.contains('.') && sum_name.contains('.') { + qualified := '${sum_name.all_before_last('.')}.${lookup}' + if qualified in g.tc.structs { + lookup = qualified + } + } + if lookup !in g.tc.structs && !lookup.contains('.') { + for struct_name, _ in g.tc.structs { + if struct_name.all_after_last('.') == lookup { + lookup = struct_name + break + } + } + } + if lookup in g.tc.structs { + for f in g.tc.structs[lookup] { + if g.type_references_sum(f.typ, sum_name, mut visited) { + return true + } + } + } + return false +} + +// normalize_variant_name transforms normalize variant name data for c. +fn (g &FlatGen) normalize_variant_name(name string) string { + _ = g + mut res := name + if res.starts_with('&') { + res = res[1..] + } + if res.starts_with('ptr') && res.len > 3 { + res = res[3..] + } + if res.contains('__') && !res.contains('.') { + res = res.replace('__', '.') + } + return res +} + +// type_references_sum returns type references sum data for FlatGen. +fn (g &FlatGen) type_references_sum(typ types.Type, sum_name string, mut visited map[string]bool) bool { + resolved_sum := g.resolve_sum_name(sum_name) + clean := types.unwrap_pointer(typ) + if clean is types.Struct && g.resolve_sum_name(clean.name) == resolved_sum { + return true + } + if clean is types.SumType && g.resolve_sum_name(clean.name) == resolved_sum { + return true + } + if clean is types.SumType { + return true + } + if clean is types.Struct { + if g.variant_refs_sum_inner(clean.name, resolved_sum, mut visited) { + return true + } + } + if clean is types.Array { + return g.type_references_sum(clean.elem_type, resolved_sum, mut visited) + } + return false +} + +// resolve_sum_name resolves resolve sum name information for c. +fn (g &FlatGen) resolve_sum_name(sum_name string) string { + if resolved := g.sum_name_lookup[sum_name] { + return resolved + } + if sum_name.contains('.') { + if resolved := g.sum_name_lookup[c_short_name_view(sum_name)] { + return resolved + } + } + return sum_name +} + +fn (mut g FlatGen) precompute_sum_name_lookup() { + g.sum_name_lookup = map[string]string{} + for name, _ in g.tc.sum_types { + g.sum_name_lookup[name] = name + short := c_short_name_view(name) + if short.len > 0 && short !in g.sum_name_lookup { + g.sum_name_lookup[short] = name + } + } +} + +// resolve_variant resolves resolve variant information for c. +fn (g &FlatGen) resolve_variant(sum_name string, variant string) string { + resolved_sum := g.resolve_sum_name(sum_name) + normalized_variant := g.normalize_variant_name(variant) + if resolved_sum in g.tc.sum_types { + for v in g.tc.sum_types[resolved_sum] { + if v == normalized_variant { + return normalized_variant + } + } + for v in g.tc.sum_types[resolved_sum] { + if v.all_after_last('.') == normalized_variant { + return v + } + } + } + return normalized_variant +} + +// sum_field_name supports sum field name handling for FlatGen. +fn (g &FlatGen) sum_field_name(variant string) string { + if variant.starts_with('&') { + return g.sum_field_name(variant[1..]) + } + if variant.starts_with('?') { + return '_Option_${g.cname(variant[1..])}' + } + if variant.starts_with('!') { + return '_Result_${g.cname(variant[1..])}' + } + if variant.starts_with('ptr') && variant.len > 3 && variant[3..].contains('.') { + return g.sum_field_name(variant[3..]) + } + if variant.starts_with('ptr') && variant.len > 3 && variant[3..].contains('__') { + return g.sum_field_name(variant[3..].replace('__', '.')) + } + if variant.starts_with('[]') { + return '_Array_${g.cname(variant[2..])}' + } + if variant.starts_with('map[') { + return '_Map_${g.cname(variant[4..].replace(']', '_'))}' + } + if variant.starts_with('fn(') || variant.starts_with('fn (') { + return '_Fn_${callback_stable_key_hash(sum_fn_variant_key(variant))}' + } + if sum_variant_needs_type_name_field(variant) { + return '_${naming.type_name_part(variant)}' + } + return match variant { + 'int' { '_int' } + 'i8' { '_i8' } + 'i16' { '_i16' } + 'i64' { '_i64' } + 'u8', 'byte' { '_u8' } + 'u16' { '_u16' } + 'u32' { '_u32' } + 'u64' { '_u64' } + 'f32' { '_f32' } + 'f64' { '_f64' } + 'bool' { '_bool' } + 'string' { '_string' } + else { g.cname(variant) } + } +} + +fn sum_variant_needs_type_name_field(variant string) bool { + return variant.contains('(') || variant.contains(')') || variant.contains(' ') +} + +fn sum_fn_variant_key(variant string) string { + clean := variant.trim_space() + open := clean.index('(') or { return clean.replace(' ', '') } + close := clean.last_index(')') or { return clean.replace(' ', '') } + params := clean[open + 1..close] + ret := clean[close + 1..].trim_space().replace(' ', '') + mut parts := []string{} + for part in sum_fn_split_top_level_commas(params) { + ptyp := sum_fn_param_type(part) + if ptyp.len > 0 { + parts << ptyp + } + } + return 'fn(${parts.join(',')})${ret}' +} + +fn sum_fn_split_top_level_commas(params string) []string { + mut parts := []string{} + mut depth := 0 + mut start := 0 + for i := 0; i < params.len; i++ { + ch := params[i] + if ch == `(` || ch == `[` || ch == `{` { + depth++ + } else if ch == `)` || ch == `]` || ch == `}` { + if depth > 0 { + depth-- + } + } else if ch == `,` && depth == 0 { + parts << params[start..i].trim_space() + start = i + 1 + } + } + parts << params[start..].trim_space() + return parts +} + +fn sum_fn_param_type(param string) string { + clean := param.trim_space() + if clean.len == 0 { + return '' + } + if clean.starts_with('fn(') || clean.starts_with('fn (') { + return sum_fn_variant_key(clean) + } + space := clean.index(' ') or { return clean } + first := clean[..space] + if sum_fn_is_ident(first) && first !in ['fn', 'mut', 'shared'] { + return clean[space + 1..].trim_space().replace(' ', '') + } + if first in ['mut', 'shared'] { + rest := clean[space + 1..].trim_space() + second_space := rest.index(' ') or { return clean.replace(' ', '') } + second := rest[..second_space] + if sum_fn_is_ident(second) { + return '${first}${rest[second_space + 1..].trim_space().replace(' ', '')}' + } + } + return clean.replace(' ', '') +} + +fn sum_fn_is_ident(s string) bool { + if s.len == 0 { + return false + } + first := s[0] + if !((first >= `a` && first <= `z`) || (first >= `A` && first <= `Z`) || first == `_`) { + return false + } + for i := 1; i < s.len; i++ { + ch := s[i] + if !((ch >= `a` && ch <= `z`) || (ch >= `A` && ch <= `Z`) + || (ch >= `0` && ch <= `9`) || ch == `_`) { + return false + } + } + return true +} + +// register_interface_strings updates register interface strings state for c. +fn (mut g FlatGen) register_interface_strings() { + for iface_name, methods in g.interfaces { + cn := g.cname(iface_name) + for method in methods { + g.intern_string('interface method ${cn}.${method} not implemented') + } + } +} + +// collect_interface_impls discovers, for every interface, the concrete struct +// types that implement it (structural typing), and assigns each a stable nonzero +// type id. The id is stored in the boxed interface value's `_typ` field and is +// what the generated method-dispatch switch matches on. +fn (mut g FlatGen) collect_interface_impls() { + g.ierror_method_emit_names = map[string]bool{} + g.collect_interface_boxed_types_for_dispatch() + mut boxed_concrete_types := map[string][]string{} + for key, _ in g.interface_boxed_types { + parts := key.split('::') + if parts.len != 2 { + continue + } + mut concrete := parts[1] + is_container := concrete.starts_with('[]') || concrete.starts_with('map[') + if !is_container && concrete !in g.tc.structs && concrete !in g.tc.type_aliases { + qualified := g.tc.qualify_name(concrete) + if qualified !in g.tc.structs && qualified !in g.tc.type_aliases { + continue + } + concrete = qualified + } + mut concrete_types := boxed_concrete_types[parts[0]] or { []string{} } + if concrete !in concrete_types { + concrete_types << concrete + boxed_concrete_types[parts[0]] = concrete_types + } + } + mut iface_names := []string{} + for name, _ in g.interfaces { + iface_names << name + } + iface_names.sort() + for iface in iface_names { + mut impls := []string{} + mut base_impls := []string{} + if g.is_ierror_type_name(iface) { + impls = g.tc.ierror_impl_names() + } else { + // Structs plus type aliases with their own implementing methods; ids + // must come from tc.interface_impl_names so the transform's `is` + // checks agree with the dispatch ids assigned here. + impls = g.tc.interface_impl_names(iface) + base_impls = impls.clone() + mut concrete_types := boxed_concrete_types[iface] or { []string{} } + concrete_types.sort() + for concrete in concrete_types { + if concrete !in impls { + impls << concrete + } + } + } + g.iface_impls[iface] = impls + type_ids := if g.is_ierror_type_name(iface) { + types.stable_interface_type_ids(impls) + } else if impls.len > base_impls.len { + types.stable_interface_type_ids_preserving_prefix(base_impls, impls) + } else { + g.tc.interface_type_ids(iface) + } + for concrete in impls { + g.iface_type_ids['${iface}::${concrete}'] = type_ids[concrete] + } + if g.is_ierror_type_name(iface) { + g.collect_ierror_method_emit_names(impls) + } + } +} + +fn (g &FlatGen) interface_dispatch_return_type(decl_key string, concrete_key string) types.Type { + decl_type := g.tc.fn_ret_types[decl_key] or { types.Type(types.void_) } + wrapped_base := interface_dispatch_wrapped_base_type(decl_type) or { types.Type(types.void_) } + needs_concrete := g.type_contains_generic_placeholder(decl_type) + || wrapped_base is types.Unknown || wrapped_base is types.Void + if !needs_concrete || concrete_key.len == 0 { + return decl_type + } + return g.tc.fn_ret_types[concrete_key] or { decl_type } +} + +fn (g &FlatGen) interface_dispatch_param_types(decl_key string, concrete_key string) []types.Type { + decl_params := g.tc.fn_param_types[decl_key] or { []types.Type{} } + concrete_params := if concrete_key.len > 0 { + g.tc.fn_param_types[concrete_key] or { []types.Type{} } + } else { + []types.Type{} + } + mut decl_has_generic := false + mut generic_params_use_pointer_abi := true + for i, param in decl_params { + if g.type_contains_generic_placeholder(param) { + decl_has_generic = true + // A generic interface has one runtime box for every specialization. + // Generic parameters passed through pointers therefore need one stable + // erased ABI instead of inheriting the first discovered implementer's + // concrete pointer type. `void *` is lossless for every such parameter; + // by-value generic parameters still require a concrete ABI below. + if i > 0 && param !is types.Pointer { + generic_params_use_pointer_abi = false + } + } + } + if concrete_params.len > 0 && ((decl_has_generic && !generic_params_use_pointer_abi) + || decl_params.len == 0 || decl_params.len != concrete_params.len) { + return concrete_params + } + return decl_params +} + +fn (mut g FlatGen) collect_ierror_method_emit_names(impls []string) { + for concrete in impls { + for method in ['msg', 'code'] { + call := g.ierror_method_call(concrete, method) or { continue } + g.add_ierror_method_emit_name(call.method_name) + } + } +} + +fn (mut g FlatGen) add_ierror_method_emit_name(name string) { + if name.len == 0 { + return + } + g.ierror_method_emit_names[name] = true + lowered := g.cname(name) + if lowered != name { + g.ierror_method_emit_names[lowered] = true + } +} + +// iface_type_id returns the 1-based dispatch id assigned to `concrete` for +// interface `iface`, or 0 if `concrete` does not implement `iface`. +fn (g &FlatGen) iface_type_id(iface string, concrete string) int { + return g.iface_type_ids['${iface}::${concrete}'] or { 0 } +} + +fn (g &FlatGen) iface_type_id_for_pattern(iface string, pattern string) int { + pattern_key := if pattern.contains('.') + && types.is_builtin_type_name(pattern.all_after_last('.')) { + pattern.all_after_last('.') + } else { + pattern + } + id := g.iface_type_id(iface, pattern_key) + if id != 0 { + return id + } + qpattern := g.tc.qualify_name(pattern_key) + if qpattern != pattern_key { + qid := g.iface_type_id(iface, qpattern) + if qid != 0 { + return qid + } + } + if pattern_key.contains('.') { + return 0 + } + mut found := 0 + for concrete in g.iface_impls[iface] or { []string{} } { + if concrete.all_after_last('.') != pattern_key { + continue + } + cid := g.iface_type_id(iface, concrete) + if cid == 0 { + continue + } + if found != 0 { + return 0 + } + found = cid + } + return found +} + +fn (g &FlatGen) ierror_interface_name() ?string { + if 'IError' in g.interfaces { + return 'IError' + } + if 'builtin.IError' in g.interfaces { + return 'builtin.IError' + } + for name, _ in g.interfaces { + if g.is_ierror_type_name(name) { + return name + } + } + return none +} + +fn (g &FlatGen) is_ierror_type_name(name string) bool { + _ = g + return name == 'IError' || name == 'builtin.IError' +} + +fn (mut g FlatGen) gen_ierror_dynamic_method_expr(id flat.NodeId, typ types.Type, method string) { + tmp := g.tmp_count + g.tmp_count++ + g.write('({ IError _ierror_${method}${tmp} = ') + if typ is types.Pointer { + g.write('*') + } + g.gen_expr(id) + g.write('; IError__${method}(&_ierror_${method}${tmp}); })') +} + +fn (g &FlatGen) ierror_direct_method_name(concrete string, method string) ?string { + direct := '${concrete}.${method}' + if g.ierror_method_signature_matches(direct, concrete, method) { + return direct + } + qconcrete := g.tc.qualify_name(concrete) + if qconcrete != concrete { + qdirect := '${qconcrete}.${method}' + if g.ierror_method_signature_matches(qdirect, qconcrete, method) { + return qdirect + } + } + return none +} + +fn (g &FlatGen) ierror_method_signature_matches(name string, concrete string, method string) bool { + params := g.tc.fn_param_types[name] or { return false } + if params.len != 1 { + return false + } + ret := g.tc.fn_ret_types[name] or { return false } + if !g.ierror_method_return_matches(method, ret) { + return false + } + receiver := g.ierror_clean_type(params[0]) + expected := g.ierror_clean_type(g.tc.parse_canonical_type(concrete)) + return g.type_names_match(receiver, expected) +} + +fn (g &FlatGen) ierror_method_return_matches(method string, ret types.Type) bool { + clean := if ret is types.Alias { ret.base_type } else { ret } + return match method { + 'msg' { clean is types.String } + 'code' { clean.name() == 'int' } + else { false } + } +} + +fn (g &FlatGen) ierror_clean_type(typ types.Type) types.Type { + clean0 := types.unwrap_pointer(typ) + return if clean0 is types.Alias { clean0.base_type } else { clean0 } +} + +struct IErrorMethodCall { + method_name string + path []types.StructField +} + +fn (g &FlatGen) ierror_method_call(concrete string, method string) ?IErrorMethodCall { + if direct := g.ierror_direct_method_name(concrete, method) { + return IErrorMethodCall{ + method_name: direct + } + } + mut seen := map[string]bool{} + return g.ierror_promoted_method_call(concrete, method, mut seen) +} + +fn (g &FlatGen) ierror_promoted_method_call(concrete string, method string, mut seen map[string]bool) ?IErrorMethodCall { + if concrete in seen { + return none + } + seen[concrete] = true + for field in g.struct_embedded_fields(concrete) { + embedded_name := g.embedded_field_type_name(field) + if embedded_name.len == 0 { + continue + } + if direct := g.ierror_direct_method_name(embedded_name, method) { + return IErrorMethodCall{ + method_name: direct + path: [field] + } + } + if nested := g.ierror_promoted_method_call(embedded_name, method, mut seen) { + mut path := [field] + path << nested.path + return IErrorMethodCall{ + method_name: nested.method_name + path: path + } + } + } + return none +} + +fn (g &FlatGen) ierror_method_receiver_expr(concrete string, path []types.StructField, recv_is_ptr bool) string { + concrete_ct := g.tc.c_type(g.tc.parse_canonical_type(concrete)) + object := '(${concrete_ct}*)i->_object' + if path.len == 0 { + return if recv_is_ptr { object } else { '*${object}' } + } + mut access := object + mut access_is_ptr := true + for field in path { + op := if access_is_ptr { '->' } else { '.' } + access = '(${access})${op}${c_field_name(field.name)}' + access_is_ptr = field.typ is types.Pointer + } + if access_is_ptr == recv_is_ptr { + return access + } + return if recv_is_ptr { '&(${access})' } else { '*(${access})' } +} + +fn (g &FlatGen) type_can_box_as_ierror(concrete string) bool { + return g.tc.named_type_compatible_with_ierror(concrete) +} + +fn (g &FlatGen) ierror_concrete_name(t types.Type) ?string { + clean := g.ierror_payload_concrete_type(t) + if clean !is types.Struct { + return none + } + iface := g.ierror_interface_name() or { return none } + name := (clean as types.Struct).name + scoped_name := g.tc.resolve_ierror_payload_name(name) + if scoped_name != name && g.iface_type_id(iface, scoped_name) != 0 { + return scoped_name + } + if g.iface_type_id(iface, name) != 0 { + return name + } + qname := g.tc.qualify_name(name) + if qname != name && g.iface_type_id(iface, qname) != 0 { + return qname + } + return none +} + +fn (g &FlatGen) ierror_payload_concrete_type(t types.Type) types.Type { + mut clean := t + mut seen := map[string]bool{} + for { + clean = types.unwrap_pointer(clean) + if clean is types.Alias { + if seen[clean.name] { + return clean + } + seen[clean.name] = true + clean = clean.base_type + continue + } + return clean + } + return clean +} + +fn (g &FlatGen) ierror_type_id_for_pattern(pattern string) int { + iface := g.ierror_interface_name() or { return 0 } + return g.iface_type_id_for_pattern(iface, pattern) +} + +fn (g &FlatGen) should_emit_ierror_method(name string, qname string) bool { + if name in g.ierror_method_emit_names || qname in g.ierror_method_emit_names { + return true + } + return g.cname(qname) in g.ierror_method_emit_names +} + +fn (mut g FlatGen) gen_ierror_from_expr(id flat.NodeId) bool { + s := g.ierror_from_expr_string(id) or { return false } + g.write(s) + return true +} + +fn (mut g FlatGen) ierror_none_literal_string() string { + type_id := g.ierror_type_id_for_pattern('None__') + empty_sid := g.intern_string('') + return '(IError){._typ = ${type_id}, ._object = memdup(&(None__){0}, sizeof(None__)), ._object_is_boxed = true, .message = _str_${empty_sid}, .code = 0}' +} + +fn (mut g FlatGen) ierror_from_expr_string(id flat.NodeId) ?string { + node := g.a.nodes[int(id)] + mut actual := g.usable_expr_type(id) + if node.kind == .struct_init && node.value.len > 0 { + // A concrete error returned from a result function can carry the surrounding + // result type as its node annotation. The literal name still identifies the + // concrete IError implementation that must be boxed. + actual = g.tc.parse_type(node.value) + } else if node.kind == .ident { + if param_type := g.current_param_type(node.value) { + actual = param_type + } else if param_type := g.cur_param_types[node.value] { + actual = param_type + } + } + return g.ierror_from_expr_string_with_type(id, actual) +} + +fn (mut g FlatGen) ierror_from_expr_string_with_type(id flat.NodeId, actual types.Type) ?string { + node := g.a.nodes[int(id)] + concrete := g.ierror_concrete_name(actual) or { return none } + iface := g.ierror_interface_name() or { return none } + type_id := g.iface_type_id(iface, concrete) + if type_id == 0 { + return none + } + expr := g.expr_to_string(id) + concrete_ct := g.tc.c_type(g.tc.parse_canonical_type(concrete)) + pointer_object_is_owned := actual is types.Pointer + && g.ierror_pointer_payload_creates_owned_object(node) + object := if actual is types.Pointer { + if g.ierror_pointer_payload_needs_heap_copy(node) + || g.ierror_pointer_payload_alias_needs_heap_copy(node) { + 'memdup(${expr}, sizeof(${concrete_ct}))' + } else { + expr + } + } else { + 'memdup((${concrete_ct}[]){${expr}}, sizeof(${concrete_ct}))' + } + empty_sid := g.intern_string('') + boxed := pointer_object_is_owned || object.starts_with('memdup(') + return '(IError){._typ = ${type_id}, ._object = ${object}, ._object_is_boxed = ${boxed}, .message = _str_${empty_sid}, .code = 0}' +} + +// ierror_pointer_payload_creates_owned_object reports pointer expressions whose C +// lowering allocates independent storage. Result/interface destruction must release +// these objects even though ordinary pointer-backed interface values are borrowed. +fn (g &FlatGen) ierror_pointer_payload_creates_owned_object(node flat.Node) bool { + clean := g.ierror_pointer_payload_unwrapped_node(node) + if clean.kind != .prefix || clean.op != .amp || clean.children_count == 0 { + return false + } + child := g.ierror_pointer_payload_unwrapped_node(g.a.nodes[int(g.a.child(&clean, 0))]) + return child.kind in [.struct_init, .assoc] +} + +fn (g &FlatGen) ierror_pointer_payload_needs_heap_copy(node flat.Node) bool { + root := g.ierror_pointer_payload_address_root(node, false) or { return false } + return g.ierror_pointer_payload_root_needs_heap_copy(root) +} + +fn (g &FlatGen) ierror_stack_subobject_address_needs_heap_copy(node flat.Node) bool { + root := g.ierror_pointer_payload_address_root(node, true) or { return false } + return g.ierror_pointer_payload_root_needs_heap_copy(root) +} + +fn (g &FlatGen) ierror_pointer_payload_expr_needs_heap_copy(node flat.Node) bool { + clean := g.ierror_pointer_payload_unwrapped_node(node) + if g.ierror_pointer_payload_needs_heap_copy(clean) { + return true + } + if g.ierror_array_get_pointer_alias_needs_copy(clean) { + return true + } + if clean.kind == .ident { + return g.ierror_pointer_alias_needs_copy(clean.value) + } + return false +} + +fn (g &FlatGen) ierror_pointer_payload_alias_needs_heap_copy(node flat.Node) bool { + clean := g.ierror_pointer_payload_unwrapped_node(node) + return clean.kind == .ident && g.cur_scope_has_local_name(clean.value) + && g.ierror_pointer_alias_needs_copy(clean.value) +} + +fn (g &FlatGen) ierror_array_get_pointer_alias_needs_copy(node flat.Node) bool { + base_name := g.ierror_array_get_base_name(node) or { return false } + return g.ierror_pointer_alias_needs_copy(base_name) +} + +fn (g &FlatGen) ierror_array_get_base_name(node flat.Node) ?string { + mut clean := g.ierror_pointer_payload_unwrapped_node(node) + if clean.kind == .prefix && clean.op == .mul && clean.children_count > 0 { + clean = g.ierror_pointer_payload_unwrapped_node(g.a.nodes[int(g.a.child(&clean, 0))]) + } + if clean.kind != .call || clean.children_count < 2 { + return none + } + target := g.call_target_name(g.a.child(&clean, 0)) + if target !in ['array_get', 'array__get'] { + return none + } + base := g.ierror_pointer_payload_unwrapped_node(g.a.nodes[int(g.a.child(&clean, 1))]) + if base.kind == .ident && base.value.len > 0 { + return base.value + } + return none +} + +fn (g &FlatGen) ierror_pointer_alias_name_from_addr(node flat.Node) ?string { + clean := g.ierror_pointer_payload_unwrapped_node(node) + if clean.kind == .ident && clean.value.len > 0 { + return clean.value + } + if clean.kind == .prefix && clean.op == .amp && clean.children_count > 0 { + child := g.ierror_pointer_payload_unwrapped_node(g.a.nodes[int(g.a.child(&clean, 0))]) + if child.kind == .ident && child.value.len > 0 { + return child.value + } + } + return none +} + +fn (g &FlatGen) ierror_pointer_payload_address_root(node flat.Node, require_subobject bool) ?flat.Node { + clean_node := g.ierror_pointer_payload_unwrapped_node(node) + if clean_node.kind != .prefix || clean_node.op != .amp || clean_node.children_count == 0 { + return none + } + mut child_id := g.a.child(&clean_node, 0) + mut saw_subobject := false + for { + child := g.ierror_pointer_payload_unwrapped_node(g.a.nodes[int(child_id)]) + if child.kind !in [.selector, .index] || child.children_count == 0 { + break + } + saw_subobject = true + child_id = g.a.child(&child, 0) + } + if require_subobject && !saw_subobject { + return none + } + root := g.ierror_pointer_payload_unwrapped_node(g.a.nodes[int(child_id)]) + if root.kind != .ident { + return none + } + return root +} + +fn (g &FlatGen) ierror_pointer_payload_unwrapped_node(node flat.Node) flat.Node { + mut cur := node + for cur.kind in [.paren, .expr_stmt, .cast_expr, .as_expr] && cur.children_count > 0 { + cur = g.a.nodes[int(g.a.child(&cur, 0))] + } + return cur +} + +fn (g &FlatGen) ierror_pointer_payload_root_needs_heap_copy(root flat.Node) bool { + if param_type := g.current_param_type(root.value) { + return param_type !is types.Pointer + } + if param_type := g.cur_param_types[root.value] { + return param_type !is types.Pointer + } + if local_type := g.tc.cur_scope.lookup(root.value) { + if local_type is types.Pointer && g.ierror_local_pointer_is_owned(root.value) { + return true + } + return local_type !is types.Pointer + } + return false +} + +// iface_type_id_for_concrete resolves the dispatch id for a boxed concrete +// type, including alias implementers. The checker normalizes alias-typed +// values to their base type (`p := Puppy{}` annotates `p` as `Dog`), so when +// the direct lookup fails, fall back to the alias's base type, and from a base +// type to the single alias implementer that resolves to it (if unambiguous). +fn (g &FlatGen) iface_type_id_for_concrete(iface string, concrete types.Type) int { + concrete_name := concrete.name() + mut id := g.iface_type_id(iface, concrete_name) + if id != 0 { + return id + } + if concrete is types.Array { + id = g.iface_type_id(iface, 'array') + if id != 0 { + return id + } + } + if concrete is types.Map { + id = g.iface_type_id(iface, 'map') + if id != 0 { + return id + } + } + if concrete_name.starts_with('main.') || concrete_name.starts_with('builtin.') { + id = g.iface_type_id(iface, concrete_name.all_after_last('.')) + if id != 0 { + return id + } + } + if concrete is types.Alias { + id = g.iface_type_id(iface, concrete.base_type.name()) + if id != 0 { + return id + } + } + mut alias_id := 0 + mut matches := 0 + for impl in g.iface_impls[iface] or { []string{} } { + target := g.tc.type_aliases[impl] or { continue } + if target == concrete_name || g.tc.qualify_name(target) == g.tc.qualify_name(concrete_name) { + alias_id = g.iface_type_id(iface, impl) + matches++ + } + } + if matches == 1 { + return alias_id + } + return 0 +} + +fn (mut g FlatGen) gen_interface_value_expr(id flat.NodeId, expected types.Type) bool { + iface_type := cgen_unalias_type(expected) + if iface_type !is types.Interface { + return false + } + iface := iface_type as types.Interface + if g.is_ierror_type_name(iface.name) { + if s := g.ierror_from_expr_string(id) { + g.write(s) + return true + } + } + node := g.a.nodes[int(id)] + mut actual := g.interface_source_type(id) + if node.kind == .ident { + if param_type := g.current_param_type(node.value) { + // A `mut p &T` parameter uses `&&T` storage, but reading `p` yields + // the semantic `&T` value that is being boxed into the interface. + actual = if g.current_param_is_mut_pointer(node.value) && param_type is types.Pointer { + param_type.base_type + } else { + param_type + } + } + } + actual_clean := if actual is types.Pointer { actual.base_type } else { actual } + actual_base := cgen_unalias_type(actual_clean) + actual_name := actual_base.name() + if actual_base is types.Interface || actual_name == iface.name + || (actual_name.starts_with('main.') && actual_name['main.'.len..] == iface.name) + || (iface.name.starts_with('main.') && iface.name['main.'.len..] == actual_name) + || g.interface_unknown_qualified_name_matches(actual_name, iface.name) { + return false + } + concrete_name := actual_name + if concrete_name.len == 0 { + return false + } + // A specialized generic interface return can retain its placeholder's + // struct-shaped annotation while already using the concrete interface ABI. + // Equal language type names mean no concrete-to-interface boxing is needed. + if concrete_name == iface.name { + return false + } + type_id := g.iface_type_id_for_concrete(iface.name, actual_clean) + ct := g.tc.c_type(iface) + fields := g.interface_cached_fields(iface.name) + concrete_ct := g.tc.c_type(actual_base) + if concrete_ct == ct { + return false + } + if fields.len > 0 { + tmp := g.tmp_count + g.tmp_count++ + if actual is types.Pointer { + g.write('({ ${concrete_ct}* _iface${tmp} = ') + g.gen_expr(id) + g.write('; (${ct}){._typ = ${type_id}, ._object = _iface${tmp}, ._object_is_boxed = false') + for field in fields { + field_ct := g.tc.c_type(field.typ) + field_name := g.cname(field.name) + g.write(', .${field_name} = _iface${tmp} ? _iface${tmp}->${field_name} : (${field_ct}){0}') + } + g.write('}; })') + } else { + g.write('({ ${concrete_ct} _iface${tmp} = ') + g.gen_expr(id) + g.write('; (${ct}){._typ = ${type_id}, ._object = memdup(&_iface${tmp}, sizeof(${concrete_ct})), ._object_is_boxed = true') + for field in fields { + g.write(', .${g.cname(field.name)} = _iface${tmp}.${g.cname(field.name)}') + } + g.write('}; })') + } + return true + } + g.write('(${ct}){._typ = ${type_id}, ._object = ') + if actual is types.Pointer { + g.gen_expr(id) + g.write(', ._object_is_boxed = false') + } else if node.kind in [.ident, .selector, .index] { + g.write('memdup(&') + g.gen_expr(id) + g.write(', sizeof(${concrete_ct})), ._object_is_boxed = true') + } else { + g.write('memdup((${concrete_ct}[]){') + g.gen_expr(id) + g.write('}, sizeof(${concrete_ct})), ._object_is_boxed = true') + } + g.write('}') + return true +} + +fn (mut g FlatGen) gen_interface_pointer_value_expr(id flat.NodeId, expected types.Type) bool { + ptr_type := if expected is types.Pointer { expected } else { return false } + mut iface_type := cgen_unalias_type(ptr_type.base_type) + if iface_type is types.Alias { + iface_type = cgen_unalias_type(iface_type.base_type) + } + if iface_type !is types.Interface { + return false + } + node := g.a.node(id) + if node.kind == .nil_literal { + return false + } + actual := cgen_unalias_type(g.interface_source_type(id)) + if actual is types.Pointer && cgen_unalias_type(actual.base_type) is types.Interface { + return false + } + iface_value := g.interface_value_to_string(id, iface_type) + if iface_value.len == 0 { + return false + } + ct := g.tc.c_type(iface_type) + tmp := g.tmp_count + g.tmp_count++ + g.write('({ ${ct} _iface_ptr${tmp} = ${iface_value}; (${ct}*)memdup(&_iface_ptr${tmp}, sizeof(${ct})); })') + return true +} + +fn (mut g FlatGen) interface_source_type(id flat.NodeId) types.Type { + node := g.a.node(id) + if node.kind == .ident && g.current_param_type(node.value) == none + && !g.cur_scope_has_local_name(node.value) { + current_global_name := qualify_name_in_module(g.tc.cur_module, node.value) + if typ := g.global_types[current_global_name] { + return typ + } + const_name := g.const_ref_name_from_node(node) + if const_name.len > 0 { + if typ := g.tc.const_types[const_name] { + return typ + } + } + } + return g.usable_expr_type(id) +} + +fn (g &FlatGen) interface_unknown_qualified_name_matches(actual_name string, iface_name string) bool { + if !actual_name.contains('.') + || actual_name.all_after_last('.') != iface_name.all_after_last('.') { + return false + } + actual_known := actual_name in g.tc.structs || actual_name in g.tc.interface_names + || actual_name in g.tc.type_aliases || actual_name in g.tc.sum_types + if actual_known { + return false + } + return iface_name in g.tc.interface_names + || g.tc.qualify_name(iface_name) in g.tc.interface_names +} + +// is_interface_type_name reports whether is interface type name applies in c. +fn (g &FlatGen) is_interface_type_name(name string) bool { + mut clean := name + base, _, is_generic := g.shared_generic_app_parts(clean) + if is_generic { + clean = base + } + return clean in g.interfaces || g.tc.qualify_name(clean) in g.interfaces +} + +// has_ierror_interface reports whether has ierror interface applies in c. +fn (g &FlatGen) has_ierror_interface() bool { + for name, _ in g.interfaces { + if g.is_ierror_type_name(name) { + return true + } + } + return false +} + +// interface_init_typ_id computes the `_typ` dispatch id for a boxed interface +// literal by recovering the concrete type from its `_object` field. +fn (g &FlatGen) interface_init_typ_id(node flat.Node) ?int { + iface := if node.value in g.interfaces { + node.value + } else { + g.tc.qualify_name(node.value) + } + for i in 0 .. node.children_count { + field := g.a.child_node(&node, i) + if field.kind == .field_init && field.value == '_object' && field.children_count > 0 { + obj_id := g.a.child(field, 0) + obj_node := g.a.node(obj_id) + mut obj_type := g.tc.resolve_type(obj_id) + if obj_node.kind == .ident && g.current_param_is_mut_pointer(obj_node.value) + && obj_type is types.Pointer { + obj_type = obj_type.base_type + } + concrete := types.unwrap_pointer(obj_type) + id := g.iface_type_id_for_concrete(iface, concrete) + if id != 0 { + return id + } + return none + } + } + return none +} + +// interface_init_object_is_boxed reports whether an interface literal's `_object` +// field owns a heap copy produced by memdup rather than borrowing a concrete pointer. +fn (g &FlatGen) interface_init_object_is_boxed(node flat.Node) bool { + for i in 0 .. node.children_count { + field := g.a.child_node(&node, i) + if field.kind == .field_init && field.value == '_object' && field.children_count > 0 { + return g.interface_object_expr_is_boxed(g.a.child(field, 0)) + } + } + return false +} + +fn (g &FlatGen) interface_object_expr_is_boxed(id flat.NodeId) bool { + if int(id) < 0 || int(id) >= g.a.nodes.len { + return false + } + node := g.a.nodes[int(id)] + if node.kind == .call && node.children_count > 0 { + callee := g.a.child_node(&node, 0) + return callee.kind == .ident && callee.value == 'memdup' + } + if node.kind in [.cast_expr, .paren, .expr_stmt] && node.children_count > 0 { + return g.interface_object_expr_is_boxed(g.a.child(&node, 0)) + } + return false +} + +// interface_method_stubs emits a dispatch function for every abstract interface +// method: it switches on the boxed value's `_typ` and forwards to the concrete +// implementation, passing `_object` as the receiver. Interfaces with no known +// implementers (and the special builtin `IError`) fall back to a panic stub. +fn (mut g FlatGen) interface_method_stubs() { + // `interface_method_forward_decls` has already declared every dispatch stub. + // Recomputing the declarations here can observe a different current-module + // context after `_vinit` generation and give a generic interface placeholder a + // different C signature from its specialized forward declaration. + for iface_name, methods in g.interfaces { + cn := g.cname(iface_name) + for method in methods { + if !g.should_emit_interface_dispatch(iface_name, method) { + continue + } + if g.cache_split { + // Dispatch tables depend on the concrete implementations in the + // current program, so they belong beside main instead of in the + // source-stable object that owns the interface declaration. + g.writeln('/* V3CACHE_MODULE main */') + } + g.gen_interface_dispatch(iface_name, cn, method) + } + } + if g.interfaces.len > 0 { + g.writeln('') + } +} + +fn (mut g FlatGen) interface_method_forward_decls() { + for iface_name, methods in g.interfaces { + cn := g.cname(iface_name) + for method in methods { + if !g.should_emit_interface_dispatch(iface_name, method) { + continue + } + if cn == 'IError' { + ret_ct := if method == 'code' { 'int' } else { 'string' } + g.writeln('${ret_ct} ${cn}__${method}(${cn}* i);') + if g.cache_split { + g.ierror_dispatch_target_forward_decls(iface_name, method, ret_ct) + } + continue + } + mname := '${iface_name}.${method}' + decl_key := g.interface_method_signature_key(iface_name, method) or { mname } + impls := g.iface_impls[iface_name] or { []string{} } + mut sig_key := '' + for concrete in impls { + candidate := '${concrete}.${method}' + if candidate in g.tc.fn_param_types { + sig_key = candidate + break + } + } + ret_type := g.interface_dispatch_return_type(decl_key, sig_key) + sig_params := g.interface_dispatch_param_types(decl_key, sig_key) + g.write('${g.fn_return_type_name(ret_type)} ${cn}__${method}(${cn}* i') + for pi := 1; pi < sig_params.len; pi++ { + pct := g.interface_dispatch_param_c_type(sig_params[pi]) + g.write(', ${pct} _a${pi - 1}') + } + g.writeln(');') + } + } + if g.interfaces.len > 0 { + g.writeln('') + } +} + +fn (mut g FlatGen) ierror_dispatch_target_forward_decls(iface_name string, method string, ret_ct string) { + mut forwarded := map[string]bool{} + for concrete in g.iface_impls[iface_name] or { []string{} } { + call := g.ierror_method_call(concrete, method) or { continue } + target_c_name := g.cname(call.method_name) + if forwarded[target_c_name] { + continue + } + params := g.tc.fn_param_types[call.method_name] or { continue } + if params.len != 1 { + continue + } + forwarded[target_c_name] = true + g.writeln('${ret_ct} ${target_c_name}(${g.tc.c_type(params[0])} _recv);') + } +} + +fn (g &FlatGen) should_emit_interface_dispatch(iface_name string, method string) bool { + if g.cache_split { + return true + } + if !g.has_used_fn_filter() { + return true + } + name := '${iface_name}.${method}' + if g.used_interface_dispatch_key(name) { + return true + } + if decl_key := g.interface_method_signature_key(iface_name, method) { + if decl_key != name && g.used_interface_dispatch_key(decl_key) { + return true + } + decl_short_name := '${decl_key.all_before_last('.').all_after_last('.')}.${method}' + if decl_short_name != decl_key && g.interface_dispatch_short_name_allowed(iface_name) + && g.used_interface_dispatch_key(decl_short_name) { + return true + } + } + for alias in g.interface_alias_names(iface_name) { + alias_name := '${alias}.${method}' + if g.used_interface_dispatch_key(alias_name) { + return true + } + short_alias_name := '${alias.all_after_last('.')}.${method}' + if short_alias_name != alias_name && g.interface_dispatch_short_name_allowed(alias) + && g.used_interface_dispatch_key(short_alias_name) { + return true + } + } + short_name := '${iface_name.all_after_last('.')}.${method}' + return short_name != name && g.interface_dispatch_short_name_allowed(iface_name) + && g.used_interface_dispatch_key(short_name) +} + +fn (mut g FlatGen) interface_dispatch_signature(iface_name string, cn string, method string) string { + if cn == 'IError' { + ret_ct := if method == 'code' { 'int' } else { 'string' } + return '${ret_ct} ${cn}__${method}(${cn}* i)' + } + mname := '${iface_name}.${method}' + decl_key := g.interface_method_signature_key(iface_name, method) or { mname } + impls := g.iface_impls[iface_name] or { []string{} } + mut sig_key := '' + for concrete in impls { + if concrete in g.tc.interface_names { + continue + } + ck := '${concrete}.${method}' + if ck in g.tc.fn_param_types { + sig_key = ck + break + } + } + ret_type := g.interface_dispatch_return_type(decl_key, sig_key) + ret_ct := g.fn_return_type_name(ret_type) + sig_params := g.interface_dispatch_param_types(decl_key, sig_key) + mut sig := '${ret_ct} ${cn}__${method}(${cn}* i' + for pi := 1; pi < sig_params.len; pi++ { + pct := g.interface_dispatch_param_c_type(sig_params[pi]) + sig += ', ${pct} _a${pi - 1}' + } + sig += ')' + return sig +} + +fn (g &FlatGen) interface_alias_names(iface_name string) []string { + mut aliases := []string{} + for alias, target in g.tc.type_aliases { + qtarget := g.tc.qualify_name(target) + if target == iface_name || qtarget == iface_name { + aliases << alias + } + } + return aliases +} + +fn (g &FlatGen) used_interface_dispatch_key(name string) bool { + return g.used_fn_contains(name) || g.used_fn_contains(g.cname(name)) +} + +fn (g &FlatGen) interface_dispatch_short_name_allowed(iface_name string) bool { + return !iface_name.contains('.') +} + +// gen_interface_dispatch emits interface dispatch output for c. +fn (mut g FlatGen) gen_interface_dispatch(iface_name string, cn string, method string) { + g.gen_interface_dispatch_with_fallback(iface_name, cn, method, true) +} + +fn (mut g FlatGen) gen_interface_dispatch_with_fallback(iface_name string, cn string, method string, panic_on_default bool) { + sid := if panic_on_default { + g.intern_string('interface method ${cn}.${method} not implemented') + } else { + -1 + } + mname := '${iface_name}.${method}' + decl_key := g.interface_method_signature_key(iface_name, method) or { mname } + impls := g.iface_impls[iface_name] or { []string{} } + if cn == 'IError' { + ret_ct := if method == 'code' { 'int' } else { 'string' } + g.writeln('${ret_ct} ${cn}__${method}(${cn}* i) {') + for concrete in impls { + id := g.iface_type_id(iface_name, concrete) + call := g.ierror_method_call(concrete, method) or { continue } + if id == 0 { + continue + } + params := g.tc.fn_param_types[call.method_name] or { []types.Type{} } + recv_is_ptr := params.len > 0 && params[0] is types.Pointer + recv := g.ierror_method_receiver_expr(concrete, call.path, recv_is_ptr) + g.writeln('\tif (i->_typ == ${id}) return ${g.cname(call.method_name)}(${recv});') + } + match method { + 'msg' { + g.writeln('\treturn i->message;') + } + 'code' { + g.writeln('\treturn i->code;') + } + else { + g.writeln('\tv_panic(_str_${sid});') + g.writeln('\treturn (${ret_ct}){0};') + } + } + + g.writeln('}') + return + } + // Interface-declared method signatures store named params unreliably (a named + // param like `node &ast.Node` can be split into two type-only params). The + // concrete implementer's method is a real fn_decl with a correctly parsed + // signature, so derive the dispatch parameter types from the first implementer + // that has the method. The receiver convention is resolved per implementer. + mut sig_key := '' + for concrete in impls { + ck := '${concrete}.${method}' + if ck in g.tc.fn_param_types { + sig_key = ck + break + } + } + ret_type := g.interface_dispatch_return_type(decl_key, sig_key) + // Use the ABI return type, not the bare value type: a fixed-array return is its `_v_ret_*` + // wrapper struct (a C function cannot return an array by value), matching what the concrete + // implementer's method returns and what the call site unwraps. + ret_ct := g.fn_return_type_name(ret_type) + mut sig_params := g.interface_dispatch_param_types(decl_key, sig_key) + mut arg_names := []string{} + g.write('${ret_ct} ${cn}__${method}(${cn}* i') + for pi := 1; pi < sig_params.len; pi++ { + pct := g.interface_dispatch_param_c_type(sig_params[pi]) + an := '_a${pi - 1}' + arg_names << an + g.write(', ${pct} ${an}') + } + g.writeln(') {') + str_dispatch_is_boxed_only := g.interface_dispatch_can_use_implicit_str(method, ret_ct, + sig_params) + if impls.len > 0 { + g.writeln('\tswitch (i->_typ) {') + for concrete in impls { + id := g.iface_type_id(iface_name, concrete) + if id == 0 { + continue + } + concrete_type_for_dispatch := g.interface_concrete_type(concrete) + concrete_is_fn_type := g.interface_unaliased_type(concrete_type_for_dispatch) is types.FnType + if str_dispatch_is_boxed_only && concrete !in g.tc.interface_names + && !concrete_is_fn_type + && !g.interface_boxed_type_marked_for_dispatch(iface_name, concrete) { + continue + } + if concrete in g.tc.interface_names { + decl := g.interface_method_signature_key(concrete, method) or { continue } + if !g.interface_dispatch_target_is_emitted('${concrete}.${method}') + && !g.interface_dispatch_target_is_emitted(decl) { + continue + } + concrete_params := g.tc.fn_param_types[decl] or { []types.Type{} } + if !g.interface_dispatch_signature_compatible(decl, ret_type, sig_params) { + continue + } + recv_is_ptr := concrete_params.len > 0 && concrete_params[0] is types.Pointer + recv := if recv_is_ptr { + '(${g.cname(concrete)}*)i->_object' + } else { + '*(${g.cname(concrete)}*)i->_object' + } + g.write('\t\tcase ${id}: ') + mut call := '${g.cname(concrete)}__${method}(${recv}' + for ai, an in arg_names { + arg_idx := ai + 1 + concrete_param := if arg_idx < concrete_params.len { + concrete_params[arg_idx] + } else { + types.Type(types.void_) + } + dispatch_param := if arg_idx < sig_params.len { + sig_params[arg_idx] + } else { + concrete_param + } + if concrete_param is types.Pointer && dispatch_param !is types.Pointer { + call += ', &${an}' + } else if concrete_param !is types.Pointer && dispatch_param is types.Pointer { + call += ', *${an}' + } else { + call += ', ${an}' + } + } + call += ')' + if ret_ct == 'void' { + g.writeln('${call}; return;') + } else if g.gen_interface_dispatch_optional_abi_return(call, ret_type, g.tc.fn_ret_types[decl] or { + ret_type + }, decl) + { + } else if g.gen_interface_dispatch_wrapped_return(call, ret_type, g.tc.fn_ret_types[decl] or { + ret_type + }, decl) + { + } else { + g.writeln('return ${call};') + } + continue + } + concrete_key := '${concrete}.${method}' + method_key := g.tc.concrete_method_signature_key(concrete, method) or { concrete_key } + if method_key !in g.tc.fn_param_types + || !g.interface_dispatch_target_is_emitted(method_key) { + if str_dispatch_is_boxed_only { + mut str_stack := []string{} + if str_expr := g.interface_implicit_str_expr(g.interface_concrete_type(concrete), + g.interface_dispatch_boxed_value_expr(concrete), false, mut str_stack) + { + g.writeln('\t\tcase ${id}: return ${str_expr};') + } + } + continue + } + concrete_params := g.tc.fn_param_types[method_key] or { []types.Type{} } + if !g.interface_dispatch_signature_compatible(method_key, ret_type, sig_params) { + continue + } + recv_is_ptr := concrete_params.len > 0 && concrete_params[0] is types.Pointer + recv := g.interface_dispatch_receiver_expr(concrete, concrete_params, recv_is_ptr) + g.write('\t\tcase ${id}: ') + mut call := '${g.cname(method_key)}(${recv}' + for ai, an in arg_names { + arg_idx := ai + 1 + concrete_param := if arg_idx < concrete_params.len { + concrete_params[arg_idx] + } else { + types.Type(types.void_) + } + dispatch_param := if arg_idx < sig_params.len { + sig_params[arg_idx] + } else { + concrete_param + } + if concrete_param is types.Pointer && dispatch_param !is types.Pointer { + call += ', &${an}' + } else if concrete_param !is types.Pointer && dispatch_param is types.Pointer { + call += ', *${an}' + } else if converted := g.interface_arg_conversion_expr(an, dispatch_param, + concrete_param) + { + call += ', ${converted}' + } else { + call += ', ${an}' + } + } + call += ')' + if ret_ct == 'void' { + g.writeln('${call}; return;') + } else if g.gen_interface_dispatch_optional_abi_return(call, ret_type, g.tc.fn_ret_types[method_key] or { + ret_type + }, method_key) + { + } else if g.gen_interface_dispatch_wrapped_return(call, ret_type, g.tc.fn_ret_types[method_key] or { + ret_type + }, method_key) + { + } else { + g.writeln('return ${call};') + } + } + g.writeln('\t\tdefault: break;') + g.writeln('\t}') + } + if panic_on_default { + g.writeln('\tv_panic(_str_${sid});') + } else if ret_ct == 'void' { + g.writeln('\treturn;') + } + if ret_ct != 'void' { + g.writeln('\treturn (${ret_ct}){0};') + } + g.writeln('}') +} + +// gen_interface_dispatch_optional_abi_value_return emits the adapted wrapper return +// after a specialized generic method and its interface dispatch use different C ABIs. +fn (mut g FlatGen) gen_interface_dispatch_optional_abi_value_return(expected_ct string, result string, expected_base types.Type) { + if _ := array_fixed_type(expected_base) { + out := g.interface_tmp('iface_abi_result_out') + g.writeln('\t\t\t${expected_ct} ${out} = { .ok = ${result}.ok, .err = ${result}.err };') + g.writeln('\t\t\tif (${result}.ok) {') + g.writeln('\t\t\t\tmemcpy(${out}.value, ${result}.value, sizeof(${out}.value));') + g.writeln('\t\t\t}') + g.writeln('\t\t\treturn ${out};') + } else { + g.writeln('\t\t\treturn (${expected_ct}){ .ok = ${result}.ok, .err = ${result}.err, .value = ${result}.value };') + } +} + +// gen_interface_dispatch_optional_abi_return adapts specialized generic methods +// whose option/result C ABI differs from the interface dispatch ABI. +fn (mut g FlatGen) gen_interface_dispatch_optional_abi_return(call string, expected types.Type, actual types.Type, actual_key string) bool { + expected_wrapped := optional_result_unalias_type(expected) + actual_wrapped := optional_result_unalias_type(actual) + if (expected_wrapped is types.OptionType) != (actual_wrapped is types.OptionType) + || (expected_wrapped is types.ResultType) != (actual_wrapped is types.ResultType) { + return false + } + expected_base := interface_dispatch_wrapped_base_type(expected) or { return false } + actual_base := interface_dispatch_wrapped_base_type(actual) or { return false } + if expected_base is types.Void || expected_base is types.Unknown || actual_base is types.Void + || actual_base is types.Unknown { + return false + } + if !g.type_names_match(actual_base, expected_base) + && g.value_c_type(actual_base) != g.value_c_type(expected_base) { + return false + } + expected_ct := g.fn_return_type_name_for_context(expected, false) + actual_ct := g.fn_return_type_name_for_context(actual, + g.call_uses_concrete_optional_params(actual_key)) + if actual_ct == expected_ct { + return false + } + result := g.interface_tmp('iface_abi_result') + g.writeln('{') + g.writeln('\t\t\t${actual_ct} ${result} = ${call};') + g.gen_interface_dispatch_optional_abi_value_return(expected_ct, result, expected_base) + g.writeln('\t\t}') + return true +} + +// gen_interface_dispatch_wrapped_return adapts an option/result whose successful +// concrete payload implements the interface returned by the dispatch signature. +fn (mut g FlatGen) gen_interface_dispatch_wrapped_return(call string, expected types.Type, actual types.Type, actual_key string) bool { + if !g.interface_dispatch_wrapped_return_can_adapt(expected, actual) { + return false + } + expected_base := interface_dispatch_wrapped_base_type(expected) or { return false } + actual_base := interface_dispatch_wrapped_base_type(actual) or { return false } + expected_iface_type := cgen_unalias_type(expected_base) + if expected_iface_type !is types.Interface { + return false + } + expected_iface := expected_iface_type as types.Interface + actual_clean := cgen_unalias_type(actual_base) + actual_value := if actual_clean is types.Pointer { actual_clean.base_type } else { actual_clean } + if cgen_unalias_type(actual_value) is types.Interface { + return false + } + type_id := g.iface_type_id_for_concrete(expected_iface.name, actual_value) + if type_id == 0 { + return false + } + actual_ct := g.fn_return_type_name_for_context(actual, + g.call_uses_concrete_optional_params(actual_key)) + expected_ct := g.fn_return_type_name_for_context(expected, false) + iface_ct := g.tc.c_type(expected_iface_type) + concrete_ct := g.tc.c_type(actual_value) + result := g.interface_tmp('iface_result') + out := g.interface_tmp('iface_result_out') + g.writeln('{') + g.writeln('\t\t\t${actual_ct} ${result} = ${call};') + g.writeln('\t\t\t${expected_ct} ${out} = { .ok = ${result}.ok, .err = ${result}.err };') + g.writeln('\t\t\tif (${result}.ok) {') + g.write('\t\t\t\t${out}.value = (${iface_ct}){._typ = ${type_id}, ._object = ') + if actual_clean is types.Pointer { + g.write('${result}.value, ._object_is_boxed = false') + } else { + g.write('memdup(&${result}.value, sizeof(${concrete_ct})), ._object_is_boxed = true') + } + for field in g.interface_cached_fields(expected_iface.name) { + field_ct := g.tc.c_type(field.typ) + field_name := g.cname(field.name) + if actual_clean is types.Pointer { + g.write(', .${field_name} = ${result}.value ? ${result}.value->${field_name} : (${field_ct}){0}') + } else { + g.write(', .${field_name} = ${result}.value.${field_name}') + } + } + g.writeln('};') + g.writeln('\t\t\t}') + g.writeln('\t\t\treturn ${out};') + g.writeln('\t\t}') + return true +} + +fn (g &FlatGen) interface_dispatch_wrapped_return_can_adapt(expected types.Type, actual types.Type) bool { + expected_base := interface_dispatch_wrapped_base_type(expected) or { return false } + actual_base := interface_dispatch_wrapped_base_type(actual) or { return false } + expected_iface_type := cgen_unalias_type(expected_base) + if expected_iface_type !is types.Interface { + return false + } + expected_iface := expected_iface_type as types.Interface + actual_clean := cgen_unalias_type(actual_base) + actual_value := if actual_clean is types.Pointer { actual_clean.base_type } else { actual_clean } + if cgen_unalias_type(actual_value) is types.Interface { + return false + } + return g.iface_type_id_for_concrete(expected_iface.name, actual_value) != 0 +} + +fn interface_dispatch_wrapped_base_type(typ types.Type) ?types.Type { + match typ { + types.OptionType, types.ResultType { + return typ.base_type + } + else { + return none + } + } +} + +fn (mut g FlatGen) interface_dispatch_param_c_type(typ types.Type) string { + if typ is types.Pointer && g.type_contains_generic_placeholder(typ) { + return 'void*' + } + mut ct := if typ is types.OptionType || typ is types.ResultType { + g.optional_type_name(typ) + } else { + g.tc.c_type(typ) + } + if ct.starts_with('fn_ptr:') { + ct = g.resolve_fn_ptr_type(ct) + } + return ct +} + +fn (mut g FlatGen) interface_boxed_type_marked_for_dispatch(iface_name string, concrete string) bool { + g.collect_interface_boxed_types_for_dispatch() + return g.interface_boxed_type_collected_for_dispatch(iface_name, concrete) +} + +fn (g &FlatGen) interface_boxed_type_collected_for_dispatch(iface_name string, concrete string) bool { + if g.interface_boxed_types['${iface_name}::${concrete}'] + || g.interface_boxed_types['${iface_name}::${c_name(concrete)}'] + || g.interface_boxed_types['${iface_name}::${concrete.all_after_last('.')}'] { + return true + } + for candidate in [concrete, g.tc.qualify_name(concrete)] { + if target := g.tc.type_aliases[candidate] { + if g.interface_boxed_types['${iface_name}::${target}'] + || g.interface_boxed_types['${iface_name}::${c_name(target)}'] + || g.interface_boxed_types['${iface_name}::${target.all_after_last('.')}'] { + return true + } + } + } + return false +} + +fn (mut g FlatGen) collect_interface_boxed_types_for_dispatch() { + if g.interface_boxed_types_done { + return + } + g.interface_boxed_types_done = true + for node in g.a.nodes { + if node.kind != .struct_init || node.children_count == 0 { + continue + } + iface_name := if node.value in g.interfaces { + node.value + } else { + g.tc.qualify_name(node.value) + } + if iface_name !in g.interfaces { + continue + } + for i in 0 .. node.children_count { + field := g.a.child_node(&node, i) + if field.kind != .field_init || field.value != '_object' || field.children_count == 0 { + continue + } + obj_type := g.tc.resolve_type(g.a.child(field, 0)) + concrete := types.unwrap_pointer(obj_type) + concrete_name := concrete.name() + if concrete_name.len == 0 { + continue + } + g.mark_interface_boxed_type_for_dispatch(iface_name, concrete_name) + } + } +} + +fn (mut g FlatGen) interface_dispatch_signature_compatible(method_key string, expected_ret types.Type, sig_params []types.Type) bool { + ret_type := g.tc.fn_ret_types[method_key] or { return false } + if g.fn_return_type_name(ret_type) != g.fn_return_type_name(expected_ret) + && !g.interface_dispatch_wrapped_return_can_adapt(expected_ret, ret_type) { + return false + } + params := g.tc.fn_param_types[method_key] or { return false } + return params.len == sig_params.len +} + +fn (mut g FlatGen) mark_interface_boxed_type_for_dispatch(iface_name string, concrete_name string) { + g.interface_boxed_types['${iface_name}::${concrete_name}'] = true + g.interface_boxed_types['${iface_name}::${c_name(concrete_name)}'] = true + g.interface_boxed_types['${iface_name}::${concrete_name.all_after_last('.')}'] = true +} + +fn (g &FlatGen) interface_dispatch_can_use_implicit_str(method string, ret_ct string, sig_params []types.Type) bool { + return method == 'str' && ret_ct == 'string' && sig_params.len == 1 +} + +fn (g &FlatGen) interface_dispatch_boxed_value_expr(concrete string) string { + ct := g.interface_concrete_storage_c_type(concrete) + return '*(${ct}*)i->_object' +} + +fn (g &FlatGen) interface_concrete_storage_c_type(concrete string) string { + concrete_type := g.interface_concrete_type(concrete) + ct := if concrete_type is types.Unknown { + g.cname(concrete) + } else { + g.tc.c_type(concrete_type) + } + if ct.starts_with('fn_ptr:') { + return naming.fn_ptr_type_name(ct) + } + if concrete.starts_with('fn_ptr:') { + return naming.fn_ptr_type_name(concrete) + } + return ct +} + +fn (g &FlatGen) interface_concrete_type(concrete string) types.Type { + if types.is_builtin_type_name(concrete) { + return g.tc.parse_type(concrete) + } + for candidate in [concrete, g.tc.qualify_name(concrete)] { + if candidate in g.tc.type_aliases { + return types.Type(types.Alias{ + name: candidate + base_type: g.tc.parse_type(g.tc.type_aliases[candidate]) + }) + } + if candidate in g.tc.structs { + return types.Type(types.Struct{ + name: candidate + }) + } + if candidate in g.tc.interface_names { + return types.Type(types.Interface{ + name: candidate + }) + } + if candidate in g.tc.sum_types { + return types.Type(types.SumType{ + name: candidate + }) + } + if candidate in g.tc.enum_names { + return types.Type(types.Enum{ + name: candidate + }) + } + } + return g.tc.parse_type(concrete) +} + +// short_module_type_text strips module qualifiers from every identifier in a +// type text: `map[string]toml.ast.Value` -> `map[string]Value`. +fn short_module_type_text(text string) string { + mut out := []u8{cap: text.len} + mut i := 0 + for i < text.len { + c := text[i] + if (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || c == `_` { + start := i + for i < text.len { + c2 := text[i] + if (c2 >= `a` && c2 <= `z`) || (c2 >= `A` && c2 <= `Z`) + || (c2 >= `0` && c2 <= `9`) || c2 == `_` || c2 == `.` { + i++ + } else { + break + } + } + token := text[start..i] + unsafe { out.push_many(token.all_after_last('.').str, token.all_after_last('.').len) } + continue + } + out << c + i++ + } + return out.bytestr() +} + +fn (mut g FlatGen) interface_implicit_str_expr(typ types.Type, expr string, quote_string bool, mut stack []string) ?string { + clean := g.interface_unaliased_type(typ) + if typ is types.Alias { + if custom := g.interface_custom_str_expr(typ.name, typ, expr) { + return custom + } + } + match clean { + types.String { + if quote_string { + return g.interface_str_plus(g.interface_str_plus(g.interface_str_lit("'"), expr), + g.interface_str_lit("'")) + } + return expr + } + types.Char, types.Rune { + inner := 'rune__str((u32)(${expr}))' + return g.interface_str_plus(g.interface_str_plus(g.interface_str_lit('`'), inner), + g.interface_str_lit('`')) + } + types.ISize { + return 'v3_i64_zpad((i64)(${expr}), 0)' + } + types.USize { + return 'u64__str((u64)(${expr}))' + } + types.Primitive { + name := types.Type(clean).name() + if clean.props.has(.float) { + return 'f64__str((double)(${expr}))' + } + if name == 'bool' { + return '((${expr}) ? ${g.interface_str_lit('true')} : ${g.interface_str_lit('false')})' + } + if name in ['i8', 'i16', 'i32', 'i64', 'int'] { + return 'v3_i64_zpad((i64)(${expr}), 0)' + } + if name in ['u8', 'byte', 'u16', 'u32', 'u64'] { + return 'u64__str((u64)(${expr}))' + } + return none + } + types.Pointer { + return g.interface_pointer_str_expr(clean.base_type, expr, true, mut stack) + } + types.FnType { + return g.interface_str_lit(types.Type(clean).name().replace('fn(', 'fn (')) + } + types.Array { + return g.interface_array_str_expr(clean, expr, mut stack) + } + types.ArrayFixed { + return g.interface_fixed_array_str_expr(clean, expr, mut stack) + } + types.Map { + key_kind := map_str_kind(g.tc, clean.key_type) + value_kind := map_str_kind(g.tc, clean.value_type) + if key_kind != 0 && value_kind != 0 { + fixed_len := map_str_fixed_len(clean.value_type) + return 'v3_map_str(${expr}, ${key_kind}, ${value_kind}, ${fixed_len})' + } + return g.interface_map_str_expr(clean, expr, mut stack) + } + types.OptionType { + return g.interface_optional_str_expr(clean.base_type, expr, mut stack) + } + types.ResultType { + return g.interface_result_str_expr(clean.base_type, expr, mut stack) + } + types.Enum { + return '${g.enum_autostr_c_name(clean.name)}__autostr(${expr})' + } + types.Struct { + if custom := g.interface_custom_str_expr(clean.name, types.Type(clean), expr) { + return custom + } + return g.interface_struct_str_expr(clean.name, expr, mut stack) + } + types.SumType { + return g.interface_sum_str_expr(clean, expr, mut stack) + } + types.Interface { + if g.is_ierror_type_name(clean.name) { + return 'IError__str(${expr})' + } + return g.interface_dynamic_str_expr(clean, expr, mut stack) + } + else { + return none + } + } +} + +fn (g &FlatGen) interface_unaliased_type(typ types.Type) types.Type { + mut clean := typ + for _ in 0 .. 100 { + if clean is types.Alias { + clean = clean.base_type + continue + } + break + } + return clean +} + +fn (mut g FlatGen) interface_custom_str_expr(type_name string, typ types.Type, expr string) ?string { + method_key := g.tc.concrete_method_signature_key(type_name, 'str') or { return none } + if typ is types.Alias { + direct_key := '${type_name}.str' + qualified_key := '${g.tc.qualify_name(type_name)}.str' + if method_key != direct_key && method_key != qualified_key { + return none + } + } + if method_key !in g.tc.fn_param_types || !g.interface_dispatch_target_is_emitted(method_key) { + return none + } + params := g.tc.fn_param_types[method_key] or { []types.Type{} } + wants_ptr := params.len > 0 && params[0] is types.Pointer + arg := if wants_ptr { + if typ is types.Pointer { expr } else { '&(${expr})' } + } else { + if typ is types.Pointer { '*(${expr})' } else { expr } + } + return '${g.cname(method_key)}(${arg})' +} + +fn (mut g FlatGen) interface_pointer_str_expr(base_type types.Type, expr string, prefix_pointer bool, mut stack []string) ?string { + ptr_type := types.Type(types.Pointer{ + base_type: base_type + }) + ptr_ct := g.tc.c_type(ptr_type) + tmp := g.interface_tmp('iface_str_ptr') + out := g.interface_tmp('iface_str_out') + mut inner := '' + clean_base := g.interface_unaliased_type(base_type) + use_custom := base_type is types.Alias || clean_base is types.Struct + if use_custom { + if custom := g.interface_custom_str_expr(base_type.name(), ptr_type, tmp) { + inner = custom + } + } + if inner.len == 0 { + inner = g.interface_implicit_str_expr(base_type, '*${tmp}', clean_base is types.String, mut + stack) or { 'ptr_str(${tmp})' } + } + if prefix_pointer { + inner = g.interface_str_plus(g.interface_str_lit('&'), inner) + } + return '({ ${ptr_ct} ${tmp} = (${ptr_ct})(${expr}); string ${out} = ${g.interface_str_lit('&nil')}; if (${tmp} != 0) { ${out} = ${inner}; } ${out}; })' +} + +fn (mut g FlatGen) interface_optional_str_expr(base_type types.Type, expr string, mut stack []string) ?string { + clean_base := g.interface_unaliased_type(base_type) + inner := g.interface_implicit_str_expr(base_type, '(${expr}).value', + clean_base is types.String, mut stack) or { g.interface_str_lit('