| 1 | // Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module builder |
| 5 | |
| 6 | import os |
| 7 | import v2.ast |
| 8 | import v2.abi |
| 9 | import v2.eval |
| 10 | import v2.gen.arm64 |
| 11 | import v2.gen.c |
| 12 | import v2.gen.cleanc |
| 13 | import v2.gen.v as gen_v |
| 14 | import v2.gen.x64 |
| 15 | import v2.insel |
| 16 | import v2.markused |
| 17 | import v2.parser |
| 18 | import v2.mir |
| 19 | import v2.pref |
| 20 | import v2.ssa |
| 21 | import v2.ssa.optimize as ssa_optimize |
| 22 | import v2.token |
| 23 | import v2.transformer |
| 24 | import v2.types |
| 25 | import time |
| 26 | import runtime |
| 27 | |
| 28 | const staged_c_file = '/tmp/v2_codegen.tmp.c' |
| 29 | const staged_main_obj_file = '/tmp/v2_codegen.tmp.main.o' |
| 30 | |
| 31 | struct Builder { |
| 32 | pref &pref.Preferences |
| 33 | mut: |
| 34 | files []ast.File |
| 35 | user_files []string // original user-provided files (for output name) |
| 36 | file_set &token.FileSet = token.FileSet.new() |
| 37 | env &types.Environment = unsafe { nil } // Type checker environment |
| 38 | parsed_full_files_n int |
| 39 | parsed_vh_files_n int |
| 40 | entry_v_lines_n int |
| 41 | parsed_v_lines_n int |
| 42 | parsed_full_files []string |
| 43 | parsed_vh_files []string |
| 44 | used_fn_keys map[string]bool |
| 45 | cached_called_fn_names map[string]bool |
| 46 | v2compiler_generic_setup_snapshot cleanc.GenericSetupSnapshot |
| 47 | has_v2compiler_generic_setup_snapshot bool |
| 48 | used_vh_for_parse bool |
| 49 | used_import_vh_for_parse bool |
| 50 | used_virtual_vh_for_parse bool |
| 51 | // flat is the canonical parse output. parse_batch streams directly into |
| 52 | // flat_builder so b.flat is built incrementally during parsing rather than |
| 53 | // via a redundant flatten_files() pass afterwards. |
| 54 | flat ast.FlatAst |
| 55 | flat_builder ast.FlatBuilder |
| 56 | // flat_builder_inited tracks whether flat_builder has been seeded with |
| 57 | // pre-sized arenas. We can only size after we know the input set, so |
| 58 | // the first parse_batch call lazily initializes it. |
| 59 | flat_builder_inited bool |
| 60 | // native_flat_pipeline_enabled means the transform phase produced a |
| 61 | // post-transform FlatAst and intentionally did not materialize b.files. |
| 62 | native_flat_pipeline_enabled bool |
| 63 | // Source snapshot used only for an isolated macOS tiny candidate graph. |
| 64 | // The normal hosted graph is still built separately and remains the fallback. |
| 65 | macos_tiny_candidate_source_files []ast.File |
| 66 | macos_tiny_candidate_source_flat ast.FlatAst |
| 67 | } |
| 68 | |
| 69 | pub fn new_builder(prefs &pref.Preferences) &Builder { |
| 70 | unsafe { |
| 71 | return &Builder{ |
| 72 | pref: prefs |
| 73 | used_fn_keys: map[string]bool{} |
| 74 | cached_called_fn_names: map[string]bool{} |
| 75 | } |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | // exec_build_c_file returns the staging C file path for executable builds. |
| 80 | // A stable temp path avoids output-name-derived string construction in |
| 81 | // self-hosted arm64 binaries, where that path is currently unstable. |
| 82 | fn (b &Builder) exec_build_c_file(output_name string) string { |
| 83 | if b.pref.keep_c { |
| 84 | return output_name + '.c' |
| 85 | } |
| 86 | return staged_c_file |
| 87 | } |
| 88 | |
| 89 | fn (b &Builder) should_use_native_flat_pipeline() bool { |
| 90 | if os.getenv('V2_NATIVE_FLAT') == '' { |
| 91 | return false |
| 92 | } |
| 93 | if os.getenv('V2_NO_NATIVE_FLAT') != '' { |
| 94 | return false |
| 95 | } |
| 96 | return b.pref.backend == .arm64 && b.pref.hot_fn.len == 0 |
| 97 | } |
| 98 | |
| 99 | fn (b &Builder) backend_uses_markused_pruning() bool { |
| 100 | return b.pref.backend != .arm64 |
| 101 | } |
| 102 | |
| 103 | // should_skip_markused_for_self_build avoids a self-host-only pruning pass that |
| 104 | // costs more than it saves once the v2 compiler object caches are hot. |
| 105 | fn (b &Builder) should_skip_markused_for_self_build() bool { |
| 106 | return b.pref.backend == .cleanc && b.is_cmd_v2_self_build() |
| 107 | } |
| 108 | |
| 109 | fn (b &Builder) should_build_ssa_from_flat() bool { |
| 110 | return b.flat.files.len > 0 |
| 111 | } |
| 112 | |
| 113 | fn (b &Builder) should_keep_flat_for_codegen() bool { |
| 114 | return match b.pref.backend { |
| 115 | .cleanc, .c, .x64, .arm64 { true } |
| 116 | else { false } |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | fn (b &Builder) can_compile_cleanc_locally() bool { |
| 121 | if b.pref == unsafe { nil } { |
| 122 | return true |
| 123 | } |
| 124 | return b.pref.can_compile_cleanc_locally() |
| 125 | } |
| 126 | |
| 127 | fn (b &Builder) cflags_target_os_for_local_compile() string { |
| 128 | if b.pref == unsafe { nil } { |
| 129 | return normalize_target_os_name(os.user_os()) |
| 130 | } |
| 131 | if b.pref.is_cross_target() && b.can_compile_cleanc_locally() { |
| 132 | return b.pref.source_filter_target_os() |
| 133 | } |
| 134 | return b.pref.target_os_or_host() |
| 135 | } |
| 136 | |
| 137 | fn cleanc_c_output_name(output_name string) string { |
| 138 | if output_name.ends_with('.c') { |
| 139 | return output_name |
| 140 | } |
| 141 | return output_name + '.c' |
| 142 | } |
| 143 | |
| 144 | fn (mut b Builder) compile_cleanc_executable(output_name string, cc string, cc_flags string, cc_link_flags string, error_limit_flag string, mut sw time.StopWatch) { |
| 145 | cc_start := sw.elapsed() |
| 146 | if b.pref.is_shared_lib { |
| 147 | // Shared library: compile with -shared -fPIC -undefined dynamic_lookup |
| 148 | // Use -fvisibility=hidden so only explicitly exported (impl_live_*) symbols |
| 149 | // are visible. All other functions become hidden, causing the dylib to |
| 150 | // resolve them from the host executable at load time. |
| 151 | mut cc_cmd := '${cc} ${cc_flags} -shared -fPIC -fvisibility=hidden -undefined dynamic_lookup -w -Wno-incompatible-function-pointer-types "${staged_c_file}"' |
| 152 | if cc_link_flags.len > 0 { |
| 153 | cc_cmd += ' -x none ${cc_link_flags}' |
| 154 | } |
| 155 | cc_cmd += ' -o "${output_name}"${error_limit_flag}' |
| 156 | run_cc_cmd_or_exit(cc_cmd, 'shared lib compilation', b.pref.show_cc) |
| 157 | print_time('CC (shared)', time.Duration(sw.elapsed() - cc_start)) |
| 158 | println('[*] Compiled shared library ${output_name}') |
| 159 | return |
| 160 | } |
| 161 | // Non-cached path: compile and link in one step. |
| 162 | // Place link flags (which may include .o files) AFTER the source file. |
| 163 | // Use `-x none` to reset the language before .o files, since -x objective-c |
| 164 | // would cause cc to treat .o files as source code. |
| 165 | mut cc_cmd := '${cc} ${cc_flags} -w -Wno-incompatible-function-pointer-types "${staged_c_file}"' |
| 166 | if cc_link_flags.len > 0 { |
| 167 | cc_cmd += ' -x none ${cc_link_flags}' |
| 168 | } |
| 169 | cc_cmd += ' -o "${output_name}"${error_limit_flag}' |
| 170 | run_cc_cmd_or_exit(cc_cmd, 'C compilation', b.pref.show_cc) |
| 171 | print_time('CC', time.Duration(sw.elapsed() - cc_start)) |
| 172 | |
| 173 | println('[*] Compiled ${output_name}') |
| 174 | } |
| 175 | |
| 176 | // print_rss prints process RSS in MB at the given phase boundary. |
| 177 | // Gated on V2_MEM=1. |
| 178 | // |
| 179 | // CAVEAT: v2 is force-built with `-gc none` (see is_v2_compiler_target in |
| 180 | // vlib/v/pref/default.v), so allocations are never reclaimed. RSS is |
| 181 | // dominated by the OS's decision of which pages to keep resident, not by |
| 182 | // what the program is actually using. Run-to-run variance is huge |
| 183 | // (observed 500 MB to 4.8 GB on identical runs). Treat these numbers as |
| 184 | // a coarse signal, not a precision metric. |
| 185 | // |
| 186 | // For reliable peak-memory comparisons between optimizations, use |
| 187 | // `/usr/bin/time -l <cmd>` and read `peak memory footprint` — that |
| 188 | // metric is stable to within ~0.1% across runs. |
| 189 | fn print_rss(stage string) { |
| 190 | if os.getenv('V2_MEM') == '' { |
| 191 | return |
| 192 | } |
| 193 | rss := runtime.used_memory() or { 0 } |
| 194 | $if macos { |
| 195 | // Under -gc none nothing is freed, so `live` is monotonic and its |
| 196 | // per-phase delta is the exact bytes that phase allocated. `peak` is |
| 197 | // the high-water mark. Both are stable run-to-run, unlike `rss`. |
| 198 | live, peak := darwin_live_malloc_bytes() |
| 199 | mb := u64(1024 * 1024) |
| 200 | eprintln(' [mem] ${stage}: live ${live / mb} MB peak ${peak / mb} MB (rss ${rss / mb} MB)') |
| 201 | return |
| 202 | } |
| 203 | eprintln(' [mem] ${stage}: ${rss / (1024 * 1024)} MB') |
| 204 | } |
| 205 | |
| 206 | pub fn (mut b Builder) build(files []string) { |
| 207 | b.user_files = files |
| 208 | // Pre-parse fast path: for cmd/v2 self-host, if the cached bundle objects + main.o are |
| 209 | // present (restored from the durable tier if /tmp was wiped) and all sources are fresh, |
| 210 | // relink directly and skip the entire front-end. Falls through on any staleness. |
| 211 | if b.try_self_build_fast_relink() { |
| 212 | return |
| 213 | } |
| 214 | mut sw := time.new_stopwatch() |
| 215 | print_rss('start') |
| 216 | $if parallel ? { |
| 217 | if b.pref.no_parallel { |
| 218 | b.parse_files(files) |
| 219 | } else { |
| 220 | b.parse_files_parallel(files) |
| 221 | } |
| 222 | } $else { |
| 223 | b.parse_files(files) |
| 224 | } |
| 225 | b.files = []ast.File{} |
| 226 | parse_time := sw.elapsed() |
| 227 | print_time('Scan & Parse', parse_time) |
| 228 | print_rss('after parse') |
| 229 | // FlatBuilder is the canonical parse output; both parse paths stream into |
| 230 | // it. parse_files / parse_files_parallel return [] in flat mode, so b.flat |
| 231 | // is the live source of truth from here on. The rehydration to legacy |
| 232 | // []ast.File is deferred until a compatibility consumer actually needs it. |
| 233 | b.flat = b.flat_builder.flat |
| 234 | b.update_parse_summary_counts() |
| 235 | print_parse_summary(b.parsed_full_files_n, b.parsed_vh_files_n, b.entry_v_lines_n, |
| 236 | b.parsed_v_lines_n, b.pref.stats, b.pref.print_parsed_files, b.parsed_full_files, |
| 237 | b.parsed_vh_files) |
| 238 | if b.pref.stats { |
| 239 | b.print_flat_ast_summary() |
| 240 | } |
| 241 | |
| 242 | if b.pref.backend == .cleanc && !b.validate_freestanding_cleanc_contract() { |
| 243 | exit(1) |
| 244 | } |
| 245 | |
| 246 | if b.pref.skip_type_check { |
| 247 | b.env = types.Environment.new() |
| 248 | } else { |
| 249 | b.env = if b.pref.no_parallel { |
| 250 | b.type_check_files() |
| 251 | } else { |
| 252 | b.type_check_files_parallel() |
| 253 | } |
| 254 | } |
| 255 | type_check_time := time.Duration(sw.elapsed() - parse_time) |
| 256 | print_time('Type Check', type_check_time) |
| 257 | print_rss('after type check') |
| 258 | |
| 259 | b.prepare_macos_tiny_candidate_source_files() |
| 260 | |
| 261 | // Transform AST (flag enum desugaring, etc.) |
| 262 | transform_start := sw.elapsed() |
| 263 | mut trans := transformer.Transformer.new_with_pref(b.env, b.pref) |
| 264 | trans.set_file_set(b.file_set) |
| 265 | sequential_transform := b.pref.no_parallel_transform || b.pref.ownership |
| 266 | use_native_flat_pipeline := b.should_use_native_flat_pipeline() |
| 267 | b.native_flat_pipeline_enabled = use_native_flat_pipeline |
| 268 | // The transform is flat-only for every backend: both the sequential and |
| 269 | // the parallel path emit cursor-native into a FlatBuilder and never |
| 270 | // materialize a transformed []ast.File. The legacy backends that still |
| 271 | // consume []ast.File (.v/eval) rehydrate once from the transformed flat |
| 272 | // at the codegen boundary below instead of dragging a compatibility |
| 273 | // path through the transform. |
| 274 | if sequential_transform { |
| 275 | b.flat = trans.transform_flat_to_flat_direct(&b.flat, b.files) |
| 276 | } else { |
| 277 | b.flat = b.transform_files_parallel_flat_direct(mut trans) |
| 278 | } |
| 279 | b.files = []ast.File{} |
| 280 | transform_time := time.Duration(sw.elapsed() - transform_start) |
| 281 | print_time('Transform', transform_time) |
| 282 | print_rss('after transform') |
| 283 | |
| 284 | // Mark used functions/methods for backend pruning. |
| 285 | if b.pref.no_markused || !b.backend_uses_markused_pruning() |
| 286 | || b.should_skip_markused_for_self_build() { |
| 287 | b.used_fn_keys = map[string]bool{} |
| 288 | } else { |
| 289 | mark_used_start := sw.elapsed() |
| 290 | // Flat markused consumes the post-transform FlatAst. Both sequential |
| 291 | // and parallel paths populate b.flat as part of their *_to_flat wedge, |
| 292 | // so the separate flatten_files() pass is gone. |
| 293 | if b.uses_minimal_x64_runtime_roots() { |
| 294 | opts := markused.MarkUsedOptions{ |
| 295 | minimal_runtime_roots: true |
| 296 | } |
| 297 | b.used_fn_keys = markused.mark_used_flat_with_options(&b.flat, b.env, opts) |
| 298 | } else { |
| 299 | b.used_fn_keys = markused.mark_used_flat(&b.flat, b.env) |
| 300 | } |
| 301 | mark_used_time := time.Duration(sw.elapsed() - mark_used_start) |
| 302 | print_time('Mark Used', mark_used_time) |
| 303 | print_rss('after markused') |
| 304 | } |
| 305 | if !b.should_keep_flat_for_codegen() { |
| 306 | // .v/eval still consume legacy []ast.File: rehydrate once from the |
| 307 | // transformed flat at the backend boundary, then drop the flat. |
| 308 | if (b.pref.backend == .v && !b.pref.skip_genv) || b.pref.backend == .eval { |
| 309 | b.files = b.flat.to_files() |
| 310 | } |
| 311 | b.flat = ast.FlatAst{} |
| 312 | } |
| 313 | |
| 314 | // Generate output based on backend |
| 315 | match b.pref.backend { |
| 316 | .v { |
| 317 | if !b.pref.skip_genv { |
| 318 | b.gen_v_files() |
| 319 | } |
| 320 | } |
| 321 | .cleanc { |
| 322 | b.gen_cleanc() |
| 323 | } |
| 324 | .c { |
| 325 | b.gen_ssa_c() |
| 326 | } |
| 327 | .x64 { |
| 328 | b.gen_native(.x64) |
| 329 | } |
| 330 | .arm64 { |
| 331 | b.gen_native(.arm64) |
| 332 | } |
| 333 | .eval { |
| 334 | mut runner := eval.new(b.pref) |
| 335 | runner.run_files(b.files) or { |
| 336 | eprintln('error: ${err.msg()}') |
| 337 | exit(1) |
| 338 | } |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | print_time('Total', sw.elapsed()) |
| 343 | print_rss('after codegen (peak)') |
| 344 | } |
| 345 | |
| 346 | fn (mut b Builder) gen_v_files() { |
| 347 | mut gen := gen_v.new_gen(b.pref) |
| 348 | for file in b.files { |
| 349 | gen.gen(file) |
| 350 | if b.pref.debug { |
| 351 | gen.print_output() |
| 352 | } |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | fn (mut b Builder) gen_cleanc() { |
| 357 | // Clean C Backend (AST -> C) |
| 358 | mut sw := time.new_stopwatch() |
| 359 | |
| 360 | // The cached-core split is currently unstable in some module builds |
| 361 | // (including cmd/v2 self-host and directory-style user module builds). |
| 362 | // Force single-unit cleanc generation there. |
| 363 | force_no_cache := b.should_disable_cleanc_cache() |
| 364 | use_cache := !b.pref.no_cache && !force_no_cache |
| 365 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 366 | eprintln('TRACE_CACHE use_cache=${use_cache} no_cache=${b.pref.no_cache} force_no_cache=${force_no_cache} self_build=${b.is_cmd_v2_self_build()} files=${b.user_files}') |
| 367 | } |
| 368 | |
| 369 | // Determine output name |
| 370 | output_name := if b.pref.output_file != '' { |
| 371 | b.pref.output_file |
| 372 | } else if b.user_files.len > 0 { |
| 373 | b.default_output_name() |
| 374 | } else { |
| 375 | 'out' |
| 376 | } |
| 377 | |
| 378 | generation_only := output_name.ends_with('.c') || !b.can_compile_cleanc_locally() |
| 379 | if generation_only { |
| 380 | c_output_name := cleanc_c_output_name(output_name) |
| 381 | c_source := b.gen_cleanc_source([]string{}) |
| 382 | print_time('C Gen', sw.elapsed()) |
| 383 | if c_source == '' { |
| 384 | eprintln('error: cleanc backend is not fully functional (compiled with stubbed functions)') |
| 385 | eprintln('hint: use v2 compiled with v1 for proper C code generation') |
| 386 | return |
| 387 | } |
| 388 | os.write_file(c_output_name, c_source) or { panic(err) } |
| 389 | if output_name.ends_with('.c') { |
| 390 | println('[*] Wrote ${c_output_name}') |
| 391 | } else { |
| 392 | println('[*] Wrote ${c_output_name} (local C compilation disabled for this target)') |
| 393 | } |
| 394 | return |
| 395 | } |
| 396 | |
| 397 | mut cc := if b.pref.ccompiler.len > 0 { |
| 398 | b.pref.ccompiler |
| 399 | } else { |
| 400 | configured_cc(b.pref.vroot) |
| 401 | } |
| 402 | // -prod requires a real optimizing compiler — TCC cannot handle -O3/-flto. |
| 403 | // Switch to system cc (gcc/clang) when the default compiler is TCC. |
| 404 | if b.pref.is_prod && cc.contains('tcc') { |
| 405 | cc = 'cc' |
| 406 | } |
| 407 | directive_flags := b.collect_cflags_from_sources() |
| 408 | $if macos { |
| 409 | if cc.contains('tcc') && cflags_need_objc_mode(directive_flags) { |
| 410 | cc = 'cc' |
| 411 | } |
| 412 | } |
| 413 | // Separate directive flags into compile-only and link-only flags. |
| 414 | // -framework, -l, -L, .o/.a/.so/.dylib are linker flags and must NOT |
| 415 | // be passed during -c compilation (they can trigger unwanted header |
| 416 | // processing, e.g. MetalKit SIMD errors on macOS). |
| 417 | directive_compile_flags, directive_link_flags := split_compile_and_link_flags(directive_flags) |
| 418 | mut cc_flag_parts := []string{} |
| 419 | mut cc_link_parts := []string{} |
| 420 | env_flags := configured_cflags() |
| 421 | if env_flags.trim_space() != '' { |
| 422 | cc_flag_parts << env_flags.trim_space() |
| 423 | } |
| 424 | if cc.contains('tcc') && directive_flags.contains('thirdparty/sqlite/sqlite3.c') |
| 425 | && !directive_compile_flags.contains('SQLITE_DISABLE_INTRINSIC') { |
| 426 | cc_flag_parts << '-DSQLITE_DISABLE_INTRINSIC' |
| 427 | } |
| 428 | if directive_compile_flags.trim_space() != '' { |
| 429 | cc_flag_parts << directive_compile_flags.trim_space() |
| 430 | } |
| 431 | if directive_link_flags.trim_space() != '' { |
| 432 | cc_link_parts << directive_link_flags.trim_space() |
| 433 | } |
| 434 | tcc_extra := tcc_flags(cc, b.pref.vroot) |
| 435 | if tcc_extra.trim_space() != '' { |
| 436 | cc_flag_parts << tcc_extra.trim_space() |
| 437 | } |
| 438 | // macOS code can include Objective-C (.m) files via #include directives. |
| 439 | // Tell the C compiler to treat the source as Objective-C only when needed. |
| 440 | $if macos { |
| 441 | if cflags_need_objc_mode(directive_flags) { |
| 442 | cc_flag_parts << '-x objective-c' |
| 443 | } |
| 444 | } |
| 445 | cc_flag_parts << '-std=gnu11' |
| 446 | cc_flag_parts << '-fwrapv' |
| 447 | |
| 448 | // Detect compiler type for optimization flags and error limit. |
| 449 | is_tcc := cc.contains('tcc') |
| 450 | mut is_clang := false |
| 451 | if !is_tcc { |
| 452 | version_res := os.execute('${cc} --version') |
| 453 | if version_res.exit_code == 0 && version_res.output.contains('clang') { |
| 454 | is_clang = true |
| 455 | } |
| 456 | } |
| 457 | |
| 458 | // -prod: add -O3, -flto, -DNDEBUG for gcc/clang |
| 459 | if b.pref.is_prod { |
| 460 | cc_flag_parts << '-O3' |
| 461 | cc_flag_parts << '-DNDEBUG' |
| 462 | if !b.pref.is_shared_lib { |
| 463 | $if !windows { |
| 464 | cc_flag_parts << '-flto' |
| 465 | } |
| 466 | } |
| 467 | if !is_clang { |
| 468 | cc_flag_parts << '-fno-strict-aliasing' |
| 469 | } |
| 470 | } |
| 471 | |
| 472 | cc_flags := cc_flag_parts.join(' ') |
| 473 | cc_link_flags := cc_link_parts.join(' ') |
| 474 | mut error_limit_flag := '' |
| 475 | if is_clang { |
| 476 | error_limit_flag = ' -ferror-limit=0' |
| 477 | } |
| 478 | |
| 479 | // Fast path: cache one core object (builtin+strconv), compile/link only the rest. |
| 480 | if use_cache && !b.pref.skip_builtin && b.has_module('builtin') && b.has_module('strconv') { |
| 481 | if b.gen_cleanc_with_cached_core(output_name, cc, cc_flags, cc_link_flags, |
| 482 | error_limit_flag, mut sw) |
| 483 | { |
| 484 | // Mirror the freshly built objects to the durable tier so a later cold |
| 485 | // build (with /tmp wiped) can restore them and fast-relink. |
| 486 | b.save_durable_object_cache(b.core_cache_dir()) |
| 487 | return |
| 488 | } |
| 489 | } |
| 490 | |
| 491 | // Fallback: compile one full C translation unit. |
| 492 | c_source := b.gen_cleanc_source([]string{}) |
| 493 | print_time('C Gen', sw.elapsed()) |
| 494 | if c_source == '' { |
| 495 | eprintln('error: cleanc backend is not fully functional (compiled with stubbed functions)') |
| 496 | eprintln('hint: use v2 compiled with v1 for proper C code generation') |
| 497 | return |
| 498 | } |
| 499 | os.write_file(staged_c_file, c_source) or { panic(err) } |
| 500 | println('[*] Wrote ${staged_c_file}') |
| 501 | b.compile_cleanc_executable(output_name, cc, cc_flags, cc_link_flags, error_limit_flag, mut sw) |
| 502 | } |
| 503 | |
| 504 | fn (b &Builder) is_cmd_v2_self_build() bool { |
| 505 | if b.user_files.len != 1 { |
| 506 | return false |
| 507 | } |
| 508 | // Avoid path normalization here: during bootstraps, some intermediate |
| 509 | // compilers can still have unstable path helpers. |
| 510 | path := b.user_files[0].replace('\\', '/') |
| 511 | if path == 'v2.v' || path.ends_with('/v2.v') { |
| 512 | return true |
| 513 | } |
| 514 | return path.ends_with('/cmd/v2/v2.v') || path.ends_with('cmd/v2/v2.v') |
| 515 | } |
| 516 | |
| 517 | fn (b &Builder) should_disable_cleanc_cache() bool { |
| 518 | // ARM64 cache previously disabled due to runtime helpers being emitted |
| 519 | // as static inline, which dropped them at cached-core boundaries. |
| 520 | // Fixed: __v2_array_eq is now a regular function with its body in the |
| 521 | // builtin cache unit and a forward declaration in the main TU. |
| 522 | for raw_input in b.user_files { |
| 523 | input := raw_input.trim_right('/\\') |
| 524 | if input.len == 0 { |
| 525 | continue |
| 526 | } |
| 527 | if input.ends_with('.v') || input.ends_with('.vv') || input.ends_with('.vsh') |
| 528 | || input.ends_with('.vh') { |
| 529 | continue |
| 530 | } |
| 531 | } |
| 532 | return false |
| 533 | } |
| 534 | |
| 535 | fn (b &Builder) default_output_name() string { |
| 536 | if b.user_files.len == 0 { |
| 537 | return 'out' |
| 538 | } |
| 539 | last_input := b.user_files[b.user_files.len - 1].trim_right('/\\') |
| 540 | if last_input.len == 0 || last_input == '.' { |
| 541 | cwd := os.getwd() |
| 542 | base := os.file_name(cwd) |
| 543 | return if base.len > 0 { base } else { 'out' } |
| 544 | } |
| 545 | if os.is_dir(last_input) { |
| 546 | base := os.file_name(last_input) |
| 547 | return if base.len > 0 { base } else { 'out' } |
| 548 | } |
| 549 | base := os.file_name(last_input).all_before_last('.v') |
| 550 | return if base.len > 0 { base } else { os.file_name(last_input) } |
| 551 | } |
| 552 | |
| 553 | fn (mut b Builder) gen_ssa_c() { |
| 554 | // SSA -> C backend. |
| 555 | mut sw := time.new_stopwatch() |
| 556 | |
| 557 | mut mod := ssa.Module.new('main') |
| 558 | if mod == unsafe { nil } { |
| 559 | eprintln('error: ssa c backend not available (compiled with stubbed ssa module)') |
| 560 | eprintln('hint: use v2 compiled with v1 for ssa c code generation') |
| 561 | return |
| 562 | } |
| 563 | mut ssa_builder := ssa.Builder.new_with_env(mod, b.env) |
| 564 | ssa_builder.target_os = b.pref.target_os_or_host() |
| 565 | |
| 566 | mut stage_start := sw.elapsed() |
| 567 | mut built_from_flat := false |
| 568 | if b.should_build_ssa_from_flat() { |
| 569 | ssa_builder.build_all_from_flat(&b.flat) |
| 570 | built_from_flat = true |
| 571 | } else { |
| 572 | ssa_builder.build_all(b.files) |
| 573 | } |
| 574 | print_time('SSA Build', time.Duration(sw.elapsed() - stage_start)) |
| 575 | |
| 576 | // TODO: re-enable SSA optimization once the new builder is mature |
| 577 | // stage_start = sw.elapsed() |
| 578 | // optimize.optimize(mut mod) |
| 579 | // print_time('SSA Optimize', time.Duration(sw.elapsed() - stage_start)) |
| 580 | |
| 581 | output_name := if b.pref.output_file != '' { |
| 582 | b.pref.output_file |
| 583 | } else if b.user_files.len > 0 { |
| 584 | b.default_output_name() |
| 585 | } else { |
| 586 | 'out' |
| 587 | } |
| 588 | |
| 589 | if output_name.ends_with('.c') { |
| 590 | if built_from_flat { |
| 591 | b.flat = ast.FlatAst{} |
| 592 | } |
| 593 | stage_start = sw.elapsed() |
| 594 | mut gen := c.new_gen(mod) |
| 595 | c_source := gen.gen() |
| 596 | print_time('C Gen', time.Duration(sw.elapsed() - stage_start)) |
| 597 | if c_source == '' { |
| 598 | eprintln('error: ssa c backend failed to generate C source') |
| 599 | return |
| 600 | } |
| 601 | os.write_file(output_name, c_source) or { panic(err) } |
| 602 | println('[*] Wrote ${output_name}') |
| 603 | return |
| 604 | } |
| 605 | |
| 606 | cc := if b.pref.ccompiler.len > 0 { b.pref.ccompiler } else { configured_cc(b.pref.vroot) } |
| 607 | directive_flags := b.collect_cflags_from_sources() |
| 608 | if built_from_flat { |
| 609 | // SSA has copied the program into MIR, and directive scanning has read |
| 610 | // source names from the FlatAst. Keep the later C generator/compiler |
| 611 | // working sets clear of the transformed FlatAst. |
| 612 | b.flat = ast.FlatAst{} |
| 613 | } |
| 614 | mut cc_flag_parts := []string{} |
| 615 | env_flags := configured_cflags() |
| 616 | if env_flags.trim_space() != '' { |
| 617 | cc_flag_parts << env_flags.trim_space() |
| 618 | } |
| 619 | if cc.contains('tcc') && directive_flags.contains('thirdparty/sqlite/sqlite3.c') |
| 620 | && !directive_flags.contains('SQLITE_DISABLE_INTRINSIC') { |
| 621 | cc_flag_parts << '-DSQLITE_DISABLE_INTRINSIC' |
| 622 | } |
| 623 | if directive_flags.trim_space() != '' { |
| 624 | cc_flag_parts << directive_flags.trim_space() |
| 625 | } |
| 626 | tcc_extra := tcc_flags(cc, b.pref.vroot) |
| 627 | if tcc_extra.trim_space() != '' { |
| 628 | cc_flag_parts << tcc_extra.trim_space() |
| 629 | } |
| 630 | cc_flags := cc_flag_parts.join(' ') |
| 631 | mut error_limit_flag := '' |
| 632 | if !cc.contains('tcc') { |
| 633 | version_res := os.execute('${cc} --version') |
| 634 | if version_res.exit_code == 0 && version_res.output.contains('clang') { |
| 635 | error_limit_flag = ' -ferror-limit=0' |
| 636 | } |
| 637 | } |
| 638 | |
| 639 | // Try to get pre-compiled builtin.o and vlib.o from the cleanc cache |
| 640 | mut builtin_obj := '' |
| 641 | mut vlib_obj := '' |
| 642 | if !b.pref.skip_builtin && b.has_module('builtin') && b.has_module('strconv') |
| 643 | && b.ensure_core_cache_dir() { |
| 644 | cache_dir := b.core_cache_dir() |
| 645 | builtin_obj = b.ensure_cached_module_object(cache_dir, builtin_cache_name, |
| 646 | builtin_cached_module_paths, builtin_cached_module_names, cc, cc_flags, '', |
| 647 | error_limit_flag, false) or { '' } |
| 648 | if builtin_obj.len > 0 && vlib_cached_module_paths.len > 0 { |
| 649 | vlib_obj = b.ensure_cached_module_object(cache_dir, vlib_cache_name, |
| 650 | vlib_cached_module_paths, vlib_cached_module_names, cc, cc_flags, '', |
| 651 | error_limit_flag, false) or { '' } |
| 652 | } |
| 653 | } |
| 654 | stage_start = sw.elapsed() |
| 655 | mut gen := c.new_gen(mod) |
| 656 | gen.link_builtin = builtin_obj.len > 0 |
| 657 | c_source := gen.gen() |
| 658 | print_time('C Gen', time.Duration(sw.elapsed() - stage_start)) |
| 659 | if c_source == '' { |
| 660 | eprintln('error: ssa c backend failed to generate C source') |
| 661 | return |
| 662 | } |
| 663 | |
| 664 | c_file := b.exec_build_c_file(output_name) |
| 665 | os.write_file(c_file, c_source) or { panic(err) } |
| 666 | if c_file != staged_c_file { |
| 667 | os.write_file(staged_c_file, c_source) or { panic(err) } |
| 668 | } |
| 669 | println('[*] Wrote ${c_file}') |
| 670 | |
| 671 | cc_start := sw.elapsed() |
| 672 | mut cc_cmd := '' |
| 673 | if builtin_obj.len > 0 { |
| 674 | // Compile SSA main.c and link against pre-compiled builtin.o |
| 675 | main_obj := staged_main_obj_file |
| 676 | compile_cmd := '${cc} ${cc_flags} -w -c "${c_file}" -o "${main_obj}"${error_limit_flag}' |
| 677 | if b.pref.show_cc { |
| 678 | println(compile_cmd) |
| 679 | } |
| 680 | compile_res := os.execute(compile_cmd) |
| 681 | if compile_res.exit_code != 0 { |
| 682 | eprintln('error: ssa c backend compilation failed') |
| 683 | lines := compile_res.output.split_into_lines() |
| 684 | limit := if lines.len < 20 { lines.len } else { 20 } |
| 685 | for line in lines[..limit] { |
| 686 | eprintln(line) |
| 687 | } |
| 688 | exit(1) |
| 689 | } |
| 690 | mut link_objects := '"${main_obj}" "${builtin_obj}"' |
| 691 | if vlib_obj.len > 0 { |
| 692 | link_objects += ' "${vlib_obj}"' |
| 693 | } |
| 694 | cc_cmd = '${cc} ${cc_flags} -w ${link_objects} -o "${output_name}"' |
| 695 | if b.pref.show_cc { |
| 696 | println(cc_cmd) |
| 697 | } |
| 698 | cc_res := os.execute(cc_cmd) |
| 699 | if cc_res.exit_code != 0 { |
| 700 | eprintln('error: ssa c backend linking failed') |
| 701 | lines := cc_res.output.split_into_lines() |
| 702 | limit := if lines.len < 20 { lines.len } else { 20 } |
| 703 | for line in lines[..limit] { |
| 704 | eprintln(line) |
| 705 | } |
| 706 | exit(1) |
| 707 | } |
| 708 | if !b.pref.keep_c { |
| 709 | os.rm(main_obj) or {} |
| 710 | } |
| 711 | } else { |
| 712 | // Single-file compilation (no builtin linking) |
| 713 | cc_cmd = '${cc} ${cc_flags} -w "${c_file}" -o "${output_name}"${error_limit_flag}' |
| 714 | if b.pref.show_cc { |
| 715 | println(cc_cmd) |
| 716 | } else if os.getenv('V2VERBOSE') != '' { |
| 717 | dump(cc_cmd) |
| 718 | } |
| 719 | cc_res := os.execute(cc_cmd) |
| 720 | if cc_res.exit_code != 0 { |
| 721 | eprintln('error: ssa c backend compilation failed') |
| 722 | lines := cc_res.output.split_into_lines() |
| 723 | limit := if lines.len < 20 { lines.len } else { 20 } |
| 724 | for line in lines[..limit] { |
| 725 | eprintln(line) |
| 726 | } |
| 727 | exit(1) |
| 728 | } |
| 729 | } |
| 730 | print_time('CC', time.Duration(sw.elapsed() - cc_start)) |
| 731 | |
| 732 | if !b.pref.keep_c { |
| 733 | os.rm(c_file) or {} |
| 734 | } |
| 735 | println('[*] Compiled ${output_name}') |
| 736 | } |
| 737 | |
| 738 | fn (mut b Builder) gen_cleanc_source(modules []string) string { |
| 739 | return b.gen_cleanc_source_with_options(modules, []string{}, false, '', []string{}, true, |
| 740 | []string{}) |
| 741 | } |
| 742 | |
| 743 | fn (mut b Builder) gen_cleanc_source_for_cache(modules []string, cache_bundle_name string, use_markused bool) string { |
| 744 | return b.gen_cleanc_source_with_options(modules, []string{}, true, cache_bundle_name, |
| 745 | []string{}, use_markused, []string{}) |
| 746 | } |
| 747 | |
| 748 | fn (mut b Builder) gen_cleanc_source_for_cache_files(modules []string, emit_files []string, cache_bundle_name string, use_markused bool) string { |
| 749 | return b.gen_cleanc_source_with_options(modules, emit_files, true, cache_bundle_name, |
| 750 | []string{}, use_markused, []string{}) |
| 751 | } |
| 752 | |
| 753 | fn (mut b Builder) gen_cleanc_source_with_cache_init_calls(modules []string, cached_init_calls []string) string { |
| 754 | return b.gen_cleanc_source_with_options(modules, []string{}, false, '', cached_init_calls, |
| 755 | true, []string{}) |
| 756 | } |
| 757 | |
| 758 | fn (mut b Builder) gen_cleanc_source_with_cache_init_calls_and_files(modules []string, emit_files []string, cached_init_calls []string, use_markused bool) string { |
| 759 | return b.gen_cleanc_source_with_options(modules, emit_files, false, '', cached_init_calls, |
| 760 | use_markused, []string{}) |
| 761 | } |
| 762 | |
| 763 | fn (mut b Builder) gen_cleanc_source_with_cache_init_calls_files_force(modules []string, emit_files []string, cached_init_calls []string, use_markused bool, force_emit_fn_names []string) string { |
| 764 | return b.gen_cleanc_source_with_options(modules, emit_files, false, '', cached_init_calls, |
| 765 | use_markused, force_emit_fn_names) |
| 766 | } |
| 767 | |
| 768 | fn (mut b Builder) gen_cleanc_source_with_options(modules []string, emit_files []string, export_const_symbols bool, cache_bundle_name string, cached_init_calls []string, use_markused bool, force_emit_fn_names []string) string { |
| 769 | type_modules := if cache_bundle_name.len > 0 { |
| 770 | b.expand_type_modules_with_imports(cache_type_module_names(cache_bundle_name, modules)) |
| 771 | } else { |
| 772 | cache_type_module_names(cache_bundle_name, modules) |
| 773 | } |
| 774 | mut type_module_names := map[string]bool{} |
| 775 | restrict_to_cache_modules := cache_bundle_name.len > 0 && type_modules.len > 0 |
| 776 | if restrict_to_cache_modules { |
| 777 | for module_name in type_modules { |
| 778 | if module_name != '' { |
| 779 | type_module_names[module_name] = true |
| 780 | } |
| 781 | } |
| 782 | } |
| 783 | // Cache-bundle generation restricts emission to a fixed set of type |
| 784 | // modules. The legacy gen achieves this by filtering its input `gen_files`; |
| 785 | // the flat gen drives every pass off `flat.files`, so for a restricted |
| 786 | // bundle in flat mode we hand it a FlatAst scoped to the bundle's type |
| 787 | // modules (sharing b.flat's arena). The main translation unit |
| 788 | // (`restrict_to_cache_modules == false`) uses the full b.flat. |
| 789 | scope_flat_bundle := restrict_to_cache_modules && b.flat.files.len > 0 |
| 790 | scoped_flat := if scope_flat_bundle { |
| 791 | b.flat_scoped_to_modules(type_module_names) |
| 792 | } else { |
| 793 | ast.FlatAst{} |
| 794 | } |
| 795 | mut gen_files := []ast.File{cap: b.files.len} |
| 796 | if !scope_flat_bundle { |
| 797 | for file in b.files { |
| 798 | if restrict_to_cache_modules && ast_file_module_name(file) !in type_module_names { |
| 799 | continue |
| 800 | } |
| 801 | gen_files << file |
| 802 | } |
| 803 | } |
| 804 | if !scope_flat_bundle && cached_init_calls.len > 0 && b.used_vh_for_parse { |
| 805 | mut has_vh_files := false |
| 806 | for file in gen_files { |
| 807 | if file.name.ends_with('.vh') { |
| 808 | has_vh_files = true |
| 809 | break |
| 810 | } |
| 811 | } |
| 812 | if !has_vh_files { |
| 813 | mut p := parser.Parser.new(b.pref) |
| 814 | header_files := p.parse_files(b.core_cached_parse_paths(), mut b.file_set) |
| 815 | for header_file in header_files { |
| 816 | gen_files << header_file |
| 817 | } |
| 818 | } |
| 819 | } |
| 820 | mut gen := if scope_flat_bundle { |
| 821 | cleanc.Gen.new_with_env_pref_and_flat(&scoped_flat, b.env, b.pref) |
| 822 | } else if b.flat.files.len > 0 { |
| 823 | cleanc.Gen.new_with_env_pref_and_flat(&b.flat, b.env, b.pref) |
| 824 | } else { |
| 825 | cleanc.Gen.new_with_env_and_pref(gen_files, b.env, b.pref) |
| 826 | } |
| 827 | if modules.len > 0 { |
| 828 | gen.set_emit_modules(modules) |
| 829 | } |
| 830 | if type_modules.len > 0 { |
| 831 | gen.set_type_modules(type_modules) |
| 832 | } |
| 833 | if emit_files.len > 0 { |
| 834 | gen.set_emit_files(emit_files) |
| 835 | } |
| 836 | if use_markused && b.used_fn_keys.len > 0 { |
| 837 | gen.set_used_fn_keys(b.used_fn_keys) |
| 838 | } |
| 839 | if force_emit_fn_names.len > 0 { |
| 840 | gen.set_force_emit_fn_names(force_emit_fn_names) |
| 841 | } |
| 842 | gen.set_export_const_symbols(export_const_symbols) |
| 843 | if cache_bundle_name.len > 0 { |
| 844 | gen.set_cache_bundle_name(cache_bundle_name) |
| 845 | } |
| 846 | if cached_init_calls.len > 0 { |
| 847 | gen.set_cached_init_calls(cached_init_calls) |
| 848 | } |
| 849 | if cache_bundle_name.len == 0 && cached_init_calls.len > 0 && b.cached_called_fn_names.len > 0 { |
| 850 | gen.add_called_fn_names(b.cached_called_fn_name_list()) |
| 851 | } |
| 852 | if cache_bundle_name.len == 0 && cached_init_calls.len > 0 |
| 853 | && b.has_v2compiler_generic_setup_snapshot { |
| 854 | gen.use_generic_setup_snapshot(b.v2compiler_generic_setup_snapshot) |
| 855 | } |
| 856 | use_parallel := b.pref != unsafe { nil } && !b.pref.no_parallel |
| 857 | if use_parallel { |
| 858 | gen.gen_passes_1_to_4() |
| 859 | if cache_bundle_name == v2compiler_cache_name { |
| 860 | b.v2compiler_generic_setup_snapshot = gen.generic_setup_snapshot() |
| 861 | b.has_v2compiler_generic_setup_snapshot = true |
| 862 | } |
| 863 | b.gen_cleanc_parallel(mut gen) |
| 864 | source := gen.gen_finalize() |
| 865 | if cache_bundle_name.len > 0 { |
| 866 | b.add_cached_called_fn_names(gen.external_called_fn_names()) |
| 867 | } |
| 868 | if os.getenv('V2TRACE_CLEANC') != '' { |
| 869 | eprintln('TRACE_CLEANC builder_files=${b.files.len} gen_files=${gen_files.len} source_len=${source.len}') |
| 870 | } |
| 871 | return source |
| 872 | } |
| 873 | source := gen.gen() |
| 874 | if cache_bundle_name == v2compiler_cache_name { |
| 875 | b.v2compiler_generic_setup_snapshot = gen.generic_setup_snapshot() |
| 876 | b.has_v2compiler_generic_setup_snapshot = true |
| 877 | } |
| 878 | if cache_bundle_name.len > 0 { |
| 879 | b.add_cached_called_fn_names(gen.external_called_fn_names()) |
| 880 | } |
| 881 | if os.getenv('V2TRACE_CLEANC') != '' { |
| 882 | eprintln('TRACE_CLEANC builder_files=${b.files.len} gen_files=${gen_files.len} source_len=${source.len}') |
| 883 | } |
| 884 | return source |
| 885 | } |
| 886 | |
| 887 | fn (mut b Builder) add_cached_called_fn_names(names []string) { |
| 888 | for name in names { |
| 889 | if name.len > 0 { |
| 890 | b.cached_called_fn_names[name] = true |
| 891 | } |
| 892 | } |
| 893 | } |
| 894 | |
| 895 | fn (b &Builder) cached_called_fn_name_list() []string { |
| 896 | mut names := b.cached_called_fn_names.keys() |
| 897 | names.sort() |
| 898 | return names |
| 899 | } |
| 900 | |
| 901 | fn cached_called_fn_names_path(cache_dir string, cache_name string) string { |
| 902 | return cache_path_join(cache_dir, '${cache_name}.calls') |
| 903 | } |
| 904 | |
| 905 | fn (mut b Builder) load_cached_called_fn_names(cache_dir string, cache_name string) bool { |
| 906 | data := os.read_file(cached_called_fn_names_path(cache_dir, cache_name)) or { return false } |
| 907 | for line in data.split_into_lines() { |
| 908 | name := line.trim_space() |
| 909 | if name.len > 0 { |
| 910 | b.cached_called_fn_names[name] = true |
| 911 | } |
| 912 | } |
| 913 | return true |
| 914 | } |
| 915 | |
| 916 | fn (mut b Builder) write_cached_called_fn_names(cache_dir string, cache_name string, before map[string]bool) { |
| 917 | mut names := []string{} |
| 918 | for name, _ in b.cached_called_fn_names { |
| 919 | if name.len == 0 || name in before { |
| 920 | continue |
| 921 | } |
| 922 | names << name |
| 923 | } |
| 924 | names.sort() |
| 925 | os.write_file(cached_called_fn_names_path(cache_dir, cache_name), names.join('\n')) or {} |
| 926 | } |
| 927 | |
| 928 | fn (b &Builder) cgen_builder_stats_enabled() bool { |
| 929 | return b.pref != unsafe { nil } && b.pref.stats |
| 930 | } |
| 931 | |
| 932 | fn (b &Builder) mark_cgen_builder_step(stats_enabled bool, cache_name string, mut sw time.StopWatch, stage_start time.Duration, step string) time.Duration { |
| 933 | if !stats_enabled { |
| 934 | return stage_start |
| 935 | } |
| 936 | now := sw.elapsed() |
| 937 | println(' - C Gen/cache:${cache_name} cache.${step}: ${time.Duration(now - stage_start).milliseconds()}ms') |
| 938 | return now |
| 939 | } |
| 940 | |
| 941 | fn cache_type_module_names(cache_bundle_name string, emit_modules []string) []string { |
| 942 | if cache_bundle_name == '' { |
| 943 | return emit_modules |
| 944 | } |
| 945 | mut names := []string{cap: core_cached_module_names.len + emit_modules.len} |
| 946 | if cache_bundle_name == builtin_cache_name { |
| 947 | names << emit_modules |
| 948 | } else if cache_bundle_name == vlib_cache_name { |
| 949 | names << builtin_cached_module_names |
| 950 | names << emit_modules |
| 951 | } else { |
| 952 | names << core_cached_module_names |
| 953 | names << emit_modules |
| 954 | } |
| 955 | return unique_sorted_strings(names) |
| 956 | } |
| 957 | |
| 958 | fn (b &Builder) expand_type_modules_with_imports(modules []string) []string { |
| 959 | mut type_modules := map[string]bool{} |
| 960 | for module_name in modules { |
| 961 | if module_name != '' { |
| 962 | type_modules[module_name] = true |
| 963 | } |
| 964 | } |
| 965 | use_flat := b.uses_flat_module_enumeration() |
| 966 | mut changed := true |
| 967 | for changed { |
| 968 | changed = false |
| 969 | if use_flat { |
| 970 | for i in 0 .. b.flat.files.len { |
| 971 | if b.flat_file_module_name(i) !in type_modules { |
| 972 | continue |
| 973 | } |
| 974 | for import_stmt in b.flat.file_cursor(i).imports().import_stmts() { |
| 975 | import_module := import_module_name(import_stmt.name) |
| 976 | if import_module == '' || import_module in type_modules { |
| 977 | continue |
| 978 | } |
| 979 | type_modules[import_module] = true |
| 980 | changed = true |
| 981 | } |
| 982 | } |
| 983 | } else { |
| 984 | for file in b.files { |
| 985 | if ast_file_module_name(file) !in type_modules { |
| 986 | continue |
| 987 | } |
| 988 | for import_stmt in file.imports { |
| 989 | import_module := import_module_name(import_stmt.name) |
| 990 | if import_module == '' || import_module in type_modules { |
| 991 | continue |
| 992 | } |
| 993 | type_modules[import_module] = true |
| 994 | changed = true |
| 995 | } |
| 996 | } |
| 997 | } |
| 998 | } |
| 999 | return unique_sorted_strings(type_modules.keys()) |
| 1000 | } |
| 1001 | |
| 1002 | fn (b &Builder) has_external_cache_module_name_collision(module_names []string) bool { |
| 1003 | mut module_set := map[string]bool{} |
| 1004 | for module_name in module_names { |
| 1005 | if module_name != '' { |
| 1006 | module_set[module_name] = true |
| 1007 | } |
| 1008 | } |
| 1009 | mut vlib_modules := map[string]bool{} |
| 1010 | mut external_modules := map[string]bool{} |
| 1011 | if b.uses_flat_module_enumeration() { |
| 1012 | for i in 0 .. b.flat.files.len { |
| 1013 | name := b.flat.file_name(b.flat.files[i]) |
| 1014 | if name == '' || name.ends_with('.vh') { |
| 1015 | continue |
| 1016 | } |
| 1017 | module_name := b.flat_file_module_name(i) |
| 1018 | if module_name !in module_set { |
| 1019 | continue |
| 1020 | } |
| 1021 | if b.is_vlib_source_file(name) { |
| 1022 | vlib_modules[module_name] = true |
| 1023 | } else { |
| 1024 | external_modules[module_name] = true |
| 1025 | } |
| 1026 | } |
| 1027 | } else { |
| 1028 | for file in b.files { |
| 1029 | if file.name == '' || file.name.ends_with('.vh') { |
| 1030 | continue |
| 1031 | } |
| 1032 | module_name := ast_file_module_name(file) |
| 1033 | if module_name !in module_set { |
| 1034 | continue |
| 1035 | } |
| 1036 | if b.is_vlib_source_file(file.name) { |
| 1037 | vlib_modules[module_name] = true |
| 1038 | } else { |
| 1039 | external_modules[module_name] = true |
| 1040 | } |
| 1041 | } |
| 1042 | } |
| 1043 | for module_name, _ in external_modules { |
| 1044 | if module_name in vlib_modules { |
| 1045 | return true |
| 1046 | } |
| 1047 | } |
| 1048 | return false |
| 1049 | } |
| 1050 | |
| 1051 | fn (b &Builder) is_vlib_source_file(file_name string) bool { |
| 1052 | root := if b.pref.vroot.len > 0 { b.pref.vroot } else { os.getwd() } |
| 1053 | vlib_root := os.norm_path(os.join_path(root, 'vlib')) + os.path_separator |
| 1054 | return os.norm_path(os.abs_path(file_name)).starts_with(vlib_root) |
| 1055 | } |
| 1056 | |
| 1057 | fn import_module_name(name string) string { |
| 1058 | return name.all_after_last('.').replace('.', '_') |
| 1059 | } |
| 1060 | |
| 1061 | fn unique_sorted_strings(items []string) []string { |
| 1062 | mut seen := map[string]bool{} |
| 1063 | mut out := []string{cap: items.len} |
| 1064 | for item in items { |
| 1065 | if item == '' || item in seen { |
| 1066 | continue |
| 1067 | } |
| 1068 | seen[item] = true |
| 1069 | out << item |
| 1070 | } |
| 1071 | out.sort() |
| 1072 | return out |
| 1073 | } |
| 1074 | |
| 1075 | fn (mut b Builder) gen_cleanc_with_cached_core(output_name string, cc string, cc_flags string, cc_link_flags string, error_limit_flag string, mut sw time.StopWatch) bool { |
| 1076 | cache_dir := b.core_cache_dir() |
| 1077 | if !b.ensure_core_cache_dir() { |
| 1078 | // If we cannot create a readable/writable cache dir, fall back to full compilation. |
| 1079 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1080 | eprintln('TRACE_CACHE cached_core=false reason=cache_dir_unusable') |
| 1081 | } |
| 1082 | return false |
| 1083 | } |
| 1084 | if b.try_link_cached_self_main_object(output_name, cache_dir, cc, cc_flags, cc_link_flags, mut |
| 1085 | sw) |
| 1086 | { |
| 1087 | return true |
| 1088 | } |
| 1089 | |
| 1090 | builtin_obj := b.ensure_cached_module_object(cache_dir, builtin_cache_name, |
| 1091 | builtin_cached_module_paths, builtin_cached_module_names, cc, cc_flags, '', |
| 1092 | error_limit_flag, false) or { |
| 1093 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1094 | eprintln('TRACE_CACHE cached_core=false reason=builtin_obj_failed') |
| 1095 | } |
| 1096 | return false |
| 1097 | } |
| 1098 | b.print_cached_bundle_modules(builtin_cache_name, builtin_cached_module_names) |
| 1099 | mut vlib_obj := '' |
| 1100 | if vlib_cached_module_paths.len > 0 { |
| 1101 | vlib_obj = b.ensure_cached_module_object(cache_dir, vlib_cache_name, |
| 1102 | vlib_cached_module_paths, vlib_cached_module_names, cc, cc_flags, '', error_limit_flag, |
| 1103 | false) or { |
| 1104 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1105 | eprintln('TRACE_CACHE cached_core=false reason=vlib_obj_failed') |
| 1106 | } |
| 1107 | return false |
| 1108 | } |
| 1109 | if vlib_obj.len > 0 { |
| 1110 | b.print_cached_bundle_modules(vlib_cache_name, vlib_cached_module_names) |
| 1111 | } |
| 1112 | } |
| 1113 | mut optional_cached_objs := []string{} |
| 1114 | mut optional_cached_cache_names := []string{} |
| 1115 | mut optional_cached_module_names := []string{} |
| 1116 | if veb_cached_module_paths.len > 0 && b.has_module('veb') { |
| 1117 | veb_cache_type_modules := b.expand_type_modules_with_imports(cache_type_module_names(veb_cache_name, |
| 1118 | veb_cached_module_names)) |
| 1119 | veb_obj := if b.has_external_cache_module_name_collision(veb_cache_type_modules) { |
| 1120 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1121 | eprintln('TRACE_CACHE optional_cache=veb reason=external_module_name_collision') |
| 1122 | } |
| 1123 | '' |
| 1124 | } else { |
| 1125 | b.ensure_cached_module_object(cache_dir, veb_cache_name, veb_cached_module_paths, |
| 1126 | veb_cached_module_names, cc, cc_flags, cc_link_flags, error_limit_flag, true) or { |
| 1127 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1128 | eprintln('TRACE_CACHE optional_cache=veb reason=${err}') |
| 1129 | } |
| 1130 | '' |
| 1131 | } |
| 1132 | } |
| 1133 | if veb_obj.len > 0 { |
| 1134 | b.print_cached_bundle_modules(veb_cache_name, veb_cached_module_names) |
| 1135 | optional_cached_objs << veb_obj |
| 1136 | optional_cached_cache_names << veb_cache_name |
| 1137 | optional_cached_module_names << veb_cached_module_names |
| 1138 | } |
| 1139 | } |
| 1140 | mut v2compiler_obj := '' |
| 1141 | if v2compiler_cached_module_paths.len > 0 && b.is_cmd_v2_self_build() { |
| 1142 | v2compiler_obj = b.ensure_cached_module_object(cache_dir, v2compiler_cache_name, |
| 1143 | v2compiler_cached_module_paths, v2compiler_cached_module_names, cc, cc_flags, '', |
| 1144 | error_limit_flag, false) or { |
| 1145 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1146 | eprintln('TRACE_CACHE cached_core=false reason=v2compiler_obj_failed') |
| 1147 | } |
| 1148 | return false |
| 1149 | } |
| 1150 | if v2compiler_obj.len > 0 { |
| 1151 | b.print_cached_bundle_modules(v2compiler_cache_name, v2compiler_cached_module_names) |
| 1152 | } |
| 1153 | } |
| 1154 | mut dynamic_excluded := core_cached_module_names.clone() |
| 1155 | for module_name in optional_cached_module_names { |
| 1156 | dynamic_excluded << module_name |
| 1157 | } |
| 1158 | entry_module_names := b.user_entry_module_names() |
| 1159 | dynamic_excluded << entry_module_names |
| 1160 | if b.is_cmd_v2_self_build() { |
| 1161 | dynamic_excluded << v2compiler_cached_module_names |
| 1162 | } |
| 1163 | dynamic_excluded << 'main' |
| 1164 | dynamic_cached_module_names := b.collect_modules_excluding(dynamic_excluded) |
| 1165 | if dynamic_cached_module_names.len > 0 { |
| 1166 | mut import_dependency_cache_names := [builtin_cache_name] |
| 1167 | if vlib_obj.len > 0 { |
| 1168 | import_dependency_cache_names << vlib_cache_name |
| 1169 | } |
| 1170 | if v2compiler_obj.len > 0 { |
| 1171 | import_dependency_cache_names << v2compiler_cache_name |
| 1172 | } |
| 1173 | for cache_name in optional_cached_cache_names { |
| 1174 | import_dependency_cache_names << cache_name |
| 1175 | } |
| 1176 | import_dependency_compile_flags, import_dependency_link_flags := |
| 1177 | b.cached_module_stamp_flags(import_dependency_cache_names) |
| 1178 | imports_cc_flags := join_flag_strings(cc_flags, import_dependency_compile_flags) |
| 1179 | imports_cc_link_flags := join_flag_strings(cc_link_flags, import_dependency_link_flags) |
| 1180 | imports_obj := b.ensure_cached_parsed_module_object(cache_dir, imports_cache_name, |
| 1181 | dynamic_cached_module_names, import_dependency_cache_names, cc, imports_cc_flags, |
| 1182 | imports_cc_link_flags, error_limit_flag, true) or { |
| 1183 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1184 | eprintln('TRACE_CACHE optional_cache=imports reason=${err}') |
| 1185 | } |
| 1186 | if b.used_import_vh_for_parse { |
| 1187 | return false |
| 1188 | } |
| 1189 | '' |
| 1190 | } |
| 1191 | if imports_obj.len > 0 { |
| 1192 | b.print_cached_bundle_modules(imports_cache_name, dynamic_cached_module_names) |
| 1193 | optional_cached_objs << imports_obj |
| 1194 | optional_cached_cache_names << imports_cache_name |
| 1195 | optional_cached_module_names << dynamic_cached_module_names |
| 1196 | } |
| 1197 | } |
| 1198 | mut virtual_groups := if b.used_virtual_vh_for_parse { |
| 1199 | b.cached_virtual_manifest() |
| 1200 | } else { |
| 1201 | b.collect_virtual_main_modules() |
| 1202 | } |
| 1203 | mut virtual_source_files := []string{} |
| 1204 | mut virtuals_obj := '' |
| 1205 | if virtual_groups.len > 0 { |
| 1206 | mut virtual_dependency_cache_names := [builtin_cache_name] |
| 1207 | if vlib_obj.len > 0 { |
| 1208 | virtual_dependency_cache_names << vlib_cache_name |
| 1209 | } |
| 1210 | if v2compiler_obj.len > 0 { |
| 1211 | virtual_dependency_cache_names << v2compiler_cache_name |
| 1212 | } |
| 1213 | for cache_name in optional_cached_cache_names { |
| 1214 | virtual_dependency_cache_names << cache_name |
| 1215 | } |
| 1216 | virtual_dependency_compile_flags, virtual_dependency_link_flags := |
| 1217 | b.cached_module_stamp_flags(virtual_dependency_cache_names) |
| 1218 | virtual_cc_flags := join_flag_strings(cc_flags, virtual_dependency_compile_flags) |
| 1219 | virtual_cc_link_flags := join_flag_strings(cc_link_flags, virtual_dependency_link_flags) |
| 1220 | virtuals_obj = b.ensure_cached_virtual_module_object(cache_dir, virtual_groups, |
| 1221 | virtual_dependency_cache_names, cc, virtual_cc_flags, virtual_cc_link_flags, |
| 1222 | error_limit_flag, true) or { |
| 1223 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1224 | eprintln('TRACE_CACHE optional_cache=${virtuals_cache_name} reason=${err}') |
| 1225 | } |
| 1226 | if b.used_virtual_vh_for_parse { |
| 1227 | return false |
| 1228 | } |
| 1229 | virtual_groups = []CachedVirtualModule{} |
| 1230 | '' |
| 1231 | } |
| 1232 | if virtuals_obj.len > 0 { |
| 1233 | virtual_source_files = virtual_module_source_files(virtual_groups) |
| 1234 | b.print_cached_bundle_modules(virtuals_cache_name, virtual_module_names(virtual_groups)) |
| 1235 | } |
| 1236 | } |
| 1237 | b.ensure_core_module_headers() |
| 1238 | b.ensure_import_module_headers(dynamic_cached_module_names) |
| 1239 | // The v2compiler .vh headers are read back only by can_use_cached_v2compiler_headers_for_parse |
| 1240 | // (via cached_import_parse_path). That parse-reuse path was disabled because the generated |
| 1241 | // headers are not yet complete/safe, so the 21 module headers are write-only on every cold |
| 1242 | // self-build — ~230ms of pure overhead. Skip generation until reuse is re-enabled (the headers |
| 1243 | // are regenerated on demand the moment it is; see v2compiler_headers_consumed_for_parse). |
| 1244 | if v2compiler_obj.len > 0 && b.v2compiler_headers_consumed_for_parse() { |
| 1245 | b.ensure_v2compiler_module_headers() |
| 1246 | } |
| 1247 | b.ensure_virtual_module_headers(virtual_groups) |
| 1248 | mut excluded := core_cached_module_names.clone() |
| 1249 | for module_name in optional_cached_module_names { |
| 1250 | excluded << module_name |
| 1251 | } |
| 1252 | if b.is_cmd_v2_self_build() { |
| 1253 | excluded << v2compiler_cached_module_names |
| 1254 | } |
| 1255 | main_modules := b.collect_modules_excluding(excluded) |
| 1256 | if main_modules.len == 0 { |
| 1257 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1258 | eprintln('TRACE_CACHE cached_core=false reason=no_main_modules') |
| 1259 | } |
| 1260 | return false |
| 1261 | } |
| 1262 | mut linked_cache_names := [builtin_cache_name] |
| 1263 | if vlib_obj.len > 0 { |
| 1264 | linked_cache_names << vlib_cache_name |
| 1265 | } |
| 1266 | if v2compiler_obj.len > 0 { |
| 1267 | linked_cache_names << v2compiler_cache_name |
| 1268 | } |
| 1269 | for cache_name in optional_cached_cache_names { |
| 1270 | linked_cache_names << cache_name |
| 1271 | } |
| 1272 | cached_compile_flags, cached_link_flags := b.cached_module_stamp_flags(linked_cache_names) |
| 1273 | |
| 1274 | // When TCC is the default compiler but fell back to cc for cache |
| 1275 | // compilation (e.g. due to TCC not supporting certain C constructs), |
| 1276 | // the cached .o files are Mach-O (from cc) while TCC would produce ELF. |
| 1277 | // Detect this mismatch and use cc for main compilation and linking too. |
| 1278 | // Also, -prod builds with -flto require gcc/clang for linking — TCC |
| 1279 | // cannot link LTO object files. |
| 1280 | mut main_cc := cc |
| 1281 | mut main_cc_flags := join_flag_strings(cc_flags, cached_compile_flags) |
| 1282 | main_cc_link_flags := join_flag_strings(cc_link_flags, cached_link_flags) |
| 1283 | if cc.contains('tcc') && os.exists(builtin_obj) { |
| 1284 | bytes := os.read_bytes(builtin_obj) or { []u8{} } |
| 1285 | is_elf := bytes.len >= 4 && bytes[0] == 0x7f && bytes[1] == 0x45 && bytes[2] == 0x4c |
| 1286 | && bytes[3] == 0x46 |
| 1287 | if !is_elf { |
| 1288 | // Cached .o was compiled by cc (via TCC fallback), not TCC. |
| 1289 | // Use cc for main compilation and linking to match formats. |
| 1290 | // Keep all flags (including directive -I paths) but strip |
| 1291 | // TCC-specific -I/-L paths that would conflict with system headers. |
| 1292 | main_cc = 'cc' |
| 1293 | tcc_dir2 := cc.all_before_last('/tcc') |
| 1294 | if tcc_dir2.len > 0 { |
| 1295 | mut parts := main_cc_flags.fields() |
| 1296 | mut filtered := []string{cap: parts.len} |
| 1297 | mut j := 0 |
| 1298 | for j < parts.len { |
| 1299 | p := parts[j] |
| 1300 | if (p == '-I' || p == '-L') && j + 1 < parts.len && parts[j + 1].contains('tcc') { |
| 1301 | j += 2 |
| 1302 | continue |
| 1303 | } |
| 1304 | if (p.starts_with('-I') || p.starts_with('-L')) && p.contains('tcc') { |
| 1305 | j++ |
| 1306 | continue |
| 1307 | } |
| 1308 | // Strip quoted -I/-L containing tcc |
| 1309 | if (p.starts_with('-I"') || p.starts_with('-L"') |
| 1310 | || p.starts_with("-I'") || p.starts_with("-L'")) && p.contains('tcc') { |
| 1311 | j++ |
| 1312 | continue |
| 1313 | } |
| 1314 | filtered << p |
| 1315 | j++ |
| 1316 | } |
| 1317 | main_cc_flags = filtered.join(' ') |
| 1318 | } |
| 1319 | } |
| 1320 | } |
| 1321 | |
| 1322 | mut cached_init_calls := []string{} |
| 1323 | cached_init_calls << '__v2_cached_init_${builtin_cache_name}' |
| 1324 | if vlib_obj.len > 0 { |
| 1325 | cached_init_calls << '__v2_cached_init_${vlib_cache_name}' |
| 1326 | } |
| 1327 | if v2compiler_obj.len > 0 { |
| 1328 | cached_init_calls << '__v2_cached_init_${v2compiler_cache_name}' |
| 1329 | } |
| 1330 | for cache_name in optional_cached_cache_names { |
| 1331 | cached_init_calls << '__v2_cached_init_${cache_name}' |
| 1332 | } |
| 1333 | all_main_emit_files := if virtual_source_files.len > 0 { |
| 1334 | filter_out_source_files(b.module_source_files(main_modules), virtual_source_files) |
| 1335 | } else { |
| 1336 | []string{} |
| 1337 | } |
| 1338 | use_self_main_cache := b.should_skip_markused_for_self_build() && !b.pref.keep_c |
| 1339 | self_main_obj := cache_path_join(cache_dir, 'main.o') |
| 1340 | self_main_c := cache_path_join(cache_dir, 'main.c') |
| 1341 | self_main_stamp := cache_path_join(cache_dir, 'main.stamp') |
| 1342 | self_main_expected_stamp := if use_self_main_cache { |
| 1343 | b.cache_stamp_for_self_main_object(main_modules, all_main_emit_files, linked_cache_names, |
| 1344 | main_cc, main_cc_flags, main_cc_link_flags, cached_init_calls) |
| 1345 | } else { |
| 1346 | '' |
| 1347 | } |
| 1348 | if use_self_main_cache && os.exists(self_main_obj) && os.exists(self_main_stamp) { |
| 1349 | if current_stamp := os.read_file(self_main_stamp) { |
| 1350 | if current_stamp == self_main_expected_stamp { |
| 1351 | print_time('C Gen', sw.elapsed()) |
| 1352 | if os.getenv('V2VERBOSE') != '' { |
| 1353 | println('[*] Reusing ${self_main_obj}') |
| 1354 | } |
| 1355 | b.link_cleanc_cached_core_executable(output_name, main_cc, main_cc_flags, |
| 1356 | main_cc_link_flags, self_main_obj, builtin_obj, vlib_obj, v2compiler_obj, |
| 1357 | optional_cached_objs, virtuals_obj, mut sw) |
| 1358 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1359 | eprintln('TRACE_CACHE cached_core=true main_obj_cache=true') |
| 1360 | } |
| 1361 | return true |
| 1362 | } |
| 1363 | } |
| 1364 | } |
| 1365 | mut main_source := if all_main_emit_files.len > 0 { |
| 1366 | b.gen_cleanc_source_with_cache_init_calls_files_force(main_modules, all_main_emit_files, |
| 1367 | cached_init_calls, true, []string{}) |
| 1368 | } else { |
| 1369 | b.gen_cleanc_source_with_cache_init_calls(main_modules, cached_init_calls) |
| 1370 | } |
| 1371 | print_time('C Gen', sw.elapsed()) |
| 1372 | if main_source == '' { |
| 1373 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1374 | eprintln('TRACE_CACHE cached_core=false reason=empty_main_source') |
| 1375 | } |
| 1376 | return false |
| 1377 | } |
| 1378 | |
| 1379 | main_c_file := if use_self_main_cache { self_main_c } else { b.exec_build_c_file(output_name) } |
| 1380 | os.write_file(main_c_file, main_source) or { return false } |
| 1381 | if !use_self_main_cache && main_c_file != staged_c_file { |
| 1382 | os.write_file(staged_c_file, main_source) or { return false } |
| 1383 | } |
| 1384 | println('[*] Wrote ${main_c_file}') |
| 1385 | |
| 1386 | main_tmp_obj := cache_path_join(cache_dir, 'main.tmp.o') |
| 1387 | main_obj := if use_self_main_cache { main_tmp_obj } else { staged_main_obj_file } |
| 1388 | compile_main_cmd := '${main_cc} ${main_cc_flags} -w -Wno-incompatible-function-pointer-types -c "${main_c_file}" -o "${main_obj}"${error_limit_flag}' |
| 1389 | main_fell_back := run_cc_cmd_or_exit(compile_main_cmd, 'C compilation', b.pref.show_cc) |
| 1390 | if main_fell_back && main_cc.contains('tcc') { |
| 1391 | // TCC failed on main.c but cached .o files are ELF (from TCC). |
| 1392 | // Fallback produced Mach-O main.o — can't link with ELF cache. |
| 1393 | // Fall back to non-cached full compilation. |
| 1394 | os.rm(main_obj) or {} |
| 1395 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1396 | eprintln('TRACE_CACHE cached_core=false reason=main_compile_fell_back') |
| 1397 | } |
| 1398 | return false |
| 1399 | } |
| 1400 | mut link_main_obj := main_obj |
| 1401 | if use_self_main_cache { |
| 1402 | os.rm(self_main_obj) or {} |
| 1403 | os.mv(main_obj, self_main_obj) or { return false } |
| 1404 | os.write_file(self_main_stamp, self_main_expected_stamp) or { return false } |
| 1405 | link_main_obj = self_main_obj |
| 1406 | } |
| 1407 | b.link_cleanc_cached_core_executable(output_name, main_cc, main_cc_flags, main_cc_link_flags, |
| 1408 | link_main_obj, builtin_obj, vlib_obj, v2compiler_obj, optional_cached_objs, virtuals_obj, mut |
| 1409 | sw) |
| 1410 | |
| 1411 | if !b.pref.keep_c && !use_self_main_cache { |
| 1412 | os.rm(main_obj) or {} |
| 1413 | os.rm(main_c_file) or {} |
| 1414 | } |
| 1415 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1416 | eprintln('TRACE_CACHE cached_core=true') |
| 1417 | } |
| 1418 | return true |
| 1419 | } |
| 1420 | |
| 1421 | fn (mut b Builder) try_link_cached_self_main_object(output_name string, cache_dir string, cc string, cc_flags string, cc_link_flags string, mut sw time.StopWatch) bool { |
| 1422 | if !b.should_skip_markused_for_self_build() || b.pref.keep_c { |
| 1423 | return false |
| 1424 | } |
| 1425 | linked_cache_names := [builtin_cache_name, vlib_cache_name, v2compiler_cache_name, |
| 1426 | imports_cache_name] |
| 1427 | for cache_name in linked_cache_names { |
| 1428 | stamp_path := cache_path_join(cache_dir, '${cache_name}.stamp') |
| 1429 | if !os.exists(cache_path_join(cache_dir, '${cache_name}.o')) || !os.exists(stamp_path) { |
| 1430 | return false |
| 1431 | } |
| 1432 | stamp := os.read_file(stamp_path) or { return false } |
| 1433 | if !stamp_file_lines_are_fresh(stamp) { |
| 1434 | return false |
| 1435 | } |
| 1436 | } |
| 1437 | self_main_obj := cache_path_join(cache_dir, 'main.o') |
| 1438 | self_main_stamp := cache_path_join(cache_dir, 'main.stamp') |
| 1439 | if !os.exists(self_main_obj) || !os.exists(self_main_stamp) { |
| 1440 | return false |
| 1441 | } |
| 1442 | cached_compile_flags, cached_link_flags := b.cached_module_stamp_flags(linked_cache_names) |
| 1443 | main_cc := cc |
| 1444 | main_cc_flags := join_flag_strings(cc_flags, cached_compile_flags) |
| 1445 | main_cc_link_flags := join_flag_strings(cc_link_flags, cached_link_flags) |
| 1446 | if main_cc.contains('tcc') { |
| 1447 | builtin_obj := cache_path_join(cache_dir, '${builtin_cache_name}.o') |
| 1448 | bytes := os.read_bytes(builtin_obj) or { return false } |
| 1449 | is_elf := bytes.len >= 4 && bytes[0] == 0x7f && bytes[1] == 0x45 && bytes[2] == 0x4c |
| 1450 | && bytes[3] == 0x46 |
| 1451 | if !is_elf { |
| 1452 | return false |
| 1453 | } |
| 1454 | } |
| 1455 | cached_init_calls := [ |
| 1456 | '__v2_cached_init_${builtin_cache_name}', |
| 1457 | '__v2_cached_init_${vlib_cache_name}', |
| 1458 | '__v2_cached_init_${v2compiler_cache_name}', |
| 1459 | '__v2_cached_init_${imports_cache_name}', |
| 1460 | ] |
| 1461 | expected_stamp := b.cache_stamp_for_self_main_object(['main'], []string{}, linked_cache_names, |
| 1462 | main_cc, main_cc_flags, main_cc_link_flags, cached_init_calls) |
| 1463 | current_stamp := os.read_file(self_main_stamp) or { return false } |
| 1464 | if current_stamp != expected_stamp { |
| 1465 | return false |
| 1466 | } |
| 1467 | print_time('C Gen', sw.elapsed()) |
| 1468 | if os.getenv('V2VERBOSE') != '' { |
| 1469 | println('[*] Reusing ${self_main_obj}') |
| 1470 | } |
| 1471 | b.link_cleanc_cached_core_executable(output_name, main_cc, main_cc_flags, main_cc_link_flags, |
| 1472 | self_main_obj, cache_path_join(cache_dir, '${builtin_cache_name}.o'), cache_path_join(cache_dir, |
| 1473 | '${vlib_cache_name}.o'), cache_path_join(cache_dir, '${v2compiler_cache_name}.o'), [ |
| 1474 | cache_path_join(cache_dir, '${imports_cache_name}.o'), |
| 1475 | ], '', mut sw) |
| 1476 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1477 | eprintln('TRACE_CACHE cached_core=true main_obj_cache=early') |
| 1478 | } |
| 1479 | return true |
| 1480 | } |
| 1481 | |
| 1482 | fn (mut b Builder) link_cleanc_cached_core_executable(output_name string, main_cc string, main_cc_flags string, main_cc_link_flags string, main_obj string, builtin_obj string, vlib_obj string, v2compiler_obj string, optional_cached_objs []string, virtuals_obj string, mut sw time.StopWatch) { |
| 1483 | cc_start := sw.elapsed() |
| 1484 | // Strip -c and -x flags from link command since we're linking, not compiling. |
| 1485 | // -x objective-c would cause cc to treat .o files as source code. |
| 1486 | mut link_flags := |
| 1487 | main_cc_flags.replace('-x objective-c', '').replace('-x c', '').replace(' -c ', ' ') |
| 1488 | mut link_cmd := '${main_cc} ${link_flags} -w "${main_obj}" "${builtin_obj}"' |
| 1489 | if vlib_obj.len > 0 { |
| 1490 | link_cmd += ' "${vlib_obj}"' |
| 1491 | } |
| 1492 | if v2compiler_obj.len > 0 { |
| 1493 | link_cmd += ' "${v2compiler_obj}"' |
| 1494 | } |
| 1495 | for obj in optional_cached_objs { |
| 1496 | link_cmd += ' "${obj}"' |
| 1497 | } |
| 1498 | if virtuals_obj.len > 0 { |
| 1499 | link_cmd += ' "${virtuals_obj}"' |
| 1500 | } |
| 1501 | link_cmd += ' -o "${output_name}"' |
| 1502 | if main_cc_link_flags.len > 0 { |
| 1503 | link_cmd += ' ${main_cc_link_flags}' |
| 1504 | } |
| 1505 | run_cc_cmd_or_exit(link_cmd, 'Linking', b.pref.show_cc) |
| 1506 | print_time('CC', time.Duration(sw.elapsed() - cc_start)) |
| 1507 | println('[*] Compiled ${output_name}') |
| 1508 | } |
| 1509 | |
| 1510 | // --------------------------------------------------------------------------- |
| 1511 | // Durable persistent object cache (survives a /tmp obj-cache wipe). |
| 1512 | // --------------------------------------------------------------------------- |
| 1513 | // core_cache_dir() lives under os.temp_dir() and is what |
| 1514 | // `rm -rf /tmp/v2_cleanc_obj_cache_<root>` removes for a "cold" measurement. A |
| 1515 | // durable mirror under os.cache_dir() survives that wipe, so a cold self-host |
| 1516 | // build with UNCHANGED sources can restore the prebuilt bundle objects + main.o |
| 1517 | // and fast-relink instead of regenerating ~14MB of C. Correctness is enforced |
| 1518 | // entirely by the existing stamp checks (try_self_build_fast_relink below): a |
| 1519 | // restored object whose recorded source mtimes no longer match is ignored and |
| 1520 | // rebuilt, so the durable tier can never yield a stale binary. |
| 1521 | |
| 1522 | const self_build_persist_bundles = ['builtin', 'vlib', 'v2compiler', 'imports'] |
| 1523 | |
| 1524 | fn self_build_persist_file_names() []string { |
| 1525 | mut names := []string{cap: self_build_persist_bundles.len * 2 + 2} |
| 1526 | for name in self_build_persist_bundles { |
| 1527 | names << '${name}.o' |
| 1528 | names << '${name}.stamp' |
| 1529 | } |
| 1530 | names << 'main.o' |
| 1531 | names << 'main.stamp' |
| 1532 | return names |
| 1533 | } |
| 1534 | |
| 1535 | fn (b &Builder) durable_object_cache_dir() string { |
| 1536 | root := if b.pref.vroot.len > 0 { b.pref.vroot } else { os.getwd() } |
| 1537 | root_key := sanitize_cache_part(os.norm_path(os.abs_path(root))) |
| 1538 | base := if b.pref.is_prod { 'v2cleanc_persist_prod' } else { 'v2cleanc_persist' } |
| 1539 | return os.join_path(os.cache_dir(), base, root_key) |
| 1540 | } |
| 1541 | |
| 1542 | // copy_file_keep_mtime copies src->dst preserving src's mtime. Mtime preservation is |
| 1543 | // required because main.stamp records `dependency:<bundle>:<mtime of that .stamp file>`, |
| 1544 | // so a restored bundle .stamp must keep its original mtime or the relink probe misses. |
| 1545 | fn copy_file_keep_mtime(src string, dst string) ! { |
| 1546 | data := os.read_bytes(src)! |
| 1547 | os.write_file_array(dst, data)! |
| 1548 | mtime := os.file_last_mod_unix(src) |
| 1549 | os.utime(dst, mtime, mtime)! |
| 1550 | } |
| 1551 | |
| 1552 | fn (b &Builder) save_durable_object_cache(cache_dir string) { |
| 1553 | if !b.is_cmd_v2_self_build() || b.pref.no_cache || b.pref.keep_c { |
| 1554 | return |
| 1555 | } |
| 1556 | durable_dir := b.durable_object_cache_dir() |
| 1557 | os.mkdir_all(durable_dir, mode: 0o700) or { return } |
| 1558 | for name in self_build_persist_file_names() { |
| 1559 | src := cache_path_join(cache_dir, name) |
| 1560 | if !os.exists(src) { |
| 1561 | continue |
| 1562 | } |
| 1563 | copy_file_keep_mtime(src, cache_path_join(durable_dir, name)) or { continue } |
| 1564 | } |
| 1565 | } |
| 1566 | |
| 1567 | fn (b &Builder) restore_durable_object_cache(cache_dir string) { |
| 1568 | if !b.is_cmd_v2_self_build() || b.pref.no_cache { |
| 1569 | return |
| 1570 | } |
| 1571 | durable_dir := b.durable_object_cache_dir() |
| 1572 | if !os.is_dir(durable_dir) { |
| 1573 | return |
| 1574 | } |
| 1575 | for name in self_build_persist_file_names() { |
| 1576 | dst := cache_path_join(cache_dir, name) |
| 1577 | if os.exists(dst) { |
| 1578 | // A /tmp copy already exists — never overwrite it with the durable mirror. |
| 1579 | continue |
| 1580 | } |
| 1581 | src := cache_path_join(durable_dir, name) |
| 1582 | if !os.exists(src) { |
| 1583 | continue |
| 1584 | } |
| 1585 | copy_file_keep_mtime(src, dst) or { continue } |
| 1586 | } |
| 1587 | } |
| 1588 | |
| 1589 | // try_self_build_fast_relink is the PRE-PARSE fast path for cmd/v2 self-host. When the |
| 1590 | // cached bundle objects + main.o are present (restored from the durable tier if /tmp was |
| 1591 | // wiped) and every source/compiler file they were built from is still fresh, it relinks the |
| 1592 | // final binary directly and skips the entire front-end (parse + type-check + transform), |
| 1593 | // which otherwise runs unconditionally. Conservative by construction: any staleness fails a |
| 1594 | // freshness check and falls through to a normal build, so it can never emit a stale binary. |
| 1595 | fn (mut b Builder) try_self_build_fast_relink() bool { |
| 1596 | trace := os.getenv('V2_TRACE_CACHE') != '' |
| 1597 | if b.pref.backend != .cleanc || b.pref.no_cache || b.pref.keep_c || b.pref.skip_builtin { |
| 1598 | return false |
| 1599 | } |
| 1600 | if !b.is_cmd_v2_self_build() || !b.should_skip_markused_for_self_build() |
| 1601 | || b.should_disable_cleanc_cache() { |
| 1602 | if trace { |
| 1603 | eprintln('TRACE_CACHE fast_relink=false reason=guard self_build=${b.is_cmd_v2_self_build()} skip_mu=${b.should_skip_markused_for_self_build()} disable=${b.should_disable_cleanc_cache()}') |
| 1604 | } |
| 1605 | return false |
| 1606 | } |
| 1607 | // Mirror gen_cleanc()'s generation-only decision: a `.c` output, a target we |
| 1608 | // cannot compile locally, or a shared lib must go through normal C generation, |
| 1609 | // never a direct relink. is_cmd_v2_self_build() keys only on the input file, so |
| 1610 | // without this a warm-cache `-o foo.c cmd/v2/v2.v` would link an executable into |
| 1611 | // foo.c instead of writing C source. |
| 1612 | output_name := if b.pref.output_file != '' { |
| 1613 | b.pref.output_file |
| 1614 | } else if b.user_files.len > 0 { |
| 1615 | b.default_output_name() |
| 1616 | } else { |
| 1617 | 'out' |
| 1618 | } |
| 1619 | if b.fast_relink_output_is_generation_only(output_name) { |
| 1620 | if trace { |
| 1621 | eprintln('TRACE_CACHE fast_relink=false reason=generation_only out=${output_name}') |
| 1622 | } |
| 1623 | return false |
| 1624 | } |
| 1625 | if !b.ensure_core_cache_dir() { |
| 1626 | return false |
| 1627 | } |
| 1628 | cache_dir := b.core_cache_dir() |
| 1629 | b.restore_durable_object_cache(cache_dir) |
| 1630 | |
| 1631 | // Every bundle object + stamp must exist and be source-fresh. |
| 1632 | for name in self_build_persist_bundles { |
| 1633 | if !os.exists(cache_path_join(cache_dir, '${name}.o')) { |
| 1634 | if trace { |
| 1635 | eprintln('TRACE_CACHE fast_relink=false reason=missing_obj ${name}') |
| 1636 | } |
| 1637 | return false |
| 1638 | } |
| 1639 | stamp := os.read_file(cache_path_join(cache_dir, '${name}.stamp')) or { return false } |
| 1640 | if !stamp_file_lines_are_fresh(stamp) { |
| 1641 | if trace { |
| 1642 | eprintln('TRACE_CACHE fast_relink=false reason=stale_bundle ${name}') |
| 1643 | } |
| 1644 | return false |
| 1645 | } |
| 1646 | } |
| 1647 | main_obj := cache_path_join(cache_dir, 'main.o') |
| 1648 | main_stamp := os.read_file(cache_path_join(cache_dir, 'main.stamp')) or { return false } |
| 1649 | if !os.exists(main_obj) || !stamp_file_lines_are_fresh(main_stamp) { |
| 1650 | if trace { |
| 1651 | eprintln('TRACE_CACHE fast_relink=false reason=stale_main main_obj=${os.exists(main_obj)}') |
| 1652 | } |
| 1653 | return false |
| 1654 | } |
| 1655 | // Validate the non-file build keys + dependency-stamp mtimes, and read back the relink |
| 1656 | // flags. The recorded flags are trusted rather than recomputed (recomputing needs the |
| 1657 | // parsed AST); the mtime-freshness checks are what guarantee the binary is up to date. |
| 1658 | mut main_cc := '' |
| 1659 | mut main_cc_flags := '' |
| 1660 | mut main_cc_link_flags := '' |
| 1661 | mut saw_self_build := false |
| 1662 | mut saw_flag_fp := false |
| 1663 | cur_flag_fp := b.preparse_flag_fingerprint() |
| 1664 | for line in main_stamp.split_into_lines() { |
| 1665 | if line.starts_with('cc=') { |
| 1666 | main_cc = line['cc='.len..] |
| 1667 | } else if line.starts_with('cc_flags=') { |
| 1668 | main_cc_flags = line['cc_flags='.len..] |
| 1669 | } else if line.starts_with('cc_link_flags=') { |
| 1670 | main_cc_link_flags = line['cc_link_flags='.len..] |
| 1671 | } else if line.starts_with('flag_fp=') { |
| 1672 | // The recorded cc/cc_flags/cc_link_flags are trusted (recomputing the |
| 1673 | // source-derived parts needs the AST). This fingerprint covers the |
| 1674 | // flag inputs that DON'T need parsing — compiler choice, prod/shared |
| 1675 | // mode, env CFLAGS — so a changed build environment invalidates the |
| 1676 | // relink even when every source file is unchanged. |
| 1677 | if line['flag_fp='.len..] != cur_flag_fp { |
| 1678 | if trace { |
| 1679 | eprintln('TRACE_CACHE fast_relink=false reason=flag_fp') |
| 1680 | } |
| 1681 | return false |
| 1682 | } |
| 1683 | saw_flag_fp = true |
| 1684 | } else if line.starts_with('context_alloc=') { |
| 1685 | if line['context_alloc='.len..] != '${b.pref.use_context_allocator}' { |
| 1686 | return false |
| 1687 | } |
| 1688 | } else if line.starts_with('target_os=') { |
| 1689 | if line['target_os='.len..] != b.pref.target_os_or_host() { |
| 1690 | return false |
| 1691 | } |
| 1692 | } else if line == 'self_build=true' { |
| 1693 | saw_self_build = true |
| 1694 | } else if line.starts_with('dependency:') { |
| 1695 | rest := line['dependency:'.len..] |
| 1696 | sep := rest.last_index(':') or { return false } |
| 1697 | dep_stamp := cache_path_join(cache_dir, '${rest[..sep]}.stamp') |
| 1698 | if '${os.file_last_mod_unix(dep_stamp)}' != rest[sep + 1..] { |
| 1699 | if trace { |
| 1700 | eprintln('TRACE_CACHE fast_relink=false reason=dep_mtime ${rest[..sep]} have=${os.file_last_mod_unix(dep_stamp)} want=${rest[ |
| 1701 | sep + 1..]}') |
| 1702 | } |
| 1703 | return false |
| 1704 | } |
| 1705 | } |
| 1706 | } |
| 1707 | if !saw_self_build || main_cc.len == 0 || !saw_flag_fp { |
| 1708 | if trace { |
| 1709 | eprintln('TRACE_CACHE fast_relink=false reason=keys saw_self_build=${saw_self_build} cc=${main_cc} flag_fp=${saw_flag_fp}') |
| 1710 | } |
| 1711 | return false |
| 1712 | } |
| 1713 | mut sw := time.new_stopwatch() |
| 1714 | b.link_cleanc_cached_core_executable(output_name, main_cc, main_cc_flags, main_cc_link_flags, |
| 1715 | main_obj, cache_path_join(cache_dir, 'builtin.o'), cache_path_join(cache_dir, 'vlib.o'), cache_path_join(cache_dir, |
| 1716 | 'v2compiler.o'), [cache_path_join(cache_dir, 'imports.o')], '', mut sw) |
| 1717 | if os.getenv('V2_TRACE_CACHE') != '' { |
| 1718 | eprintln('TRACE_CACHE self_build_fast_relink=true') |
| 1719 | } |
| 1720 | return true |
| 1721 | } |
| 1722 | |
| 1723 | fn (b &Builder) cache_stamp_for_self_main_object(main_modules []string, emit_files []string, linked_cache_names []string, main_cc string, main_cc_flags string, main_cc_link_flags string, cached_init_calls []string) string { |
| 1724 | source_files := b.module_source_files(main_modules) |
| 1725 | dependency_lines := b.cache_dependency_stamp_lines(linked_cache_names) |
| 1726 | mut lines := []string{cap: source_files.len + emit_files.len + dependency_lines.len + |
| 1727 | cached_init_calls.len + 12} |
| 1728 | lines << 'cache=main' |
| 1729 | lines << 'format=${core_cache_format}' |
| 1730 | lines << 'cc=${main_cc}' |
| 1731 | lines << 'cc_flags=${main_cc_flags}' |
| 1732 | lines << 'cc_link_flags=${main_cc_link_flags}' |
| 1733 | lines << 'flag_fp=${b.preparse_flag_fingerprint()}' |
| 1734 | lines << 'context_alloc=${b.pref.use_context_allocator}' |
| 1735 | lines << 'target_os=${b.pref.target_os_or_host()}' |
| 1736 | lines << 'self_build=true' |
| 1737 | exe := os.executable() |
| 1738 | lines << 'compiler_exe:${exe}:${os.file_last_mod_unix(exe)}' |
| 1739 | for module_name in main_modules { |
| 1740 | lines << 'module:${module_name}' |
| 1741 | } |
| 1742 | for init_call in cached_init_calls { |
| 1743 | lines << 'init:${init_call}' |
| 1744 | } |
| 1745 | lines << dependency_lines |
| 1746 | for file in b.user_entry_stamp_files() { |
| 1747 | lines << 'entry:${file}:${os.file_last_mod_unix(file)}' |
| 1748 | } |
| 1749 | for file in source_files { |
| 1750 | lines << 'source:${file}:${os.file_last_mod_unix(file)}' |
| 1751 | } |
| 1752 | for file in emit_files { |
| 1753 | lines << 'emit:${file}:${os.file_last_mod_unix(file)}' |
| 1754 | } |
| 1755 | return lines.join('\n') |
| 1756 | } |
| 1757 | |
| 1758 | fn (b &Builder) cached_module_stamp_flags(cache_names []string) (string, string) { |
| 1759 | cache_dir := b.core_cache_dir() |
| 1760 | mut compile_flags := '' |
| 1761 | mut link_flags := '' |
| 1762 | for cache_name in cache_names { |
| 1763 | stamp_path := os.join_path(cache_dir, '${cache_name}.stamp') |
| 1764 | stamp := os.read_file(stamp_path) or { continue } |
| 1765 | for line in stamp.split_into_lines() { |
| 1766 | if line.starts_with('cc_flags=') { |
| 1767 | compile_flags = join_flag_strings(compile_flags, line['cc_flags='.len..]) |
| 1768 | } else if line.starts_with('cc_link_flags=') { |
| 1769 | link_flags = join_flag_strings(link_flags, line['cc_link_flags='.len..]) |
| 1770 | } |
| 1771 | } |
| 1772 | } |
| 1773 | return compile_flags, link_flags |
| 1774 | } |
| 1775 | |
| 1776 | fn join_flag_strings(a string, b string) string { |
| 1777 | mut joined := []string{} |
| 1778 | for flags in [a, b] { |
| 1779 | for item in flag_string_items(flags) { |
| 1780 | if item !in joined { |
| 1781 | joined << item |
| 1782 | } |
| 1783 | } |
| 1784 | } |
| 1785 | return joined.join(' ') |
| 1786 | } |
| 1787 | |
| 1788 | fn flag_string_items(flags string) []string { |
| 1789 | tokens := flags.fields() |
| 1790 | mut items := []string{} |
| 1791 | mut i := 0 |
| 1792 | for i < tokens.len { |
| 1793 | tok := tokens[i] |
| 1794 | if tok in ['-I', '-L', '-l', '-F', '-framework', '-isystem', '-include'] |
| 1795 | && i + 1 < tokens.len { |
| 1796 | items << '${tok} ${tokens[i + 1]}' |
| 1797 | i += 2 |
| 1798 | continue |
| 1799 | } |
| 1800 | items << tok |
| 1801 | i++ |
| 1802 | } |
| 1803 | return items |
| 1804 | } |
| 1805 | |
| 1806 | fn (mut b Builder) ensure_cached_module_object(cache_dir string, cache_name string, module_paths []string, emit_modules []string, cc string, cc_flags string, cc_link_flags string, error_limit_flag string, use_markused bool) !string { |
| 1807 | obj_path := cache_path_join(cache_dir, '${cache_name}.o') |
| 1808 | stamp_path := cache_path_join(cache_dir, '${cache_name}.stamp') |
| 1809 | c_path := cache_path_join(cache_dir, '${cache_name}.c') |
| 1810 | expected_stamp := b.cache_stamp_for_modules(cache_name, module_paths, cc, cc_flags, |
| 1811 | cc_link_flags, use_markused) |
| 1812 | if os.exists(obj_path) && os.exists(stamp_path) { |
| 1813 | if current_stamp := os.read_file(stamp_path) { |
| 1814 | if current_stamp == expected_stamp { |
| 1815 | if b.load_cached_called_fn_names(cache_dir, cache_name) { |
| 1816 | if os.getenv('V2VERBOSE') != '' { |
| 1817 | println('[*] Reusing ${obj_path}') |
| 1818 | } |
| 1819 | return obj_path |
| 1820 | } |
| 1821 | } |
| 1822 | } |
| 1823 | } |
| 1824 | if b.used_vh_for_parse { |
| 1825 | if os.exists(obj_path) && os.exists(stamp_path) { |
| 1826 | if b.load_cached_called_fn_names(cache_dir, cache_name) { |
| 1827 | return obj_path |
| 1828 | } |
| 1829 | return error('missing cached ${cache_name} call metadata for .vh parse') |
| 1830 | } |
| 1831 | return error('missing cached ${cache_name} object for .vh parse') |
| 1832 | } |
| 1833 | |
| 1834 | stats_enabled := b.cgen_builder_stats_enabled() |
| 1835 | mut stats_sw := time.new_stopwatch() |
| 1836 | mut stage_start := stats_sw.elapsed() |
| 1837 | cached_called_before := b.cached_called_fn_names.clone() |
| 1838 | stage_start = b.mark_cgen_builder_step(stats_enabled, cache_name, mut stats_sw, stage_start, |
| 1839 | 'called_before_clone') |
| 1840 | module_source := b.gen_cleanc_source_for_cache(emit_modules, cache_name, use_markused) |
| 1841 | stage_start = b.mark_cgen_builder_step(stats_enabled, cache_name, mut stats_sw, stage_start, |
| 1842 | 'source') |
| 1843 | if module_source == '' { |
| 1844 | return error('failed to generate C source for ${cache_name}') |
| 1845 | } |
| 1846 | os.write_file(c_path, module_source)! |
| 1847 | stage_start = b.mark_cgen_builder_step(stats_enabled, cache_name, mut stats_sw, stage_start, |
| 1848 | 'write_c') |
| 1849 | |
| 1850 | compile_cmd := '${cc} ${cc_flags} -w -Wno-incompatible-function-pointer-types -c "${c_path}" -o "${obj_path}"${error_limit_flag}' |
| 1851 | run_cc_cmd_or_exit(compile_cmd, 'C compilation', b.pref.show_cc) |
| 1852 | stage_start = |
| 1853 | b.mark_cgen_builder_step(stats_enabled, cache_name, mut stats_sw, stage_start, 'cc') |
| 1854 | b.write_cached_called_fn_names(cache_dir, cache_name, cached_called_before) |
| 1855 | os.write_file(stamp_path, expected_stamp)! |
| 1856 | _ = b.mark_cgen_builder_step(stats_enabled, cache_name, mut stats_sw, stage_start, 'metadata') |
| 1857 | return obj_path |
| 1858 | } |
| 1859 | |
| 1860 | fn (mut b Builder) ensure_cached_parsed_module_object(cache_dir string, cache_name string, module_names []string, dependency_cache_names []string, cc string, cc_flags string, cc_link_flags string, error_limit_flag string, use_markused bool) !string { |
| 1861 | obj_path := cache_path_join(cache_dir, '${cache_name}.o') |
| 1862 | stamp_path := cache_path_join(cache_dir, '${cache_name}.stamp') |
| 1863 | c_path := cache_path_join(cache_dir, '${cache_name}.c') |
| 1864 | expected_stamp := b.cache_stamp_for_parsed_modules(cache_name, module_names, |
| 1865 | dependency_cache_names, cc, cc_flags, cc_link_flags, use_markused) |
| 1866 | if os.exists(obj_path) && os.exists(stamp_path) { |
| 1867 | if current_stamp := os.read_file(stamp_path) { |
| 1868 | if current_stamp == expected_stamp { |
| 1869 | if b.load_cached_called_fn_names(cache_dir, cache_name) { |
| 1870 | if os.getenv('V2VERBOSE') != '' { |
| 1871 | println('[*] Reusing ${obj_path}') |
| 1872 | } |
| 1873 | return obj_path |
| 1874 | } |
| 1875 | } |
| 1876 | } |
| 1877 | } |
| 1878 | if b.used_import_vh_for_parse { |
| 1879 | if os.exists(obj_path) && os.exists(stamp_path) { |
| 1880 | if b.load_cached_called_fn_names(cache_dir, cache_name) { |
| 1881 | return obj_path |
| 1882 | } |
| 1883 | return error('missing cached ${cache_name} call metadata for .vh parse') |
| 1884 | } |
| 1885 | return error('missing cached ${cache_name} object for .vh parse') |
| 1886 | } |
| 1887 | |
| 1888 | stats_enabled := b.cgen_builder_stats_enabled() |
| 1889 | mut stats_sw := time.new_stopwatch() |
| 1890 | mut stage_start := stats_sw.elapsed() |
| 1891 | cached_called_before := b.cached_called_fn_names.clone() |
| 1892 | stage_start = b.mark_cgen_builder_step(stats_enabled, cache_name, mut stats_sw, stage_start, |
| 1893 | 'called_before_clone') |
| 1894 | mut module_source := b.gen_cleanc_source_for_cache(module_names, cache_name, use_markused) |
| 1895 | stage_start = b.mark_cgen_builder_step(stats_enabled, cache_name, mut stats_sw, stage_start, |
| 1896 | 'source') |
| 1897 | if module_source == '' { |
| 1898 | return error('failed to generate C source for ${cache_name}') |
| 1899 | } |
| 1900 | os.write_file(c_path, module_source)! |
| 1901 | stage_start = b.mark_cgen_builder_step(stats_enabled, cache_name, mut stats_sw, stage_start, |
| 1902 | 'write_c') |
| 1903 | |
| 1904 | compile_cmd := '${cc} ${cc_flags} -w -Wno-incompatible-function-pointer-types -c "${c_path}" -o "${obj_path}"${error_limit_flag}' |
| 1905 | run_cc_cmd_or_exit(compile_cmd, 'C compilation', b.pref.show_cc) |
| 1906 | stage_start = |
| 1907 | b.mark_cgen_builder_step(stats_enabled, cache_name, mut stats_sw, stage_start, 'cc') |
| 1908 | b.write_cached_called_fn_names(cache_dir, cache_name, cached_called_before) |
| 1909 | os.write_file(stamp_path, expected_stamp)! |
| 1910 | _ = b.mark_cgen_builder_step(stats_enabled, cache_name, mut stats_sw, stage_start, 'metadata') |
| 1911 | return obj_path |
| 1912 | } |
| 1913 | |
| 1914 | fn (mut b Builder) ensure_cached_virtual_module_object(cache_dir string, groups []CachedVirtualModule, dependency_cache_names []string, cc string, cc_flags string, cc_link_flags string, error_limit_flag string, use_markused bool) !string { |
| 1915 | obj_path := cache_path_join(cache_dir, '${virtuals_cache_name}.o') |
| 1916 | stamp_path := cache_path_join(cache_dir, '${virtuals_cache_name}.stamp') |
| 1917 | c_path := cache_path_join(cache_dir, '${virtuals_cache_name}.c') |
| 1918 | expected_stamp := b.cache_stamp_for_virtual_modules(groups, dependency_cache_names, cc, |
| 1919 | cc_flags, cc_link_flags, use_markused) |
| 1920 | if os.exists(obj_path) && os.exists(stamp_path) { |
| 1921 | if current_stamp := os.read_file(stamp_path) { |
| 1922 | if current_stamp == expected_stamp { |
| 1923 | if b.load_cached_called_fn_names(cache_dir, virtuals_cache_name) { |
| 1924 | if os.getenv('V2VERBOSE') != '' { |
| 1925 | println('[*] Reusing ${obj_path}') |
| 1926 | } |
| 1927 | return obj_path |
| 1928 | } |
| 1929 | } |
| 1930 | } |
| 1931 | } |
| 1932 | if b.used_virtual_vh_for_parse { |
| 1933 | if os.exists(obj_path) && os.exists(stamp_path) { |
| 1934 | if b.load_cached_called_fn_names(cache_dir, virtuals_cache_name) { |
| 1935 | return obj_path |
| 1936 | } |
| 1937 | return error('missing cached ${virtuals_cache_name} call metadata for .vh parse') |
| 1938 | } |
| 1939 | return error('missing cached ${virtuals_cache_name} object for .vh parse') |
| 1940 | } |
| 1941 | |
| 1942 | emit_files := virtual_module_source_files(groups) |
| 1943 | stats_enabled := b.cgen_builder_stats_enabled() |
| 1944 | mut stats_sw := time.new_stopwatch() |
| 1945 | mut stage_start := stats_sw.elapsed() |
| 1946 | cached_called_before := b.cached_called_fn_names.clone() |
| 1947 | stage_start = b.mark_cgen_builder_step(stats_enabled, virtuals_cache_name, mut stats_sw, |
| 1948 | stage_start, 'called_before_clone') |
| 1949 | mut module_source := b.gen_cleanc_source_for_cache_files(['main'], emit_files, |
| 1950 | virtuals_cache_name, use_markused) |
| 1951 | stage_start = b.mark_cgen_builder_step(stats_enabled, virtuals_cache_name, mut stats_sw, |
| 1952 | stage_start, 'source') |
| 1953 | if module_source == '' { |
| 1954 | return error('failed to generate C source for ${virtuals_cache_name}') |
| 1955 | } |
| 1956 | os.write_file(c_path, module_source)! |
| 1957 | stage_start = b.mark_cgen_builder_step(stats_enabled, virtuals_cache_name, mut stats_sw, |
| 1958 | stage_start, 'write_c') |
| 1959 | |
| 1960 | compile_cmd := '${cc} ${cc_flags} -w -Wno-incompatible-function-pointer-types -c "${c_path}" -o "${obj_path}"${error_limit_flag}' |
| 1961 | run_cc_cmd_or_exit(compile_cmd, 'C compilation', b.pref.show_cc) |
| 1962 | stage_start = b.mark_cgen_builder_step(stats_enabled, virtuals_cache_name, mut stats_sw, |
| 1963 | stage_start, 'cc') |
| 1964 | b.write_cached_called_fn_names(cache_dir, virtuals_cache_name, cached_called_before) |
| 1965 | os.write_file(stamp_path, expected_stamp)! |
| 1966 | _ = b.mark_cgen_builder_step(stats_enabled, virtuals_cache_name, mut stats_sw, stage_start, |
| 1967 | 'metadata') |
| 1968 | return obj_path |
| 1969 | } |
| 1970 | |
| 1971 | // flat_file_module_name returns the normalized module name of the i-th flat |
| 1972 | // file, mirroring `ast_file_module_name` for the flat-AST path: the raw module |
| 1973 | // string with `.` replaced by `_`, defaulting to `main`. Used by the cleanc |
| 1974 | // cached-core enumeration helpers when `b.files` has been dropped in favour of |
| 1975 | // the post-transform FlatAst. |
| 1976 | fn (b &Builder) flat_file_module_name(i int) string { |
| 1977 | mod := b.flat.string_at(b.flat.files[i].mod_idx) |
| 1978 | if mod == '' { |
| 1979 | return 'main' |
| 1980 | } |
| 1981 | return mod.replace('.', '_') |
| 1982 | } |
| 1983 | |
| 1984 | // uses_flat_module_enumeration reports whether module/file enumeration must run |
| 1985 | // against `b.flat` instead of `b.files`. In flat-codegen mode the transformer |
| 1986 | // drops `b.files` after producing the post-transform FlatAst, so the cleanc |
| 1987 | // cache helpers source their module/file metadata from the flat cursors. |
| 1988 | fn (b &Builder) uses_flat_module_enumeration() bool { |
| 1989 | return b.files.len == 0 && b.flat.files.len > 0 |
| 1990 | } |
| 1991 | |
| 1992 | // flat_scoped_to_modules returns a FlatAst whose file list is restricted to the |
| 1993 | // given module set, reusing b.flat's node/edge/string arena (the arrays are |
| 1994 | // shared, not deep-copied). The cleanc gen drives every emission pass off |
| 1995 | // `flat.files`, so a restricted file list makes the whole gen see exactly the |
| 1996 | // bundle's type-module files — matching the legacy gen, which filtered its input |
| 1997 | // `gen_files` to `type_module_names`. This lets restricted cache bundles run on |
| 1998 | // the flat gen with no per-file legacy rehydrate. |
| 1999 | fn (b &Builder) flat_scoped_to_modules(module_names map[string]bool) ast.FlatAst { |
| 2000 | mut files := []ast.FlatFile{cap: b.flat.files.len} |
| 2001 | for i in 0 .. b.flat.files.len { |
| 2002 | if b.flat_file_module_name(i) in module_names { |
| 2003 | files << b.flat.files[i] |
| 2004 | } |
| 2005 | } |
| 2006 | return ast.FlatAst{ |
| 2007 | files: files |
| 2008 | nodes: b.flat.nodes |
| 2009 | edges: b.flat.edges |
| 2010 | strings: b.flat.strings |
| 2011 | } |
| 2012 | } |
| 2013 | |
| 2014 | fn (b &Builder) has_module(module_name string) bool { |
| 2015 | if b.uses_flat_module_enumeration() { |
| 2016 | for i in 0 .. b.flat.files.len { |
| 2017 | if b.flat_file_module_name(i) == module_name { |
| 2018 | return true |
| 2019 | } |
| 2020 | } |
| 2021 | return false |
| 2022 | } |
| 2023 | for file in b.files { |
| 2024 | if ast_file_module_name(file) == module_name { |
| 2025 | return true |
| 2026 | } |
| 2027 | } |
| 2028 | return false |
| 2029 | } |
| 2030 | |
| 2031 | fn (b &Builder) collect_modules_excluding(excluded []string) []string { |
| 2032 | mut excluded_set := map[string]bool{} |
| 2033 | for module_name in excluded { |
| 2034 | excluded_set[module_name] = true |
| 2035 | } |
| 2036 | mut modules_set := map[string]bool{} |
| 2037 | if b.uses_flat_module_enumeration() { |
| 2038 | for i in 0 .. b.flat.files.len { |
| 2039 | module_name := b.flat_file_module_name(i) |
| 2040 | if module_name in excluded_set { |
| 2041 | continue |
| 2042 | } |
| 2043 | modules_set[module_name] = true |
| 2044 | } |
| 2045 | } else { |
| 2046 | for file in b.files { |
| 2047 | module_name := ast_file_module_name(file) |
| 2048 | if module_name in excluded_set { |
| 2049 | continue |
| 2050 | } |
| 2051 | modules_set[module_name] = true |
| 2052 | } |
| 2053 | } |
| 2054 | mut modules := modules_set.keys() |
| 2055 | modules.sort() |
| 2056 | return modules |
| 2057 | } |
| 2058 | |
| 2059 | fn (b &Builder) user_entry_module_names() []string { |
| 2060 | mut entry_files := map[string]bool{} |
| 2061 | for file in b.user_entry_stamp_files() { |
| 2062 | entry_files[os.norm_path(file)] = true |
| 2063 | entry_files[os.norm_path(os.abs_path(file))] = true |
| 2064 | } |
| 2065 | mut modules_set := map[string]bool{} |
| 2066 | if b.uses_flat_module_enumeration() { |
| 2067 | for i in 0 .. b.flat.files.len { |
| 2068 | name := b.flat.file_name(b.flat.files[i]) |
| 2069 | if name == '' || name.ends_with('.vh') { |
| 2070 | continue |
| 2071 | } |
| 2072 | norm_name := os.norm_path(name) |
| 2073 | abs_name := os.norm_path(os.abs_path(name)) |
| 2074 | if norm_name !in entry_files && abs_name !in entry_files { |
| 2075 | continue |
| 2076 | } |
| 2077 | module_name := b.flat_file_module_name(i) |
| 2078 | if module_name.len > 0 { |
| 2079 | modules_set[module_name] = true |
| 2080 | } |
| 2081 | } |
| 2082 | } else { |
| 2083 | for file in b.files { |
| 2084 | if file.name == '' || file.name.ends_with('.vh') { |
| 2085 | continue |
| 2086 | } |
| 2087 | norm_name := os.norm_path(file.name) |
| 2088 | abs_name := os.norm_path(os.abs_path(file.name)) |
| 2089 | if norm_name !in entry_files && abs_name !in entry_files { |
| 2090 | continue |
| 2091 | } |
| 2092 | module_name := ast_file_module_name(file) |
| 2093 | if module_name.len > 0 { |
| 2094 | modules_set[module_name] = true |
| 2095 | } |
| 2096 | } |
| 2097 | } |
| 2098 | mut modules := modules_set.keys() |
| 2099 | modules.sort() |
| 2100 | return modules |
| 2101 | } |
| 2102 | |
| 2103 | fn (b &Builder) print_cached_bundle_modules(cache_name string, module_names []string) { |
| 2104 | if module_names.len == 0 { |
| 2105 | return |
| 2106 | } |
| 2107 | stats_enabled := b.pref != unsafe { nil } && b.pref.stats |
| 2108 | show_cc_enabled := b.pref != unsafe { nil } && b.pref.show_cc |
| 2109 | if !stats_enabled && !show_cc_enabled && os.getenv('V2_TRACE_CACHE') == '' { |
| 2110 | return |
| 2111 | } |
| 2112 | if stats_enabled { |
| 2113 | println(' * Cached ${cache_name} modules: ${module_names.len}') |
| 2114 | for module_name in module_names { |
| 2115 | println(' [cache ${cache_name}] ${module_name}') |
| 2116 | } |
| 2117 | return |
| 2118 | } |
| 2119 | println('[*] Cached ${cache_name} modules: ${module_names.join(', ')}') |
| 2120 | } |
| 2121 | |
| 2122 | fn ast_file_module_name(file ast.File) string { |
| 2123 | for stmt in file.stmts { |
| 2124 | if stmt is ast.ModuleStmt { |
| 2125 | return stmt.name.replace('.', '_') |
| 2126 | } |
| 2127 | } |
| 2128 | return 'main' |
| 2129 | } |
| 2130 | |
| 2131 | fn normalize_target_os_name(target_os string) string { |
| 2132 | return match target_os.to_lower() { |
| 2133 | 'darwin', 'mac' { 'macos' } |
| 2134 | else { target_os.to_lower() } |
| 2135 | } |
| 2136 | } |
| 2137 | |
| 2138 | fn is_windows_x64_native_target(arch pref.Arch, target_os string) bool { |
| 2139 | return arch == .x64 && normalize_target_os_name(target_os) == 'windows' |
| 2140 | } |
| 2141 | |
| 2142 | fn is_linux_x64_native_target(arch pref.Arch, target_os string) bool { |
| 2143 | return arch == .x64 && normalize_target_os_name(target_os) == 'linux' |
| 2144 | } |
| 2145 | |
| 2146 | fn is_macos_x64_native_target(arch pref.Arch, target_os string) bool { |
| 2147 | return arch == .x64 && is_macos_native_target(target_os) |
| 2148 | } |
| 2149 | |
| 2150 | fn eprint_native_x64_link_error(message string) { |
| 2151 | if message.starts_with('x64: unsupported backend feature: ') { |
| 2152 | eprintln(message) |
| 2153 | eprintln(x64.x64_backend_limitation_hint) |
| 2154 | return |
| 2155 | } |
| 2156 | eprintln('Link failed:') |
| 2157 | eprintln(message) |
| 2158 | } |
| 2159 | |
| 2160 | fn (b &Builder) uses_minimal_windows_x64_runtime() bool { |
| 2161 | arch := b.pref.get_effective_arch() |
| 2162 | return b.pref.backend == .x64 && is_windows_x64_native_target(arch, b.pref.target_os_or_host()) |
| 2163 | } |
| 2164 | |
| 2165 | fn (b &Builder) uses_minimal_linux_x64_runtime() bool { |
| 2166 | arch := b.pref.get_effective_arch() |
| 2167 | return b.pref.backend == .x64 && is_linux_x64_native_target(arch, b.pref.target_os_or_host()) |
| 2168 | } |
| 2169 | |
| 2170 | fn (b &Builder) uses_minimal_x64_runtime() bool { |
| 2171 | return b.uses_minimal_windows_x64_runtime() || b.uses_minimal_linux_x64_runtime() |
| 2172 | } |
| 2173 | |
| 2174 | fn linux_x64_tiny_strict_enabled() bool { |
| 2175 | return os.getenv('V2_X64_LINUX_TINY') != '' |
| 2176 | } |
| 2177 | |
| 2178 | fn (b &Builder) uses_minimal_linux_x64_runtime_roots() bool { |
| 2179 | return b.uses_minimal_linux_x64_runtime() && linux_x64_tiny_strict_enabled() |
| 2180 | } |
| 2181 | |
| 2182 | fn (b &Builder) uses_minimal_x64_runtime_roots() bool { |
| 2183 | return b.uses_minimal_windows_x64_runtime() || b.uses_minimal_linux_x64_runtime_roots() |
| 2184 | } |
| 2185 | |
| 2186 | fn (b &Builder) uses_macos_x64_tiny_object(arch pref.Arch) bool { |
| 2187 | return b.pref.backend == .x64 && is_macos_x64_native_target(arch, b.pref.target_os_or_host()) |
| 2188 | && b.pref.macos_tiny |
| 2189 | } |
| 2190 | |
| 2191 | fn native_link_flags_suffix(link_flags string) string { |
| 2192 | trimmed := link_flags.trim_space() |
| 2193 | if trimmed == '' { |
| 2194 | return '' |
| 2195 | } |
| 2196 | return ' ${trimmed}' |
| 2197 | } |
| 2198 | |
| 2199 | fn macos_native_ld_link_flags(link_flags string) string { |
| 2200 | mut normalized := []string{} |
| 2201 | tokens := link_flags.fields() |
| 2202 | mut i := 0 |
| 2203 | for i < tokens.len { |
| 2204 | tok := tokens[i] |
| 2205 | if tok.starts_with('-Wl,') { |
| 2206 | for linker_arg in tok['-Wl,'.len..].split(',') { |
| 2207 | if linker_arg != '' { |
| 2208 | normalized << linker_arg |
| 2209 | } |
| 2210 | } |
| 2211 | i++ |
| 2212 | continue |
| 2213 | } |
| 2214 | if tok == '-Xlinker' { |
| 2215 | if i + 1 < tokens.len { |
| 2216 | i++ |
| 2217 | normalized << tokens[i] |
| 2218 | } |
| 2219 | i++ |
| 2220 | continue |
| 2221 | } |
| 2222 | normalized << tok |
| 2223 | i++ |
| 2224 | } |
| 2225 | return normalized.join(' ') |
| 2226 | } |
| 2227 | |
| 2228 | fn native_driver_flag_is_dual_use(tok string) bool { |
| 2229 | return tok == '-pthread' || tok == '-fopenmp' || tok.starts_with('-fopenmp=') |
| 2230 | } |
| 2231 | |
| 2232 | fn macos_native_ld_unsupported_driver_link_flags(link_flags string) []string { |
| 2233 | mut unsupported := []string{} |
| 2234 | for tok in link_flags.fields() { |
| 2235 | if native_driver_flag_is_dual_use(tok) { |
| 2236 | unsupported << tok |
| 2237 | } |
| 2238 | } |
| 2239 | return unsupported |
| 2240 | } |
| 2241 | |
| 2242 | fn validate_macos_native_ld_link_flags(link_flags string) ! { |
| 2243 | unsupported := macos_native_ld_unsupported_driver_link_flags(link_flags) |
| 2244 | if unsupported.len > 0 { |
| 2245 | return error('x64: unsupported backend feature: macOS native ld cannot consume driver linker flags: ${unsupported.join(' ')}') |
| 2246 | } |
| 2247 | } |
| 2248 | |
| 2249 | fn native_linux_tiny_allows_system_lib(lib string) bool { |
| 2250 | return lib in ['pthread', 'm', 'dl', 'c'] |
| 2251 | } |
| 2252 | |
| 2253 | fn native_internal_link_flags_allow_builtin_linux_tiny(link_flags string) bool { |
| 2254 | tokens := link_flags.trim_space().fields() |
| 2255 | mut i := 0 |
| 2256 | for i < tokens.len { |
| 2257 | tok := tokens[i] |
| 2258 | if tok == '-l' { |
| 2259 | if i + 1 >= tokens.len { |
| 2260 | return false |
| 2261 | } |
| 2262 | i++ |
| 2263 | if !native_linux_tiny_allows_system_lib(tokens[i]) { |
| 2264 | return false |
| 2265 | } |
| 2266 | } else if tok.starts_with('-l') { |
| 2267 | if !native_linux_tiny_allows_system_lib(tok['-l'.len..]) { |
| 2268 | return false |
| 2269 | } |
| 2270 | } else { |
| 2271 | return false |
| 2272 | } |
| 2273 | i++ |
| 2274 | } |
| 2275 | return true |
| 2276 | } |
| 2277 | |
| 2278 | fn native_link_flags_allow_builtin_linux_tiny(link_flags string, user_link_flags string) bool { |
| 2279 | return user_link_flags.trim_space() == '' |
| 2280 | && native_internal_link_flags_allow_builtin_linux_tiny(link_flags) |
| 2281 | } |
| 2282 | |
| 2283 | fn (b &Builder) native_link_flags_from_sources() string { |
| 2284 | _, directive_link_flags := split_compile_and_link_flags(b.collect_cflags_from_sources()) |
| 2285 | return directive_link_flags.trim_space() |
| 2286 | } |
| 2287 | |
| 2288 | fn (b &Builder) native_compile_and_link_flags_from_sources() (string, string) { |
| 2289 | directive_compile_flags, directive_link_flags := |
| 2290 | split_compile_and_link_flags(b.collect_cflags_from_sources()) |
| 2291 | return directive_compile_flags.trim_space(), directive_link_flags.trim_space() |
| 2292 | } |
| 2293 | |
| 2294 | fn (b &Builder) native_user_compile_and_link_flags_from_sources() (string, string) { |
| 2295 | directive_compile_flags, directive_link_flags := |
| 2296 | split_compile_and_link_flags(b.collect_user_cflags_from_sources()) |
| 2297 | return directive_compile_flags.trim_space(), directive_link_flags.trim_space() |
| 2298 | } |
| 2299 | |
| 2300 | struct NativeExternalLinkInputs { |
| 2301 | link_flags string |
| 2302 | source_files []string |
| 2303 | object_files []string |
| 2304 | } |
| 2305 | |
| 2306 | fn native_external_source_clean_token(tok string) string { |
| 2307 | return tok.trim('"').trim("'") |
| 2308 | } |
| 2309 | |
| 2310 | fn native_external_source_is_supported(tok string) bool { |
| 2311 | lower := native_external_source_clean_token(tok).to_lower() |
| 2312 | return lower.ends_with('.c') || lower.ends_with('.m') |
| 2313 | } |
| 2314 | |
| 2315 | fn native_external_source_is_unsupported(tok string) bool { |
| 2316 | lower := native_external_source_clean_token(tok).to_lower() |
| 2317 | return lower.ends_with('.cc') || lower.ends_with('.cpp') || lower.ends_with('.cxx') |
| 2318 | || lower.ends_with('.mm') |
| 2319 | } |
| 2320 | |
| 2321 | fn native_external_source_unsupported_message(tok string) string { |
| 2322 | clean := native_external_source_clean_token(tok) |
| 2323 | if clean.to_lower().ends_with('.mm') { |
| 2324 | return 'x64: unsupported backend feature: Objective-C++ #flag source `${clean}`' |
| 2325 | } |
| 2326 | return 'x64: unsupported backend feature: C++ #flag source `${clean}`' |
| 2327 | } |
| 2328 | |
| 2329 | fn native_external_source_object_file(output_binary string, source_index int) string { |
| 2330 | output_name := if output_binary == '' { 'out' } else { os.file_name(output_binary) } |
| 2331 | clean_name := output_name.replace('/', '_').replace('\\', '_').replace('.', '_') |
| 2332 | return os.join_path(os.vtmp_dir(), 'v2_native_${os.getpid()}_${clean_name}_${source_index}.o') |
| 2333 | } |
| 2334 | |
| 2335 | fn native_external_link_inputs(link_flags string, output_binary string) !NativeExternalLinkInputs { |
| 2336 | mut rewritten := []string{} |
| 2337 | mut source_files := []string{} |
| 2338 | mut object_files := []string{} |
| 2339 | for tok in link_flags.fields() { |
| 2340 | clean := native_external_source_clean_token(tok) |
| 2341 | if native_external_source_is_unsupported(clean) { |
| 2342 | return error(native_external_source_unsupported_message(clean)) |
| 2343 | } |
| 2344 | if native_external_source_is_supported(clean) { |
| 2345 | if tok.contains('"') || tok.contains("'") { |
| 2346 | return error('x64: unsupported backend feature: quoted #flag source path `${tok}`') |
| 2347 | } |
| 2348 | obj_file := native_external_source_object_file(output_binary, source_files.len) |
| 2349 | source_files << clean |
| 2350 | object_files << obj_file |
| 2351 | rewritten << os.quoted_path(obj_file) |
| 2352 | continue |
| 2353 | } |
| 2354 | rewritten << tok |
| 2355 | } |
| 2356 | return NativeExternalLinkInputs{ |
| 2357 | link_flags: rewritten.join(' ') |
| 2358 | source_files: source_files |
| 2359 | object_files: object_files |
| 2360 | } |
| 2361 | } |
| 2362 | |
| 2363 | fn native_external_source_compile_command(cc string, source_file string, object_file string, compile_flags string, sdk_path string, arch_flag string, target_os string) string { |
| 2364 | mut parts := [cc, '-c'] |
| 2365 | if is_macos_native_target(target_os) { |
| 2366 | if sdk_path != '' { |
| 2367 | parts << '-isysroot' |
| 2368 | parts << os.quoted_path(sdk_path) |
| 2369 | } |
| 2370 | if arch_flag != '' { |
| 2371 | parts << '-arch' |
| 2372 | parts << arch_flag |
| 2373 | } |
| 2374 | } |
| 2375 | if compile_flags != '' { |
| 2376 | parts << compile_flags |
| 2377 | } |
| 2378 | parts << os.quoted_path(source_file) |
| 2379 | parts << '-o' |
| 2380 | parts << os.quoted_path(object_file) |
| 2381 | return parts.join(' ') |
| 2382 | } |
| 2383 | |
| 2384 | fn (b &Builder) native_external_source_compiler(target_os string) string { |
| 2385 | if b.pref.ccompiler.len > 0 { |
| 2386 | return b.pref.ccompiler |
| 2387 | } |
| 2388 | v2cc := (os.getenv_opt('V2CC') or { '' }).trim_space() |
| 2389 | if v2cc != '' { |
| 2390 | return v2cc |
| 2391 | } |
| 2392 | if is_macos_native_target(target_os) { |
| 2393 | return 'cc' |
| 2394 | } |
| 2395 | return configured_cc(b.pref.vroot) |
| 2396 | } |
| 2397 | |
| 2398 | fn (b &Builder) native_linux_hosted_link_compiler() string { |
| 2399 | if b.pref.ccompiler.len > 0 { |
| 2400 | return b.pref.ccompiler |
| 2401 | } |
| 2402 | v2cc := (os.getenv_opt('V2CC') or { '' }).trim_space() |
| 2403 | if v2cc != '' { |
| 2404 | return v2cc |
| 2405 | } |
| 2406 | return 'cc' |
| 2407 | } |
| 2408 | |
| 2409 | fn (b &Builder) compile_native_external_sources(inputs NativeExternalLinkInputs, compile_flags string, target_os string, sdk_path string, arch_flag string) ! { |
| 2410 | if inputs.source_files.len == 0 { |
| 2411 | return |
| 2412 | } |
| 2413 | cc := b.native_external_source_compiler(target_os) |
| 2414 | for i, source_file in inputs.source_files { |
| 2415 | cmd := native_external_source_compile_command(cc, source_file, inputs.object_files[i], |
| 2416 | compile_flags, sdk_path, arch_flag, target_os) |
| 2417 | if b.pref.show_cc { |
| 2418 | println(cmd) |
| 2419 | } |
| 2420 | res := os.execute(cmd) |
| 2421 | if res.exit_code != 0 { |
| 2422 | return error('native external source compilation failed:\n${cmd}\n${res.output}') |
| 2423 | } |
| 2424 | } |
| 2425 | } |
| 2426 | |
| 2427 | fn cleanup_native_external_objects(inputs NativeExternalLinkInputs) { |
| 2428 | for obj_file in inputs.object_files { |
| 2429 | os.rm(obj_file) or {} |
| 2430 | } |
| 2431 | } |
| 2432 | |
| 2433 | fn macos_native_link_command(output_binary string, obj_file string, sdk_path string, arch_flag string, tiny_object bool, link_flags string) string { |
| 2434 | ld_link_flags := macos_native_ld_link_flags(link_flags) |
| 2435 | normal_link_cmd := 'ld -o ${os.quoted_path(output_binary)} ${os.quoted_path(obj_file)}${native_link_flags_suffix(ld_link_flags)} -lSystem -syslibroot ${os.quoted_path(sdk_path)} -e _main -arch ${arch_flag} -platform_version macos 11.0.0 11.0.0' |
| 2436 | if tiny_object { |
| 2437 | return '${normal_link_cmd} -dead_strip -x -S' |
| 2438 | } |
| 2439 | return normal_link_cmd |
| 2440 | } |
| 2441 | |
| 2442 | fn macos_sdk_path_from_xcrun_output(output string) !string { |
| 2443 | mut sdk_path := '' |
| 2444 | for raw_line in output.split_into_lines() { |
| 2445 | line := raw_line.trim(' \t\r') |
| 2446 | if line == '' { |
| 2447 | continue |
| 2448 | } |
| 2449 | if is_clean_macos_sdk_path_line(line) { |
| 2450 | sdk_path = line |
| 2451 | } |
| 2452 | } |
| 2453 | if sdk_path == '' { |
| 2454 | return error('could not find a clean macOS SDK path in xcrun output') |
| 2455 | } |
| 2456 | return sdk_path |
| 2457 | } |
| 2458 | |
| 2459 | fn is_clean_macos_sdk_path_line(path string) bool { |
| 2460 | if !os.is_abs_path(path) { |
| 2461 | return false |
| 2462 | } |
| 2463 | base := os.file_name(path) |
| 2464 | return base.starts_with('MacOSX') && base.ends_with('.sdk') |
| 2465 | } |
| 2466 | |
| 2467 | fn validate_macos_sdk_path_for_native_link(sdk_path string) ! { |
| 2468 | if !os.is_dir(sdk_path) { |
| 2469 | return error('macOS SDK path does not exist: ${sdk_path}') |
| 2470 | } |
| 2471 | libsystem_tbd := os.join_path(sdk_path, 'usr', 'lib', 'libSystem.tbd') |
| 2472 | libsystem_dylib := os.join_path(sdk_path, 'usr', 'lib', 'libSystem.dylib') |
| 2473 | if !os.exists(libsystem_tbd) && !os.exists(libsystem_dylib) { |
| 2474 | return error('macOS SDK path is missing usr/lib/libSystem.tbd or usr/lib/libSystem.dylib: ${sdk_path}') |
| 2475 | } |
| 2476 | } |
| 2477 | |
| 2478 | fn linux_native_link_command(cc string, output_binary string, obj_file string, link_flags string) string { |
| 2479 | return '${cc} ${os.quoted_path(obj_file)} -o ${os.quoted_path(output_binary)} -no-pie${native_link_flags_suffix(link_flags)}' |
| 2480 | } |
| 2481 | |
| 2482 | fn native_external_object_file(output_binary string, target_os string) string { |
| 2483 | if !is_macos_native_target(target_os) { |
| 2484 | return 'main.o' |
| 2485 | } |
| 2486 | output_path := if os.is_abs_path(output_binary) { |
| 2487 | output_binary |
| 2488 | } else { |
| 2489 | os.abs_path(output_binary) |
| 2490 | } |
| 2491 | output_dir := os.dir(output_path) |
| 2492 | output_name := os.file_name(output_path) |
| 2493 | if output_name == '' { |
| 2494 | return os.join_path(output_dir, '.v_native_${os.getpid()}.o') |
| 2495 | } |
| 2496 | return os.join_path(output_dir, '.${output_name}.${os.getpid()}.o') |
| 2497 | } |
| 2498 | |
| 2499 | fn (mut b Builder) prepare_macos_tiny_candidate_source_files() { |
| 2500 | b.macos_tiny_candidate_source_files = []ast.File{} |
| 2501 | b.macos_tiny_candidate_source_flat = ast.FlatAst{} |
| 2502 | if !b.uses_macos_x64_tiny_object(.x64) { |
| 2503 | return |
| 2504 | } |
| 2505 | if b.flat.files.len > 0 { |
| 2506 | b.macos_tiny_candidate_source_flat = b.flat |
| 2507 | return |
| 2508 | } |
| 2509 | b.macos_tiny_candidate_source_files = b.files.clone() |
| 2510 | } |
| 2511 | |
| 2512 | fn is_macos_native_target(target_os string) bool { |
| 2513 | return normalize_target_os_name(target_os) == 'macos' |
| 2514 | } |
| 2515 | |
| 2516 | fn native_x64_object_format_for_os(target_os string) x64.ObjectFormat { |
| 2517 | return match normalize_target_os_name(target_os) { |
| 2518 | 'macos' { x64.ObjectFormat.macho } |
| 2519 | 'windows' { x64.ObjectFormat.coff } |
| 2520 | else { x64.ObjectFormat.elf } |
| 2521 | } |
| 2522 | } |
| 2523 | |
| 2524 | fn native_x64_codegen_abi_for_os(target_os string) x64.X64Abi { |
| 2525 | return match normalize_target_os_name(target_os) { |
| 2526 | 'windows' { x64.X64Abi.windows } |
| 2527 | else { x64.X64Abi.sysv } |
| 2528 | } |
| 2529 | } |
| 2530 | |
| 2531 | fn native_x64_lowering_abi_for_os(target_os string) abi.X64Abi { |
| 2532 | return match normalize_target_os_name(target_os) { |
| 2533 | 'windows' { abi.X64Abi.windows } |
| 2534 | else { abi.X64Abi.sysv } |
| 2535 | } |
| 2536 | } |
| 2537 | |
| 2538 | fn native_x64_mir_unsupported_external_symbol_message(mir_mod &mir.Module, obj_format x64.ObjectFormat) ?string { |
| 2539 | for val in mir_mod.values { |
| 2540 | if val.kind != .func_ref { |
| 2541 | continue |
| 2542 | } |
| 2543 | if msg := x64.unsupported_external_symbol_message_for_name(obj_format, val.name, |
| 2544 | 'needed while preparing native x64 output') |
| 2545 | { |
| 2546 | return msg |
| 2547 | } |
| 2548 | } |
| 2549 | return none |
| 2550 | } |
| 2551 | |
| 2552 | fn (b &Builder) native_backend_requires_ssa_optimization(arch pref.Arch) bool { |
| 2553 | return arch == .x64 |
| 2554 | } |
| 2555 | |
| 2556 | fn flag_os_matches(cond string, target_os string) bool { |
| 2557 | current := normalize_target_os_name(target_os) |
| 2558 | return match cond.to_lower() { |
| 2559 | 'darwin', 'macos', 'mac' { current == 'macos' || current == 'darwin' } |
| 2560 | 'linux' { current == 'linux' } |
| 2561 | 'windows' { current == 'windows' } |
| 2562 | 'bsd' { current in ['macos', 'freebsd', 'openbsd', 'netbsd', 'dragonfly'] } |
| 2563 | 'freebsd' { current == 'freebsd' } |
| 2564 | 'openbsd' { current == 'openbsd' } |
| 2565 | 'netbsd' { current == 'netbsd' } |
| 2566 | 'dragonfly' { current == 'dragonfly' } |
| 2567 | 'android' { current == 'android' } |
| 2568 | 'termux' { current == 'termux' } |
| 2569 | 'ios' { current == 'ios' } |
| 2570 | 'solaris' { current == 'solaris' } |
| 2571 | 'qnx' { current == 'qnx' } |
| 2572 | 'serenity' { current == 'serenity' } |
| 2573 | 'plan9' { current == 'plan9' } |
| 2574 | 'vinix' { current == 'vinix' } |
| 2575 | 'cross' { current == 'cross' } |
| 2576 | 'none' { current == 'none' } |
| 2577 | else { false } |
| 2578 | } |
| 2579 | } |
| 2580 | |
| 2581 | fn flag_pref_matches(cond string, prefs &pref.Preferences) bool { |
| 2582 | if prefs == unsafe { nil } { |
| 2583 | return false |
| 2584 | } |
| 2585 | lower := cond.to_lower() |
| 2586 | if pref.comptime_flag_value(prefs, lower) { |
| 2587 | return true |
| 2588 | } |
| 2589 | return flag_os_matches(lower, prefs.target_os_or_host()) |
| 2590 | } |
| 2591 | |
| 2592 | fn find_vmod_root_for_file(file_path string) string { |
| 2593 | mut dir := os.dir(file_path) |
| 2594 | for _ in 0 .. 12 { |
| 2595 | if os.exists(os.join_path(dir, 'v.mod')) { |
| 2596 | return dir |
| 2597 | } |
| 2598 | parent := os.dir(dir) |
| 2599 | if parent == dir || parent == '' { |
| 2600 | break |
| 2601 | } |
| 2602 | dir = parent |
| 2603 | } |
| 2604 | return os.dir(file_path) |
| 2605 | } |
| 2606 | |
| 2607 | fn resolve_flag_path(path string, file_dir string, vmod_root string) string { |
| 2608 | mut resolved := path.replace('@VMODROOT', vmod_root) |
| 2609 | if os.is_abs_path(resolved) { |
| 2610 | return resolved |
| 2611 | } |
| 2612 | // Resolve any relative path (including bare relative like 'r/qrcodegen') |
| 2613 | // relative to the source file's directory, matching V1 behavior |
| 2614 | return os.norm_path(os.join_path(file_dir, resolved)) |
| 2615 | } |
| 2616 | |
| 2617 | fn expand_existing_path_macros(flag_value string) ?string { |
| 2618 | mut out := '' |
| 2619 | mut i := 0 |
| 2620 | for i < flag_value.len { |
| 2621 | if flag_value[i] == `$` { |
| 2622 | remainder := flag_value[i..] |
| 2623 | mut literal := '' |
| 2624 | if remainder.starts_with(r'$when_first_existing') { |
| 2625 | literal = r'$when_first_existing' |
| 2626 | } else if remainder.starts_with(r'$first_existing') { |
| 2627 | literal = r'$first_existing' |
| 2628 | } |
| 2629 | if literal != '' { |
| 2630 | if remainder.len <= literal.len || remainder[literal.len] != `(` { |
| 2631 | out += flag_value[i].ascii_str() |
| 2632 | i++ |
| 2633 | continue |
| 2634 | } |
| 2635 | params_part := remainder[literal.len + 1..] |
| 2636 | params := params_part.all_before(')') |
| 2637 | if params == params_part { |
| 2638 | return none |
| 2639 | } |
| 2640 | paths := params.replace(',', '\n').split_into_lines().map(it.trim('\t \'"')) |
| 2641 | mut found := '' |
| 2642 | for path in paths { |
| 2643 | if path != '' && os.exists(path) { |
| 2644 | found = path |
| 2645 | break |
| 2646 | } |
| 2647 | } |
| 2648 | if found == '' { |
| 2649 | return none |
| 2650 | } |
| 2651 | out += found |
| 2652 | i += literal.len + 1 + params.len + 1 |
| 2653 | continue |
| 2654 | } |
| 2655 | } |
| 2656 | out += flag_value[i].ascii_str() |
| 2657 | i++ |
| 2658 | } |
| 2659 | return out |
| 2660 | } |
| 2661 | |
| 2662 | fn normalize_flag_value_for_file(flag_value string, file_path string) string { |
| 2663 | file_dir := os.dir(os.real_path(file_path)) |
| 2664 | vmod_root := find_vmod_root_for_file(file_path) |
| 2665 | expanded_flag_value := expand_existing_path_macros(flag_value) or { return '' } |
| 2666 | mut tokens := expanded_flag_value.fields() |
| 2667 | mut out := []string{} |
| 2668 | mut i := 0 |
| 2669 | for i < tokens.len { |
| 2670 | tok := tokens[i] |
| 2671 | if tok in ['-I', '-L', '-F'] && i + 1 < tokens.len { |
| 2672 | out << tok |
| 2673 | out << resolve_flag_path(tokens[i + 1], file_dir, vmod_root) |
| 2674 | i += 2 |
| 2675 | continue |
| 2676 | } |
| 2677 | if tok.starts_with('-I') && tok.len > 2 { |
| 2678 | out << '-I' + resolve_flag_path(tok[2..], file_dir, vmod_root) |
| 2679 | i++ |
| 2680 | continue |
| 2681 | } |
| 2682 | if tok.starts_with('-L') && tok.len > 2 { |
| 2683 | out << '-L' + resolve_flag_path(tok[2..], file_dir, vmod_root) |
| 2684 | i++ |
| 2685 | continue |
| 2686 | } |
| 2687 | if tok.starts_with('-F') && tok.len > 2 { |
| 2688 | out << '-F' + resolve_flag_path(tok[2..], file_dir, vmod_root) |
| 2689 | i++ |
| 2690 | continue |
| 2691 | } |
| 2692 | if tok.contains('@VMODROOT') || tok.contains('@VEXEROOT') || tok.starts_with('./') |
| 2693 | || tok.starts_with('../') || tok.ends_with('.c') || tok.ends_with('.m') |
| 2694 | || tok.ends_with('.o') { |
| 2695 | out << resolve_flag_path(tok, file_dir, vmod_root) |
| 2696 | i++ |
| 2697 | continue |
| 2698 | } |
| 2699 | out << tok |
| 2700 | i++ |
| 2701 | } |
| 2702 | return out.join(' ') |
| 2703 | } |
| 2704 | |
| 2705 | fn parse_flag_directive_line(line string, file_path string, target_os string) ?string { |
| 2706 | return parse_flag_directive_line_with_context(line, file_path, target_os, unsafe { |
| 2707 | &pref.Preferences(nil) |
| 2708 | }) |
| 2709 | } |
| 2710 | |
| 2711 | fn parse_flag_directive_line_with_pref(line string, file_path string, prefs &pref.Preferences) ?string { |
| 2712 | target_os := if prefs == unsafe { nil } { '' } else { prefs.target_os_or_host() } |
| 2713 | return parse_flag_directive_line_with_context(line, file_path, target_os, prefs) |
| 2714 | } |
| 2715 | |
| 2716 | fn parse_flag_directive_line_with_context(line string, file_path string, target_os string, prefs &pref.Preferences) ?string { |
| 2717 | trimmed := line.trim_space() |
| 2718 | if trimmed.starts_with('#pkgconfig') { |
| 2719 | if prefs != unsafe { nil } && prefs.is_cross_target() { |
| 2720 | return none |
| 2721 | } |
| 2722 | rest := trimmed['#pkgconfig'.len..].trim_space() |
| 2723 | return resolve_pkgconfig_directive_flags(rest) |
| 2724 | } |
| 2725 | if !trimmed.starts_with('#flag') { |
| 2726 | return none |
| 2727 | } |
| 2728 | mut rest := trimmed['#flag'.len..].trim_space() |
| 2729 | if rest == '' { |
| 2730 | return none |
| 2731 | } |
| 2732 | if comment_idx := rest.index('//') { |
| 2733 | rest = rest[..comment_idx].trim_space() |
| 2734 | if rest == '' { |
| 2735 | return none |
| 2736 | } |
| 2737 | } |
| 2738 | parts := rest.fields() |
| 2739 | if parts.len == 0 { |
| 2740 | return none |
| 2741 | } |
| 2742 | if !parts[0].starts_with('-') && !parts[0].starts_with('@') && parts.len > 1 { |
| 2743 | matches := if prefs == unsafe { nil } { |
| 2744 | flag_os_matches(parts[0], target_os) |
| 2745 | } else { |
| 2746 | flag_os_matches(parts[0], target_os) || flag_pref_matches(parts[0], prefs) |
| 2747 | } |
| 2748 | if !matches { |
| 2749 | return none |
| 2750 | } |
| 2751 | rest = rest[parts[0].len..].trim_space() |
| 2752 | } |
| 2753 | if rest == '' { |
| 2754 | return none |
| 2755 | } |
| 2756 | return normalize_flag_value_for_file(rest, file_path) |
| 2757 | } |
| 2758 | |
| 2759 | fn resolve_pkgconfig_directive_flags(value string) ?string { |
| 2760 | if value == '' { |
| 2761 | return none |
| 2762 | } |
| 2763 | args := if value.contains('--') { |
| 2764 | value.fields() |
| 2765 | } else { |
| 2766 | '--cflags --libs ${value}'.fields() |
| 2767 | } |
| 2768 | flags := pref.pkgconfig_result(args) or { return none } |
| 2769 | if flags == '' { |
| 2770 | return none |
| 2771 | } |
| 2772 | return flags |
| 2773 | } |
| 2774 | |
| 2775 | fn flag_references_missing_file(flag string, include_flags []string) bool { |
| 2776 | for tok in flag.fields() { |
| 2777 | clean := tok.trim('"').trim("'") |
| 2778 | if clean.len == 0 { |
| 2779 | continue |
| 2780 | } |
| 2781 | if clean.ends_with('.o') || clean.ends_with('.a') || clean.ends_with('.so') |
| 2782 | || clean.ends_with('.dylib') || clean.ends_with('.m') || clean.ends_with('.c') { |
| 2783 | if os.is_abs_path(clean) || clean.starts_with('./') || clean.starts_with('../') { |
| 2784 | if !os.exists(clean) { |
| 2785 | // For .o files, try to build from corresponding .c file |
| 2786 | if clean.ends_with('.o') { |
| 2787 | c_file := clean[..clean.len - 2] + '.c' |
| 2788 | if os.exists(c_file) { |
| 2789 | inc_flags := include_flags.join(' ') |
| 2790 | compile_cmd := 'cc -c -w -O2 ${inc_flags} "${c_file}" -o "${clean}"' |
| 2791 | res := os.execute(compile_cmd) |
| 2792 | if res.exit_code == 0 { |
| 2793 | continue // successfully compiled, not missing |
| 2794 | } |
| 2795 | } |
| 2796 | } |
| 2797 | return true |
| 2798 | } |
| 2799 | } |
| 2800 | } |
| 2801 | } |
| 2802 | return false |
| 2803 | } |
| 2804 | |
| 2805 | fn (b &Builder) collect_cflags_from_sources() string { |
| 2806 | // Collect source file paths to scan. When .vh headers were used for |
| 2807 | // parsing, b.files references the .vh summaries which lack #flag |
| 2808 | // directives. Always include the original core module source files |
| 2809 | // so that directive flags (e.g. -I paths) are never lost. |
| 2810 | mut scan_paths := []string{} |
| 2811 | for file in b.files { |
| 2812 | if file.name != '' { |
| 2813 | scan_paths << file.name |
| 2814 | } |
| 2815 | } |
| 2816 | for ff in b.flat.files { |
| 2817 | name := b.flat.file_name(ff) |
| 2818 | if name != '' { |
| 2819 | scan_paths << name |
| 2820 | } |
| 2821 | } |
| 2822 | cflags_target_os := b.cflags_target_os_for_local_compile() |
| 2823 | if !b.pref.skip_builtin { |
| 2824 | target_os := cflags_target_os |
| 2825 | for module_path in core_cached_module_paths { |
| 2826 | vlib_path := b.pref.get_vlib_module_path(module_path) |
| 2827 | module_files := get_v_files_from_dir(vlib_path, b.pref.user_defines, target_os) |
| 2828 | for mf in module_files { |
| 2829 | scan_paths << mf |
| 2830 | } |
| 2831 | } |
| 2832 | } |
| 2833 | return b.collect_cflags_from_scan_paths(scan_paths) |
| 2834 | } |
| 2835 | |
| 2836 | fn (b &Builder) source_path_is_internal_vlib(path string) bool { |
| 2837 | vlib_root := os.real_path(os.join_path(b.pref.vroot, 'vlib')).replace('\\', '/').trim_right('/') |
| 2838 | real_path := os.real_path(path).replace('\\', '/') |
| 2839 | return real_path == vlib_root || real_path.starts_with('${vlib_root}/') |
| 2840 | } |
| 2841 | |
| 2842 | fn (b &Builder) collect_user_cflags_from_sources() string { |
| 2843 | mut scan_paths := []string{} |
| 2844 | for path in b.user_files { |
| 2845 | if path != '' { |
| 2846 | scan_paths << path |
| 2847 | } |
| 2848 | } |
| 2849 | for file in b.files { |
| 2850 | if file.name != '' && !b.source_path_is_internal_vlib(file.name) { |
| 2851 | scan_paths << file.name |
| 2852 | } |
| 2853 | } |
| 2854 | for ff in b.flat.files { |
| 2855 | name := b.flat.file_name(ff) |
| 2856 | if name != '' && !b.source_path_is_internal_vlib(name) { |
| 2857 | scan_paths << name |
| 2858 | } |
| 2859 | } |
| 2860 | return b.collect_cflags_from_scan_paths(scan_paths) |
| 2861 | } |
| 2862 | |
| 2863 | fn (b &Builder) collect_cflags_from_scan_paths(paths []string) string { |
| 2864 | mut flags := []string{} |
| 2865 | mut seen := map[string]bool{} |
| 2866 | mut scanned_files := map[string]bool{} |
| 2867 | mut scan_paths := paths.clone() |
| 2868 | cflags_target_os := b.cflags_target_os_for_local_compile() |
| 2869 | scan_paths.sort() |
| 2870 | for scan_path in scan_paths { |
| 2871 | if scan_path == '' || scan_path in scanned_files { |
| 2872 | continue |
| 2873 | } |
| 2874 | scanned_files[scan_path] = true |
| 2875 | lines := os.read_lines(scan_path) or { continue } |
| 2876 | // Track $if nesting to skip flags inside non-matching comptime blocks. |
| 2877 | // skip_depth > 0 means we are inside a non-matching $if block. |
| 2878 | // chain_matched[i] tracks whether the i-th enclosing $if chain has |
| 2879 | // already matched some branch (so subsequent $else / $else $if at the |
| 2880 | // same level should be skipped even if their cond would otherwise match). |
| 2881 | mut skip_depth := 0 |
| 2882 | mut chain_matched := []bool{} |
| 2883 | for line in lines { |
| 2884 | trimmed := line.trim_space() |
| 2885 | // Strip a leading "} " so chained patterns like "} $else $if X {" parse |
| 2886 | // the same way as "$else $if X {" on their own line. |
| 2887 | rest := if trimmed.starts_with('} ') { trimmed[2..].trim_space() } else { trimmed } |
| 2888 | leading_close := rest != trimmed |
| 2889 | // $else $if cond { (a chain continuation) |
| 2890 | if rest.starts_with(r'$else $if ') { |
| 2891 | new_cond := rest[10..].trim_right('{ ').trim_space() |
| 2892 | if skip_depth > 1 { |
| 2893 | // nested skipping; just continue without touching outer state |
| 2894 | continue |
| 2895 | } |
| 2896 | cur := chain_matched.len - 1 |
| 2897 | if cur < 0 { |
| 2898 | continue |
| 2899 | } |
| 2900 | if chain_matched[cur] { |
| 2901 | skip_depth = 1 |
| 2902 | } else if comptime_cond_matches_with_context(new_cond, cflags_target_os, b.pref) { |
| 2903 | chain_matched[cur] = true |
| 2904 | skip_depth = 0 |
| 2905 | } else { |
| 2906 | skip_depth = 1 |
| 2907 | } |
| 2908 | continue |
| 2909 | } |
| 2910 | // plain $else { (chain terminator) |
| 2911 | if rest.starts_with(r'$else') { |
| 2912 | if skip_depth > 1 { |
| 2913 | continue |
| 2914 | } |
| 2915 | cur := chain_matched.len - 1 |
| 2916 | if cur < 0 { |
| 2917 | continue |
| 2918 | } |
| 2919 | if chain_matched[cur] { |
| 2920 | skip_depth = 1 |
| 2921 | } else { |
| 2922 | chain_matched[cur] = true |
| 2923 | skip_depth = 0 |
| 2924 | } |
| 2925 | continue |
| 2926 | } |
| 2927 | // $if cond { (chain opener) |
| 2928 | if rest.starts_with(r'$if ') { |
| 2929 | cond := rest[4..].trim_right('{ ').trim_space() |
| 2930 | matched := comptime_cond_matches_with_context(cond, cflags_target_os, b.pref) |
| 2931 | chain_matched << matched |
| 2932 | if skip_depth > 0 { |
| 2933 | skip_depth++ |
| 2934 | } else if !matched { |
| 2935 | skip_depth = 1 |
| 2936 | } |
| 2937 | continue |
| 2938 | } |
| 2939 | // closing } |
| 2940 | if trimmed == '}' { |
| 2941 | if skip_depth > 0 { |
| 2942 | skip_depth-- |
| 2943 | } |
| 2944 | if chain_matched.len > 0 { |
| 2945 | chain_matched.delete_last() |
| 2946 | } |
| 2947 | continue |
| 2948 | } |
| 2949 | // "} something" where the leading } closed a chain but the rest is |
| 2950 | // unrecognized: treat the } as a chain close. |
| 2951 | if leading_close && rest == '' { |
| 2952 | if skip_depth > 0 { |
| 2953 | skip_depth-- |
| 2954 | } |
| 2955 | if chain_matched.len > 0 { |
| 2956 | chain_matched.delete_last() |
| 2957 | } |
| 2958 | continue |
| 2959 | } |
| 2960 | if skip_depth > 0 { |
| 2961 | continue |
| 2962 | } |
| 2963 | // Replace @VEXEROOT before parsing so path normalization sees absolute paths |
| 2964 | resolved_line := line.replace('@VEXEROOT', b.pref.vroot).replace('VEXEROOT', |
| 2965 | b.pref.vroot) |
| 2966 | mut flag := parse_flag_directive_line_with_context(resolved_line, scan_path, |
| 2967 | cflags_target_os, b.pref) or { continue } |
| 2968 | // Build include flags from already-collected flags for compiling missing .o files |
| 2969 | mut inc_flags := []string{} |
| 2970 | for f in flags { |
| 2971 | if f.starts_with('-I') { |
| 2972 | inc_flags << f |
| 2973 | } |
| 2974 | } |
| 2975 | if flag_references_missing_file(flag, inc_flags) { |
| 2976 | continue |
| 2977 | } |
| 2978 | if flag == '' || flag in seen { |
| 2979 | continue |
| 2980 | } |
| 2981 | seen[flag] = true |
| 2982 | flags << flag |
| 2983 | } |
| 2984 | } |
| 2985 | return flags.join(' ') |
| 2986 | } |
| 2987 | |
| 2988 | // split_compile_and_link_flags separates a flags string into compiler-only |
| 2989 | // flags (for -c compilation) and linker-only flags (for the link step). |
| 2990 | // Linker flags include: -l*, -L*, -Wl,*, -Xlinker, -framework, C source files |
| 2991 | // and prebuilt object/library files. Minimal dual-use driver flags, plus -F |
| 2992 | // framework search paths, are kept in both outputs. Source files from #flag |
| 2993 | // directives must not be passed to |
| 2994 | // per-module `-c -o module.o` cache compilations. |
| 2995 | fn split_compile_and_link_flags(flags string) (string, string) { |
| 2996 | tokens := flags.fields() |
| 2997 | mut compile := []string{} |
| 2998 | mut link := []string{} |
| 2999 | mut i := 0 |
| 3000 | for i < tokens.len { |
| 3001 | tok := tokens[i] |
| 3002 | if native_driver_flag_is_dual_use(tok) { |
| 3003 | compile << tok |
| 3004 | link << tok |
| 3005 | } else if tok == '-F' { |
| 3006 | compile << tok |
| 3007 | link << tok |
| 3008 | if i + 1 < tokens.len { |
| 3009 | i++ |
| 3010 | compile << tokens[i] |
| 3011 | link << tokens[i] |
| 3012 | } |
| 3013 | } else if tok.starts_with('-F') { |
| 3014 | compile << tok |
| 3015 | link << tok |
| 3016 | } else if tok == '-Xlinker' { |
| 3017 | link << tok |
| 3018 | if i + 1 < tokens.len { |
| 3019 | i++ |
| 3020 | link << tokens[i] |
| 3021 | } |
| 3022 | } else if tok == '-framework' { |
| 3023 | // -framework Name: two tokens, linker only |
| 3024 | link << tok |
| 3025 | if i + 1 < tokens.len { |
| 3026 | i++ |
| 3027 | link << tokens[i] |
| 3028 | } |
| 3029 | } else if tok.starts_with('-Wl,') || tok.starts_with('-l') || tok.starts_with('-L') { |
| 3030 | link << tok |
| 3031 | // -L or -l alone (space-separated from its argument): grab the next token |
| 3032 | if (tok == '-L' || tok == '-l') && i + 1 < tokens.len { |
| 3033 | i++ |
| 3034 | link << tokens[i] |
| 3035 | } |
| 3036 | } else if tok.ends_with('.c') || tok.ends_with('.cc') || tok.ends_with('.cpp') |
| 3037 | || tok.ends_with('.cxx') || tok.ends_with('.m') || tok.ends_with('.mm') |
| 3038 | || tok.ends_with('.o') || tok.ends_with('.obj') || tok.ends_with('.a') |
| 3039 | || tok.ends_with('.so') || tok.ends_with('.dylib') { |
| 3040 | link << tok |
| 3041 | } else if tok == '-I' && i + 1 < tokens.len { |
| 3042 | // -I alone (space-separated from its argument): grab the next token |
| 3043 | compile << tok |
| 3044 | i++ |
| 3045 | compile << tokens[i] |
| 3046 | } else { |
| 3047 | compile << tok |
| 3048 | } |
| 3049 | i++ |
| 3050 | } |
| 3051 | return compile.join(' '), link.join(' ') |
| 3052 | } |
| 3053 | |
| 3054 | fn comptime_cond_matches(cond string, target_os string) bool { |
| 3055 | return comptime_cond_matches_with_context(cond, target_os, unsafe { &pref.Preferences(nil) }) |
| 3056 | } |
| 3057 | |
| 3058 | fn comptime_cond_matches_with_pref(cond string, prefs &pref.Preferences) bool { |
| 3059 | target_os := if prefs == unsafe { nil } { '' } else { prefs.target_os_or_host() } |
| 3060 | return comptime_cond_matches_with_context(cond, target_os, prefs) |
| 3061 | } |
| 3062 | |
| 3063 | fn comptime_cond_matches_with_context(cond string, target_os string, prefs &pref.Preferences) bool { |
| 3064 | trimmed := cond.trim_space() |
| 3065 | if or_idx := top_level_bool_op_index(trimmed, '||') { |
| 3066 | left := trimmed[..or_idx].trim_space() |
| 3067 | right := trimmed[or_idx + 2..].trim_space() |
| 3068 | return comptime_cond_matches_with_context(left, target_os, prefs) |
| 3069 | || comptime_cond_matches_with_context(right, target_os, prefs) |
| 3070 | } |
| 3071 | if and_idx := top_level_bool_op_index(trimmed, '&&') { |
| 3072 | left := trimmed[..and_idx].trim_space() |
| 3073 | right := trimmed[and_idx + 2..].trim_space() |
| 3074 | return comptime_cond_matches_with_context(left, target_os, prefs) |
| 3075 | && comptime_cond_matches_with_context(right, target_os, prefs) |
| 3076 | } |
| 3077 | if trimmed.starts_with('!') { |
| 3078 | return !comptime_cond_matches_with_context(trimmed[1..], target_os, prefs) |
| 3079 | } |
| 3080 | if stripped := strip_outer_bool_parens(trimmed) { |
| 3081 | return comptime_cond_matches_with_context(stripped, target_os, prefs) |
| 3082 | } |
| 3083 | if optional_name := optional_user_ct_flag_name(trimmed) { |
| 3084 | if prefs == unsafe { nil } { |
| 3085 | return false |
| 3086 | } |
| 3087 | return pref.comptime_optional_flag_value(prefs, optional_name) |
| 3088 | } |
| 3089 | if pkg_name := pkgconfig_cond_name(trimmed) { |
| 3090 | if prefs != unsafe { nil } && prefs.is_cross_target() { |
| 3091 | return false |
| 3092 | } |
| 3093 | return pref.comptime_pkgconfig_value(pkg_name) |
| 3094 | } |
| 3095 | if prefs != unsafe { nil } { |
| 3096 | return flag_os_matches(trimmed, target_os) || flag_pref_matches(trimmed, prefs) |
| 3097 | } |
| 3098 | current := normalize_target_os_name(target_os) |
| 3099 | return match trimmed.to_lower() { |
| 3100 | 'macos', 'darwin', 'mac' { current == 'macos' || current == 'darwin' } |
| 3101 | 'linux' { current == 'linux' } |
| 3102 | 'windows' { current == 'windows' } |
| 3103 | 'bsd' { current in ['macos', 'freebsd', 'openbsd', 'netbsd', 'dragonfly'] } |
| 3104 | 'freebsd' { current == 'freebsd' } |
| 3105 | 'openbsd' { current == 'openbsd' } |
| 3106 | 'netbsd' { current == 'netbsd' } |
| 3107 | 'dragonfly' { current == 'dragonfly' } |
| 3108 | 'android' { current == 'android' } |
| 3109 | 'termux' { current == 'termux' } |
| 3110 | 'ios' { current == 'ios' } |
| 3111 | 'solaris' { current == 'solaris' } |
| 3112 | 'qnx' { current == 'qnx' } |
| 3113 | 'serenity' { current == 'serenity' } |
| 3114 | 'plan9' { current == 'plan9' } |
| 3115 | 'vinix' { current == 'vinix' } |
| 3116 | 'cross' { current == 'cross' } |
| 3117 | 'native' { false } |
| 3118 | 'emscripten' { false } |
| 3119 | else { false } // unknown user-defined flags default to false |
| 3120 | } |
| 3121 | } |
| 3122 | |
| 3123 | fn pkgconfig_cond_name(cond string) ?string { |
| 3124 | trimmed := cond.trim_space() |
| 3125 | if !trimmed.starts_with(r'$pkgconfig(') || !trimmed.ends_with(')') { |
| 3126 | return none |
| 3127 | } |
| 3128 | arg := trimmed[r'$pkgconfig('.len..trimmed.len - 1].trim_space() |
| 3129 | if arg.len < 2 { |
| 3130 | return none |
| 3131 | } |
| 3132 | quote := arg[0] |
| 3133 | if (quote != `'` && quote != `"`) || arg[arg.len - 1] != quote { |
| 3134 | return none |
| 3135 | } |
| 3136 | return arg[1..arg.len - 1] |
| 3137 | } |
| 3138 | |
| 3139 | fn optional_user_ct_flag_name(cond string) ?string { |
| 3140 | trimmed := cond.trim_space() |
| 3141 | if !trimmed.ends_with('?') { |
| 3142 | return none |
| 3143 | } |
| 3144 | name := trimmed[..trimmed.len - 1].trim_space() |
| 3145 | if name == '' { |
| 3146 | return none |
| 3147 | } |
| 3148 | return name |
| 3149 | } |
| 3150 | |
| 3151 | fn top_level_bool_op_index(expr string, op string) ?int { |
| 3152 | mut depth := 0 |
| 3153 | mut i := 0 |
| 3154 | for i < expr.len { |
| 3155 | ch := expr[i] |
| 3156 | if ch == `(` { |
| 3157 | depth++ |
| 3158 | } else if ch == `)` { |
| 3159 | if depth > 0 { |
| 3160 | depth-- |
| 3161 | } |
| 3162 | } else if depth == 0 && i + op.len <= expr.len && expr[i..i + op.len] == op { |
| 3163 | return i |
| 3164 | } |
| 3165 | i++ |
| 3166 | } |
| 3167 | return none |
| 3168 | } |
| 3169 | |
| 3170 | fn strip_outer_bool_parens(expr string) ?string { |
| 3171 | if expr.len < 2 || expr[0] != `(` || expr[expr.len - 1] != `)` { |
| 3172 | return none |
| 3173 | } |
| 3174 | mut depth := 0 |
| 3175 | for i, ch in expr { |
| 3176 | if ch == `(` { |
| 3177 | depth++ |
| 3178 | } else if ch == `)` { |
| 3179 | depth-- |
| 3180 | if depth == 0 && i < expr.len - 1 { |
| 3181 | return none |
| 3182 | } |
| 3183 | } |
| 3184 | } |
| 3185 | if depth == 0 { |
| 3186 | return expr[1..expr.len - 1].trim_space() |
| 3187 | } |
| 3188 | return none |
| 3189 | } |
| 3190 | |
| 3191 | fn default_cc(vroot string) string { |
| 3192 | // Try to use tcc by default, like v1 does. |
| 3193 | tcc_path := os.join_path(vroot, 'thirdparty', 'tcc', 'tcc.exe') |
| 3194 | if os.exists(tcc_path) { |
| 3195 | return tcc_path |
| 3196 | } |
| 3197 | return 'cc' |
| 3198 | } |
| 3199 | |
| 3200 | // fast_relink_output_is_generation_only mirrors gen_cleanc()'s generation-only |
| 3201 | // decision: a `.c` output, a target we cannot compile locally, or a shared lib. |
| 3202 | // Such a request must go through normal C generation, never the pre-parse relink |
| 3203 | // (is_cmd_v2_self_build() keys only on the input file, so the relink path would |
| 3204 | // otherwise link an executable into e.g. foo.c). Extracted so the decision is |
| 3205 | // unit-testable without a warm object cache. |
| 3206 | fn (b &Builder) fast_relink_output_is_generation_only(output_name string) bool { |
| 3207 | return output_name.ends_with('.c') || !b.can_compile_cleanc_locally() || b.pref.is_shared_lib |
| 3208 | } |
| 3209 | |
| 3210 | // preparse_flag_fingerprint captures the flag-affecting build inputs that are |
| 3211 | // knowable WITHOUT parsing the sources: the C compiler choice (-cc / V2CC / |
| 3212 | // default), prod/shared mode, and the env CFLAGS (V2CFLAGS). It is recorded in |
| 3213 | // main.stamp and re-checked by the pre-parse fast relink so a changed compiler or |
| 3214 | // CFLAGS environment invalidates a relink even when every source file is |
| 3215 | // unchanged. Source-derived `#flag` directives are intentionally excluded — they |
| 3216 | // change only when a source file changes, which the stamp freshness checks catch. |
| 3217 | fn (b &Builder) preparse_flag_fingerprint() string { |
| 3218 | cc := if b.pref.ccompiler.len > 0 { b.pref.ccompiler } else { configured_cc(b.pref.vroot) } |
| 3219 | return 'cc=${cc}\x01ccpref=${b.pref.ccompiler}\x01prod=${b.pref.is_prod}\x01shared=${b.pref.is_shared_lib}\x01env=${configured_cflags()}' |
| 3220 | } |
| 3221 | |
| 3222 | fn configured_cc(vroot string) string { |
| 3223 | cc := (os.getenv_opt('V2CC') or { '' }).trim_space() |
| 3224 | if cc != '' { |
| 3225 | return cc |
| 3226 | } |
| 3227 | return default_cc(vroot) |
| 3228 | } |
| 3229 | |
| 3230 | fn configured_cflags() string { |
| 3231 | return (os.getenv_opt('V2CFLAGS') or { '' }).trim_space() |
| 3232 | } |
| 3233 | |
| 3234 | fn tcc_flags(cc string, vroot string) string { |
| 3235 | if !cc.contains('tcc') { |
| 3236 | return '' |
| 3237 | } |
| 3238 | tcc_dir := os.join_path(vroot, 'thirdparty', 'tcc') |
| 3239 | return '-I "${os.join_path(tcc_dir, 'lib', 'include')}" -L "${os.join_path(tcc_dir, 'lib')}"' |
| 3240 | } |
| 3241 | |
| 3242 | fn cflags_need_objc_mode(flags string) bool { |
| 3243 | lower_flags := flags.to_lower() |
| 3244 | for tok in lower_flags.fields() { |
| 3245 | clean := tok.trim('"\'') |
| 3246 | if clean.ends_with('.m') || clean.ends_with('.mm') { |
| 3247 | return true |
| 3248 | } |
| 3249 | } |
| 3250 | return lower_flags.contains('-framework cocoa') || lower_flags.contains('-framework appkit') |
| 3251 | || lower_flags.contains('-framework foundation') || lower_flags.contains('-framework uikit') |
| 3252 | || lower_flags.contains('-framework metal') || lower_flags.contains('-framework metalkit') |
| 3253 | || lower_flags.contains('-framework quartzcore') |
| 3254 | } |
| 3255 | |
| 3256 | fn cc_recompile_flags_from_cmd(cmd string) string { |
| 3257 | parts := cmd.fields() |
| 3258 | mut flags := []string{} |
| 3259 | mut i := 1 // skip compiler |
| 3260 | for i < parts.len { |
| 3261 | p := parts[i] |
| 3262 | if p == '-o' { |
| 3263 | i += 2 |
| 3264 | continue |
| 3265 | } |
| 3266 | if p == '-x' { |
| 3267 | if i + 1 < parts.len && parts[i + 1] != 'none' { |
| 3268 | flags << p |
| 3269 | flags << parts[i + 1] |
| 3270 | } |
| 3271 | i += 2 |
| 3272 | continue |
| 3273 | } |
| 3274 | if p in ['-I', '-D', '-U', '-F', '-include', '-isystem', '-idirafter'] { |
| 3275 | flags << p |
| 3276 | if i + 1 < parts.len { |
| 3277 | i++ |
| 3278 | flags << parts[i] |
| 3279 | } |
| 3280 | i++ |
| 3281 | continue |
| 3282 | } |
| 3283 | if p.starts_with('-I') || p.starts_with('-D') || p.starts_with('-U') || p.starts_with('-F') |
| 3284 | || p.starts_with('-std=') || p.starts_with('-W') || p.starts_with('-f') |
| 3285 | || p.starts_with('-m') || p == '-pthread' { |
| 3286 | flags << p |
| 3287 | } |
| 3288 | i++ |
| 3289 | } |
| 3290 | return flags.join(' ') |
| 3291 | } |
| 3292 | |
| 3293 | // run_cc_cmd_or_exit runs a C compiler command, falling back from tcc to cc |
| 3294 | // if needed. Returns true if tcc fell back to cc. |
| 3295 | fn run_cc_cmd_or_exit(cmd string, stage string, show_cc bool) bool { |
| 3296 | if show_cc { |
| 3297 | println(cmd) |
| 3298 | } else if os.getenv('V2VERBOSE') != '' { |
| 3299 | dump(cmd) |
| 3300 | } |
| 3301 | result := os.execute(cmd) |
| 3302 | if result.exit_code != 0 { |
| 3303 | // If tcc failed, fall back to cc. |
| 3304 | // Check only the compiler binary (before the first space), not the full |
| 3305 | // command string which contains tcc in include/library flag paths. |
| 3306 | cc_binary := cmd.all_before(' ') |
| 3307 | if cc_binary.contains('tcc') { |
| 3308 | eprintln('Failed to compile with tcc, falling back to cc') |
| 3309 | eprintln('tcc cmd: ${cmd}') |
| 3310 | eprintln(result.output) |
| 3311 | // Replace TCC binary with cc and strip TCC-specific include/lib |
| 3312 | // paths. TCC's tgmath.h conflicts with macOS system headers, |
| 3313 | // causing SIMD ambiguity errors in MetalKit when compiling as |
| 3314 | // Objective-C. |
| 3315 | mut fallback_cmd := cmd.replace_once(cc_binary, 'cc') |
| 3316 | tcc_dir := cc_binary.all_before_last('/tcc') |
| 3317 | if tcc_dir.len > 0 { |
| 3318 | // Remove -I and -L flags pointing into the TCC directory. |
| 3319 | mut parts := fallback_cmd.fields() |
| 3320 | mut filtered := []string{cap: parts.len} |
| 3321 | mut i2 := 0 |
| 3322 | for i2 < parts.len { |
| 3323 | p := parts[i2] |
| 3324 | if (p == '-I' || p == '-L') && i2 + 1 < parts.len |
| 3325 | && parts[i2 + 1].contains('tcc') { |
| 3326 | i2 += 2 |
| 3327 | continue |
| 3328 | } |
| 3329 | if (p.starts_with('-I') || p.starts_with('-L')) && p.contains('tcc') { |
| 3330 | i2++ |
| 3331 | continue |
| 3332 | } |
| 3333 | filtered << p |
| 3334 | i2++ |
| 3335 | } |
| 3336 | fallback_cmd = filtered.join(' ') |
| 3337 | } |
| 3338 | // cc cannot read .o files produced by tcc on macOS arm64. Recompile |
| 3339 | // any cached .o files referenced in the command from their .c siblings |
| 3340 | // using cc before retrying the link. |
| 3341 | recompile_flags := cc_recompile_flags_from_cmd(fallback_cmd) |
| 3342 | for tok in fallback_cmd.fields() { |
| 3343 | clean_tok := tok.trim('"') |
| 3344 | if !clean_tok.ends_with('.o') { |
| 3345 | continue |
| 3346 | } |
| 3347 | stem := clean_tok.all_before_last('.o') |
| 3348 | mut c_sibling := stem + '.c' |
| 3349 | if !os.exists(c_sibling) && stem.ends_with('.main') { |
| 3350 | c_sibling = stem.all_before_last('.main') + '.c' |
| 3351 | } |
| 3352 | if !os.exists(c_sibling) { |
| 3353 | continue |
| 3354 | } |
| 3355 | recompile_cmd := 'cc ${recompile_flags} -w -Wno-incompatible-function-pointer-types -c "${c_sibling}" -o "${clean_tok}"' |
| 3356 | if show_cc { |
| 3357 | println(recompile_cmd) |
| 3358 | } |
| 3359 | rr := os.execute(recompile_cmd) |
| 3360 | if rr.exit_code != 0 { |
| 3361 | eprintln('cc recompile failed for ${c_sibling}:') |
| 3362 | eprintln(rr.output) |
| 3363 | } |
| 3364 | // Invalidate stamp so future builds rebuild from .c too. |
| 3365 | stamp_path := stem + '.stamp' |
| 3366 | if os.exists(stamp_path) { |
| 3367 | os.rm(stamp_path) or {} |
| 3368 | } |
| 3369 | } |
| 3370 | run_cc_cmd_or_exit(fallback_cmd, stage, show_cc) |
| 3371 | return true |
| 3372 | } |
| 3373 | eprintln('${stage} failed:') |
| 3374 | lines := result.output.split_into_lines() |
| 3375 | limit := if lines.len < 50 { lines.len } else { 50 } |
| 3376 | for line in lines[..limit] { |
| 3377 | eprintln(line) |
| 3378 | } |
| 3379 | mut error_count := 0 |
| 3380 | mut warning_count := 0 |
| 3381 | for line in lines { |
| 3382 | if line.contains(': error:') || line.contains(': fatal error:') { |
| 3383 | error_count += 1 |
| 3384 | } else if line.contains(': warning:') { |
| 3385 | warning_count += 1 |
| 3386 | } |
| 3387 | } |
| 3388 | if stage == 'C compilation' { |
| 3389 | eprintln('Total: ${warning_count} warnings and ${error_count} errors') |
| 3390 | } |
| 3391 | exit(1) |
| 3392 | } |
| 3393 | return false |
| 3394 | } |
| 3395 | |
| 3396 | fn native_graph_stage_title(label string, title string) string { |
| 3397 | if label == '' { |
| 3398 | return title |
| 3399 | } |
| 3400 | return '${label} ${title}' |
| 3401 | } |
| 3402 | |
| 3403 | const macos_tiny_candidate_graph_label = 'macOS Tiny Candidate' |
| 3404 | |
| 3405 | fn (b &Builder) native_mir_build_sequential(label string) bool { |
| 3406 | return label == macos_tiny_candidate_graph_label || b.pref.no_parallel || b.pref.hot_fn.len > 0 |
| 3407 | } |
| 3408 | |
| 3409 | fn (b &Builder) should_prune_native_backend_modules(arch pref.Arch) bool { |
| 3410 | return b.pref.single_backend || arch == .arm64 |
| 3411 | } |
| 3412 | |
| 3413 | fn native_backend_module_file_fragment(backend_mod string) string { |
| 3414 | return match backend_mod { |
| 3415 | 'eval' { '/vlib/v2/eval/' } |
| 3416 | else { '/vlib/v2/gen/${backend_mod}/' } |
| 3417 | } |
| 3418 | } |
| 3419 | |
| 3420 | fn (mut b Builder) build_native_mir_from_files(files []ast.File, arch pref.Arch, target_os string, minimal_runtime_roots bool, used_fn_keys map[string]bool, label string) mir.Module { |
| 3421 | mut mod := ssa.Module.new('main') |
| 3422 | if mod == unsafe { nil } { |
| 3423 | eprintln('error: native backend not available (compiled with stubbed ssa module)') |
| 3424 | eprintln('hint: use v2 compiled with v1 for native code generation') |
| 3425 | exit(1) |
| 3426 | } |
| 3427 | mut ssa_builder := ssa.Builder.new_with_env(mod, b.env) |
| 3428 | ssa_builder.guard_invalid_type_payloads = true |
| 3429 | ssa_builder.target_os = target_os |
| 3430 | ssa_builder.minimal_runtime_roots = minimal_runtime_roots |
| 3431 | ssa_builder.native_backend_bulk_zero_alloca = arch == .x64 |
| 3432 | mut native_sw := time.new_stopwatch() |
| 3433 | |
| 3434 | // Pass markused data for dead code elimination. The ARM64 backend has its own |
| 3435 | // relocation-based dead stripping; using markused before SSA makes self-hosted |
| 3436 | // compiler builds fragile when the markused set is under-collected. |
| 3437 | if used_fn_keys.len > 0 && arch != .arm64 { |
| 3438 | ssa_builder.used_fn_keys = used_fn_keys.clone() |
| 3439 | } |
| 3440 | |
| 3441 | // Strip unused compiler backend modules before SSA. ARM64 already strips |
| 3442 | // these symbols after codegen, so avoid building MIR for them in the first place. |
| 3443 | if b.should_prune_native_backend_modules(arch) { |
| 3444 | all_backends := ['cleanc', 'eval', 'v', 'c', 'x64', 'arm64'] |
| 3445 | own := match b.pref.backend { |
| 3446 | .arm64 { 'arm64' } |
| 3447 | .x64 { 'x64' } |
| 3448 | .cleanc { 'cleanc' } |
| 3449 | .v { 'v' } |
| 3450 | .c { 'c' } |
| 3451 | .eval { 'eval' } |
| 3452 | } |
| 3453 | |
| 3454 | for backend_mod in all_backends { |
| 3455 | if backend_mod != own { |
| 3456 | ssa_builder.skip_modules[backend_mod] = true |
| 3457 | ssa_builder.skip_module_file_fragments[backend_mod] = |
| 3458 | native_backend_module_file_fragment(backend_mod) |
| 3459 | } |
| 3460 | } |
| 3461 | } |
| 3462 | |
| 3463 | // In hot_fn mode, only build the target function body (skip all others) |
| 3464 | if b.pref.hot_fn.len > 0 { |
| 3465 | ssa_builder.hot_fn = b.pref.hot_fn |
| 3466 | } |
| 3467 | |
| 3468 | mut stage_start := native_sw.elapsed() |
| 3469 | // Route the whole SSA build through the cursor-native build_all_from_flat |
| 3470 | // on the post-transform b.flat (kept alive above). Sequential only |
| 3471 | // (build_all_from_flat builds fn bodies in-phase). |
| 3472 | // |
| 3473 | // b.flat is only POST-TRANSFORM when flat markused has routed transform |
| 3474 | // through transform_files_to_flat, or the direct native flat pipeline has |
| 3475 | // emitted transform output directly into FlatAst. |
| 3476 | build_from_flat := b.should_build_ssa_from_flat() |
| 3477 | || (b.flat.files.len > 0 && b.native_flat_pipeline_enabled && label == '') |
| 3478 | if build_from_flat { |
| 3479 | ssa_builder.build_all_from_flat(&b.flat) |
| 3480 | // SSA has copied the program into MIR; keep the FlatAst lifetime out of |
| 3481 | // the later optimizer and machine-code generator working sets. |
| 3482 | b.flat = ast.FlatAst{} |
| 3483 | } else if b.native_mir_build_sequential(label) { |
| 3484 | ssa_builder.build_all(files) |
| 3485 | } else { |
| 3486 | // Phases 1-3 sequential, Phase 4 parallel, Phase 5 sequential |
| 3487 | ssa_builder.skip_fn_bodies = true |
| 3488 | ssa_builder.build_all(files) |
| 3489 | ssa_builder.skip_fn_bodies = false |
| 3490 | b.ssa_build_parallel(mut ssa_builder, files) |
| 3491 | ssa_builder.generate_vinit() |
| 3492 | } |
| 3493 | if arch == .arm64 { |
| 3494 | b.env.release_expr_type_cache_after_ssa() |
| 3495 | } |
| 3496 | print_time(native_graph_stage_title(label, 'SSA Build'), |
| 3497 | time.Duration(native_sw.elapsed() - stage_start)) |
| 3498 | print_rss(native_graph_stage_title(label, 'after SSA build')) |
| 3499 | |
| 3500 | stage_start = native_sw.elapsed() |
| 3501 | ssa_optimization_required := b.native_backend_requires_ssa_optimization(arch) |
| 3502 | ssa_optimization_ran := !b.pref.no_optimize || ssa_optimization_required |
| 3503 | if ssa_optimization_required { |
| 3504 | if b.pref.no_optimize { |
| 3505 | eprintln(' opt: required for x64 backend (-O0 SSA is not supported yet)') |
| 3506 | } |
| 3507 | ssa_optimize.optimize(mut mod) |
| 3508 | } else if b.pref.no_optimize { |
| 3509 | eprintln(' opt: skipped (-O0)') |
| 3510 | } else { |
| 3511 | ssa_optimize.optimize(mut mod) |
| 3512 | } |
| 3513 | print_time(native_graph_stage_title(label, 'SSA Optimize'), |
| 3514 | time.Duration(native_sw.elapsed() - stage_start)) |
| 3515 | print_rss(native_graph_stage_title(label, 'after SSA optimize')) |
| 3516 | $if debug { |
| 3517 | // Post-opt SSA verification is useful while debugging the optimizer, but it |
| 3518 | // is currently noisy enough to block normal self-host builds. Keep it |
| 3519 | // opt-in so `test_all.sh` and manual self-hosting can still complete. |
| 3520 | if ssa_optimization_ran && os.getenv('V2_VERIFY') != '' { |
| 3521 | ssa_optimize.verify_and_panic(mod, 'full optimization') |
| 3522 | } |
| 3523 | } |
| 3524 | |
| 3525 | // Post-optimization SSA dump for debugging |
| 3526 | dump_fn_name := os.getenv('V2_DUMP_OPT_SSA') |
| 3527 | if dump_fn_name.len > 0 { |
| 3528 | for func in mod.funcs { |
| 3529 | if func.name == dump_fn_name { |
| 3530 | eprintln('=== POST-OPT SSA DUMP: ${func.name} ===') |
| 3531 | eprintln(' params_len: ${func.params.len}') |
| 3532 | for pi, pid in func.params { |
| 3533 | pval := mod.values[pid] |
| 3534 | eprintln(' param[${pi}]: v${pid} kind=${pval.kind} name=`${pval.name}` typ=${pval.typ}') |
| 3535 | } |
| 3536 | for blk_id in func.blocks { |
| 3537 | blk := mod.blocks[blk_id] |
| 3538 | eprintln(' block ${blk_id} (${blk.name}):') |
| 3539 | for dval_id in blk.instrs { |
| 3540 | dval := mod.values[dval_id] |
| 3541 | if dval.kind != .instruction { |
| 3542 | continue |
| 3543 | } |
| 3544 | dinstr := mod.instrs[dval.index] |
| 3545 | mut ops_str := '' |
| 3546 | for oi, op_id in dinstr.operands { |
| 3547 | op_v := mod.values[op_id] |
| 3548 | ops_str += 'v${op_id}(${op_v.kind}:${op_v.name})' |
| 3549 | if oi < dinstr.operands.len - 1 { |
| 3550 | ops_str += ', ' |
| 3551 | } |
| 3552 | } |
| 3553 | eprintln(' v${dval_id}: ${dinstr.op} [${ops_str}] typ=${dval.typ}') |
| 3554 | } |
| 3555 | } |
| 3556 | eprintln('=== END POST-OPT SSA DUMP ===') |
| 3557 | } |
| 3558 | } |
| 3559 | } |
| 3560 | |
| 3561 | stage_start = native_sw.elapsed() |
| 3562 | mut mir_mod := mir.lower_from_ssa(mod) |
| 3563 | print_time(native_graph_stage_title(label, 'MIR Lower'), |
| 3564 | time.Duration(native_sw.elapsed() - stage_start)) |
| 3565 | mod.release_outer_arenas_after_mir_lower() |
| 3566 | print_rss(native_graph_stage_title(label, 'after MIR lower')) |
| 3567 | |
| 3568 | stage_start = native_sw.elapsed() |
| 3569 | if is_windows_x64_native_target(arch, target_os) { |
| 3570 | abi.lower_with_x64_abi(mut mir_mod, arch, native_x64_lowering_abi_for_os(target_os)) |
| 3571 | } else { |
| 3572 | abi.lower(mut mir_mod, arch) |
| 3573 | } |
| 3574 | print_time(native_graph_stage_title(label, 'ABI Lower'), |
| 3575 | time.Duration(native_sw.elapsed() - stage_start)) |
| 3576 | print_rss(native_graph_stage_title(label, 'after ABI lower')) |
| 3577 | |
| 3578 | if arch != .arm64 { |
| 3579 | stage_start = native_sw.elapsed() |
| 3580 | insel.select(mut mir_mod, arch) |
| 3581 | print_time(native_graph_stage_title(label, 'InsSel'), |
| 3582 | time.Duration(native_sw.elapsed() - stage_start)) |
| 3583 | print_rss(native_graph_stage_title(label, 'after InsSel')) |
| 3584 | } |
| 3585 | return mir_mod |
| 3586 | } |
| 3587 | |
| 3588 | fn (mut b Builder) build_macos_tiny_candidate_mir(arch pref.Arch, target_os string) mir.Module { |
| 3589 | if b.macos_tiny_candidate_source_flat.files.len == 0 |
| 3590 | && b.macos_tiny_candidate_source_files.len == 0 { |
| 3591 | eprintln('internal error: macOS tiny candidate graph was not prepared') |
| 3592 | exit(1) |
| 3593 | } |
| 3594 | mut trans := transformer.Transformer.new_with_pref(b.env, b.pref) |
| 3595 | trans.set_file_set(b.file_set) |
| 3596 | trans.enable_macos_tiny_candidate_graph() |
| 3597 | opts := markused.MarkUsedOptions{ |
| 3598 | minimal_runtime_roots: true |
| 3599 | } |
| 3600 | if b.macos_tiny_candidate_source_flat.files.len > 0 { |
| 3601 | candidate_flat := |
| 3602 | trans.transform_flat_to_flat_direct(&b.macos_tiny_candidate_source_flat, []) |
| 3603 | candidate_used_fn_keys := markused.mark_used_flat_with_options(&candidate_flat, b.env, opts) |
| 3604 | old_flat := b.flat |
| 3605 | b.flat = candidate_flat |
| 3606 | candidate_mir := b.build_native_mir_from_files([], arch, target_os, true, |
| 3607 | candidate_used_fn_keys, macos_tiny_candidate_graph_label) |
| 3608 | b.flat = old_flat |
| 3609 | return candidate_mir |
| 3610 | } |
| 3611 | candidate_files := trans.transform_files(b.macos_tiny_candidate_source_files) |
| 3612 | candidate_used_fn_keys := markused.mark_used_with_options(candidate_files, b.env, opts) |
| 3613 | return b.build_native_mir_from_files(candidate_files, arch, target_os, true, |
| 3614 | candidate_used_fn_keys, macos_tiny_candidate_graph_label) |
| 3615 | } |
| 3616 | |
| 3617 | fn (mut b Builder) gen_native(backend_arch pref.Arch) { |
| 3618 | arch := if backend_arch == .auto { b.pref.get_effective_arch() } else { backend_arch } |
| 3619 | target_os := b.pref.target_os_or_host() |
| 3620 | native_compile_flags, native_link_flags := b.native_compile_and_link_flags_from_sources() |
| 3621 | _, native_user_link_flags := b.native_user_compile_and_link_flags_from_sources() |
| 3622 | mut native_external_inputs := NativeExternalLinkInputs{ |
| 3623 | link_flags: native_link_flags |
| 3624 | } |
| 3625 | |
| 3626 | mut mir_mod := b.build_native_mir_from_files(b.files, arch, target_os, |
| 3627 | b.uses_minimal_x64_runtime_roots(), b.used_fn_keys, '') |
| 3628 | // The hosted SSA build has consumed b.files into `mir_mod`; the rest of the |
| 3629 | // normal native pipeline operates on MIR only. Drop the ~120MB legacy AST so |
| 3630 | // it can be reclaimed before codegen's working-set grows. The macOS tiny |
| 3631 | // candidate, when requested, uses its own saved source snapshot. |
| 3632 | b.files = []ast.File{} |
| 3633 | |
| 3634 | // Determine output binary name from the last user file |
| 3635 | output_binary := if b.pref.output_file != '' { |
| 3636 | b.pref.output_file |
| 3637 | } else if b.user_files.len > 0 { |
| 3638 | b.default_output_name() |
| 3639 | } else { |
| 3640 | 'out' |
| 3641 | } |
| 3642 | |
| 3643 | if arch == .arm64 && is_macos_native_target(target_os) { |
| 3644 | // Use built-in linker for ARM64 macOS |
| 3645 | mut native_sw := time.new_stopwatch() |
| 3646 | stage_start := native_sw.elapsed() |
| 3647 | mut gen := arm64.Gen.new(&mir_mod) |
| 3648 | if b.pref.no_parallel { |
| 3649 | gen.gen() |
| 3650 | } else { |
| 3651 | b.gen_arm64_parallel(mut gen) |
| 3652 | } |
| 3653 | gen.release_scratch_after_gen() |
| 3654 | mir_mod.release_after_native_codegen() |
| 3655 | print_time('ARM64 Gen', time.Duration(native_sw.elapsed() - stage_start)) |
| 3656 | |
| 3657 | if b.pref.hot_fn.len > 0 { |
| 3658 | // Hot code reloading: extract raw machine code for a single function |
| 3659 | code := gen.extract_function(b.pref.hot_fn) |
| 3660 | if code.len > 0 { |
| 3661 | os.write_file_array(output_binary, code) or { panic(err) } |
| 3662 | println('hot-fn: wrote ${code.len} bytes for "${b.pref.hot_fn}" to ${output_binary}') |
| 3663 | } |
| 3664 | return |
| 3665 | } |
| 3666 | |
| 3667 | gen.link_executable(output_binary) |
| 3668 | |
| 3669 | if b.pref.verbose { |
| 3670 | println('[*] Linked ${output_binary} (built-in linker)') |
| 3671 | } |
| 3672 | } else { |
| 3673 | // Generate object file and use external linker |
| 3674 | obj_file := native_external_object_file(output_binary, target_os) |
| 3675 | mut used_macos_tiny_object := false |
| 3676 | |
| 3677 | if arch == .arm64 { |
| 3678 | mut gen := arm64.Gen.new(&mir_mod) |
| 3679 | if b.pref.no_parallel { |
| 3680 | gen.gen() |
| 3681 | } else { |
| 3682 | b.gen_arm64_parallel(mut gen) |
| 3683 | } |
| 3684 | gen.release_scratch_after_gen() |
| 3685 | mir_mod.release_after_native_codegen() |
| 3686 | gen.write_file(obj_file) |
| 3687 | } else { |
| 3688 | obj_format := native_x64_object_format_for_os(target_os) |
| 3689 | codegen_abi := native_x64_codegen_abi_for_os(target_os) |
| 3690 | if msg := native_x64_mir_unsupported_external_symbol_message(&mir_mod, obj_format) { |
| 3691 | eprint_native_x64_link_error(msg) |
| 3692 | exit(1) |
| 3693 | } |
| 3694 | if is_windows_x64_native_target(arch, target_os) { |
| 3695 | mut windows_gen := x64.Gen.new_with_format_and_abi(&mir_mod, obj_format, |
| 3696 | codegen_abi) |
| 3697 | windows_gen.gen() |
| 3698 | windows_gen.link_executable(output_binary) or { |
| 3699 | eprint_native_x64_link_error(err.msg()) |
| 3700 | exit(1) |
| 3701 | } |
| 3702 | if b.pref.verbose { |
| 3703 | println('[*] Linked ${output_binary} (built-in PE linker)') |
| 3704 | } |
| 3705 | return |
| 3706 | } |
| 3707 | if is_linux_x64_native_target(arch, target_os) { |
| 3708 | mut linux_gen := x64.Gen.new_with_format_and_abi(&mir_mod, obj_format, codegen_abi) |
| 3709 | linux_gen.gen() |
| 3710 | if native_link_flags_allow_builtin_linux_tiny(native_link_flags, |
| 3711 | native_user_link_flags) |
| 3712 | { |
| 3713 | if os.exists(output_binary) { |
| 3714 | os.rm(output_binary) or {} |
| 3715 | } |
| 3716 | linux_gen.link_linux_tiny_executable(output_binary) or { |
| 3717 | msg := err.msg() |
| 3718 | if linux_x64_tiny_strict_enabled() |
| 3719 | || !msg.starts_with(x64.linux_tiny_not_eligible_prefix) { |
| 3720 | eprint_native_x64_link_error(msg) |
| 3721 | exit(1) |
| 3722 | } |
| 3723 | } |
| 3724 | if os.exists(output_binary) { |
| 3725 | if b.pref.verbose { |
| 3726 | println('[*] Linked ${output_binary} (built-in Linux tiny linker)') |
| 3727 | } |
| 3728 | return |
| 3729 | } |
| 3730 | } |
| 3731 | linux_gen.write_file(obj_file) |
| 3732 | } else if b.uses_macos_x64_tiny_object(arch) { |
| 3733 | if os.exists(obj_file) { |
| 3734 | os.rm(obj_file) or {} |
| 3735 | } |
| 3736 | if b.pref.verbose { |
| 3737 | println('[*] macOS tiny object candidate enabled') |
| 3738 | } |
| 3739 | mut candidate_mir := b.build_macos_tiny_candidate_mir(arch, target_os) |
| 3740 | mut candidate_gen := x64.Gen.new_with_format_and_abi(&candidate_mir, obj_format, |
| 3741 | codegen_abi) |
| 3742 | candidate_gen.gen() |
| 3743 | candidate_gen.write_macos_tiny_object(obj_file) or { |
| 3744 | msg := err.msg() |
| 3745 | if !msg.starts_with(x64.macos_tiny_not_eligible_prefix) { |
| 3746 | eprint_native_x64_link_error(msg) |
| 3747 | exit(1) |
| 3748 | } |
| 3749 | if b.pref.verbose { |
| 3750 | println('[*] macOS tiny object not eligible; falling back to normal Mach-O object: ${msg}') |
| 3751 | } |
| 3752 | } |
| 3753 | if os.exists(obj_file) { |
| 3754 | used_macos_tiny_object = true |
| 3755 | } else { |
| 3756 | if b.pref.verbose { |
| 3757 | println('[*] macOS tiny object fallback: writing normal Mach-O object') |
| 3758 | } |
| 3759 | mut macos_fallback_gen := x64.Gen.new_with_format_and_abi(&mir_mod, obj_format, |
| 3760 | codegen_abi) |
| 3761 | macos_fallback_gen.gen() |
| 3762 | macos_fallback_gen.write_file(obj_file) |
| 3763 | } |
| 3764 | } else { |
| 3765 | mut normal_x64_gen := x64.Gen.new_with_format_and_abi(&mir_mod, obj_format, |
| 3766 | codegen_abi) |
| 3767 | normal_x64_gen.gen() |
| 3768 | normal_x64_gen.write_file(obj_file) |
| 3769 | } |
| 3770 | } |
| 3771 | |
| 3772 | if b.pref.verbose { |
| 3773 | if used_macos_tiny_object { |
| 3774 | println('[*] Wrote ${obj_file} (macOS tiny candidate object)') |
| 3775 | } else { |
| 3776 | println('[*] Wrote ${obj_file}') |
| 3777 | } |
| 3778 | } |
| 3779 | |
| 3780 | // Link the object file into an executable |
| 3781 | if is_macos_native_target(target_os) { |
| 3782 | sdk_res := os.execute('xcrun -sdk macosx --show-sdk-path') |
| 3783 | if sdk_res.exit_code != 0 { |
| 3784 | eprintln('Link failed:') |
| 3785 | eprintln('failed to resolve macOS SDK path with xcrun:') |
| 3786 | eprintln(sdk_res.output) |
| 3787 | exit(1) |
| 3788 | } |
| 3789 | sdk_path := macos_sdk_path_from_xcrun_output(sdk_res.output) or { |
| 3790 | eprintln('Link failed:') |
| 3791 | eprintln(err.msg()) |
| 3792 | eprintln('xcrun output:') |
| 3793 | eprintln(sdk_res.output) |
| 3794 | exit(1) |
| 3795 | } |
| 3796 | validate_macos_sdk_path_for_native_link(sdk_path) or { |
| 3797 | eprintln('Link failed:') |
| 3798 | eprintln(err.msg()) |
| 3799 | exit(1) |
| 3800 | } |
| 3801 | arch_flag := if arch == .arm64 { 'arm64' } else { 'x86_64' } |
| 3802 | native_external_inputs = native_external_link_inputs(native_link_flags, output_binary) or { |
| 3803 | eprint_native_x64_link_error(err.msg()) |
| 3804 | exit(1) |
| 3805 | } |
| 3806 | validate_macos_native_ld_link_flags(native_external_inputs.link_flags) or { |
| 3807 | eprint_native_x64_link_error(err.msg()) |
| 3808 | exit(1) |
| 3809 | } |
| 3810 | b.compile_native_external_sources(native_external_inputs, native_compile_flags, |
| 3811 | target_os, sdk_path, arch_flag) or { |
| 3812 | cleanup_native_external_objects(native_external_inputs) |
| 3813 | eprint_native_x64_link_error(err.msg()) |
| 3814 | exit(1) |
| 3815 | } |
| 3816 | normal_link_cmd := macos_native_link_command(output_binary, obj_file, sdk_path, |
| 3817 | arch_flag, false, native_external_inputs.link_flags) |
| 3818 | link_cmd := macos_native_link_command(output_binary, obj_file, sdk_path, arch_flag, |
| 3819 | used_macos_tiny_object, native_external_inputs.link_flags) |
| 3820 | mut link_result := os.execute(link_cmd) |
| 3821 | if link_result.exit_code != 0 && used_macos_tiny_object { |
| 3822 | if b.pref.verbose { |
| 3823 | println('[*] macOS tiny object link failed; retrying with normal Mach-O object') |
| 3824 | println('[*] macOS tiny object link exit code: ${link_result.exit_code}') |
| 3825 | println('[*] macOS tiny object link output:') |
| 3826 | if link_result.output.len > 0 { |
| 3827 | print(link_result.output) |
| 3828 | if !link_result.output.ends_with('\n') { |
| 3829 | println('') |
| 3830 | } |
| 3831 | } else { |
| 3832 | println('<empty>') |
| 3833 | } |
| 3834 | } |
| 3835 | if os.exists(output_binary) { |
| 3836 | os.rm(output_binary) or {} |
| 3837 | } |
| 3838 | obj_format := native_x64_object_format_for_os(target_os) |
| 3839 | codegen_abi := native_x64_codegen_abi_for_os(target_os) |
| 3840 | mut fallback_gen := x64.Gen.new_with_format_and_abi(&mir_mod, obj_format, |
| 3841 | codegen_abi) |
| 3842 | fallback_gen.gen() |
| 3843 | fallback_gen.write_file(obj_file) |
| 3844 | used_macos_tiny_object = false |
| 3845 | if b.pref.verbose { |
| 3846 | println('[*] Wrote ${obj_file} (normal Mach-O fallback after macOS tiny link failure)') |
| 3847 | } |
| 3848 | link_result = os.execute(normal_link_cmd) |
| 3849 | } |
| 3850 | if link_result.exit_code != 0 { |
| 3851 | eprintln('Link failed:') |
| 3852 | eprintln(link_result.output) |
| 3853 | cleanup_native_external_objects(native_external_inputs) |
| 3854 | exit(1) |
| 3855 | } |
| 3856 | if b.pref.verbose && used_macos_tiny_object { |
| 3857 | println('[*] Linked ${output_binary} (built-in macOS tiny object)') |
| 3858 | } |
| 3859 | } else { |
| 3860 | // Linux linking |
| 3861 | native_external_inputs = native_external_link_inputs(native_link_flags, output_binary) or { |
| 3862 | eprint_native_x64_link_error(err.msg()) |
| 3863 | exit(1) |
| 3864 | } |
| 3865 | b.compile_native_external_sources(native_external_inputs, native_compile_flags, |
| 3866 | target_os, '', '') or { |
| 3867 | cleanup_native_external_objects(native_external_inputs) |
| 3868 | eprint_native_x64_link_error(err.msg()) |
| 3869 | exit(1) |
| 3870 | } |
| 3871 | link_result := os.execute(linux_native_link_command(b.native_linux_hosted_link_compiler(), |
| 3872 | output_binary, obj_file, native_external_inputs.link_flags)) |
| 3873 | if link_result.exit_code != 0 { |
| 3874 | eprintln('Link failed:') |
| 3875 | eprintln(link_result.output) |
| 3876 | cleanup_native_external_objects(native_external_inputs) |
| 3877 | exit(1) |
| 3878 | } |
| 3879 | } |
| 3880 | |
| 3881 | if b.pref.verbose { |
| 3882 | println('[*] Linked ${output_binary}') |
| 3883 | } |
| 3884 | |
| 3885 | // Clean up object file |
| 3886 | if !b.pref.keep_c { |
| 3887 | cleanup_native_external_objects(native_external_inputs) |
| 3888 | os.rm(obj_file) or {} |
| 3889 | } |
| 3890 | } |
| 3891 | } |
| 3892 | |
| 3893 | fn print_time(title string, time_d time.Duration) { |
| 3894 | println(' * ${title}: ${time_d.milliseconds()}ms') |
| 3895 | } |
| 3896 | |
| 3897 | fn (mut b Builder) update_parse_summary_counts() { |
| 3898 | mut parsed_full_files_n := 0 |
| 3899 | mut parsed_vh_files_n := 0 |
| 3900 | mut parsed_full_files := []string{} |
| 3901 | mut parsed_vh_files := []string{} |
| 3902 | for ff in b.flat.files { |
| 3903 | name := b.flat.file_name(ff) |
| 3904 | if name.ends_with('.vh') { |
| 3905 | parsed_vh_files_n++ |
| 3906 | parsed_vh_files << name |
| 3907 | } else { |
| 3908 | parsed_full_files_n++ |
| 3909 | parsed_full_files << name |
| 3910 | } |
| 3911 | } |
| 3912 | b.parsed_full_files_n = parsed_full_files_n |
| 3913 | b.parsed_vh_files_n = parsed_vh_files_n |
| 3914 | b.parsed_full_files = parsed_full_files |
| 3915 | b.parsed_vh_files = parsed_vh_files |
| 3916 | if b.pref.stats { |
| 3917 | b.entry_v_lines_n = count_v_lines_for_paths(b.user_files) |
| 3918 | b.parsed_v_lines_n = b.count_parsed_v_lines() |
| 3919 | } else { |
| 3920 | b.entry_v_lines_n = 0 |
| 3921 | b.parsed_v_lines_n = 0 |
| 3922 | } |
| 3923 | } |
| 3924 | |
| 3925 | fn (b &Builder) print_flat_ast_summary() { |
| 3926 | legacy_stats := ast.legacy_ast_stats(b.files) |
| 3927 | legacy_nodes := ast.count_legacy_nodes(b.files) |
| 3928 | flat_stats := b.flat.stats() |
| 3929 | mut mem_delta_pct := f64(0) |
| 3930 | if legacy_stats.bytes_estimate > 0 { |
| 3931 | mem_delta_pct = (f64(legacy_stats.bytes_estimate) - f64(flat_stats.bytes_estimate)) * 100.0 / f64(legacy_stats.bytes_estimate) |
| 3932 | } |
| 3933 | // Flat AST uses 4 arenas (files, nodes, edges, strings) regardless of payload |
| 3934 | // count; each is one allocation amortised across millions of cells. |
| 3935 | flat_allocs := u64(4) |
| 3936 | mut alloc_delta_pct := f64(0) |
| 3937 | if legacy_stats.allocs > 0 { |
| 3938 | alloc_delta_pct = (f64(legacy_stats.allocs) - f64(flat_allocs)) * 100.0 / f64(legacy_stats.allocs) |
| 3939 | } |
| 3940 | println(' * AST nodes: legacy=${legacy_nodes}, flat=${flat_stats.nodes} (edges=${flat_stats.edges}, strings=${flat_stats.strings})') |
| 3941 | println(' * AST memory est: legacy=${legacy_stats.bytes_estimate}B, flat=${flat_stats.bytes_estimate}B (${mem_delta_pct:.2f}% reduction)') |
| 3942 | println(' * AST allocs: legacy=${legacy_stats.allocs}, flat=${flat_allocs} (${alloc_delta_pct:.2f}% reduction)') |
| 3943 | if os.getenv('V2_FLAT_HIST') != '' { |
| 3944 | hist := b.flat.count_nodes_by_kind() |
| 3945 | mut keys := hist.keys() |
| 3946 | keys.sort_with_compare(fn [hist] (a &string, b &string) int { |
| 3947 | return hist[*b] - hist[*a] |
| 3948 | }) |
| 3949 | println(' * AST per-kind histogram (top 20):') |
| 3950 | for i, k in keys { |
| 3951 | if i >= 20 { |
| 3952 | break |
| 3953 | } |
| 3954 | println(' ${k:-30s} ${hist[k]}') |
| 3955 | } |
| 3956 | } |
| 3957 | } |
| 3958 | |
| 3959 | fn count_v_lines_for_paths(paths []string) int { |
| 3960 | mut seen_paths := map[string]bool{} |
| 3961 | mut total_v_lines := 0 |
| 3962 | for path in paths { |
| 3963 | norm_path := os.norm_path(path) |
| 3964 | if norm_path in seen_paths { |
| 3965 | continue |
| 3966 | } |
| 3967 | seen_paths[norm_path] = true |
| 3968 | lines := os.read_lines(norm_path) or { continue } |
| 3969 | total_v_lines += lines.len |
| 3970 | } |
| 3971 | return total_v_lines |
| 3972 | } |
| 3973 | |
| 3974 | fn (b &Builder) count_parsed_v_lines() int { |
| 3975 | mut parsed_paths := []string{} |
| 3976 | mut seen_files := map[string]bool{} |
| 3977 | for ff in b.flat.files { |
| 3978 | name := b.flat.file_name(ff) |
| 3979 | if name in seen_files { |
| 3980 | continue |
| 3981 | } |
| 3982 | seen_files[name] = true |
| 3983 | parsed_paths << name |
| 3984 | } |
| 3985 | return count_v_lines_for_paths(parsed_paths) |
| 3986 | } |
| 3987 | |
| 3988 | fn print_parse_summary(parsed_full_files_n int, parsed_vh_files_n int, entry_v_lines_n int, parsed_v_lines_n int, show_stats bool, print_parsed_files bool, parsed_full_files []string, parsed_vh_files []string) { |
| 3989 | println(' * Parsed files: fully parsed files: ${parsed_full_files_n}, parsed .vh files: ${parsed_vh_files_n}') |
| 3990 | if print_parsed_files { |
| 3991 | if parsed_full_files.len > 0 { |
| 3992 | println(' * Fully parsed files:') |
| 3993 | for path in parsed_full_files { |
| 3994 | println(' [full] ${path}') |
| 3995 | } |
| 3996 | } |
| 3997 | } |
| 3998 | if (show_stats || print_parsed_files) && parsed_vh_files.len > 0 { |
| 3999 | println(' * Parsed .vh files:') |
| 4000 | for path in parsed_vh_files { |
| 4001 | println(' [vh] ${path}') |
| 4002 | } |
| 4003 | } |
| 4004 | if show_stats { |
| 4005 | println(' * Parsed V LOC (entry files): ${entry_v_lines_n}') |
| 4006 | println(' * Parsed V LOC (all parsed sources): ${parsed_v_lines_n}') |
| 4007 | } |
| 4008 | } |
| 4009 | |