From 916371c8626c4eed3a044ca044d953c5dfc700a1 Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:40:19 -0400 Subject: [PATCH] gen/c: resolve each file at most once in the native-input cache scan cache_external_input_snapshot_with_resolved_flags checks, for every file node it visits, whether the file is a program file under its written or its resolved path. It called os.real_path for each visit that missed the written path, so the same files were resolved again and again. On Windows each call opens the file and queries its final path (roughly 120-170 us). Reuse the per-path memo that FlatGen.file_is_cache_program_file already used, through a shared cache_program_file_matches helper. A serial Linux cmd/v build now makes 1988 os.real_path calls instead of 2278. Co-Authored-By: WOZCODE --- vlib/v/gen/c/cache_program_file_test.v | 40 ++++++++++++++++++++++++++ vlib/v/gen/c/cleanc.v | 4 ++- vlib/v/gen/c/fn.v | 10 +++++-- 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 vlib/v/gen/c/cache_program_file_test.v diff --git a/vlib/v/gen/c/cache_program_file_test.v b/vlib/v/gen/c/cache_program_file_test.v new file mode 100644 index 00000000000000..17d6991098f453 --- /dev/null +++ b/vlib/v/gen/c/cache_program_file_test.v @@ -0,0 +1,40 @@ +module c + +import os + +// cache_program_file_matches must accept a program file under any spelling that +// resolves to it, and resolve each written path at most once per memo. +fn test_cache_program_file_matches_resolves_each_written_path_once() { + root := os.join_path(os.temp_dir(), 'v_cache_program_file_${os.getpid()}') + os.rmdir_all(root) or {} + os.mkdir_all(root)! + defer { + os.rmdir_all(root) or {} + } + program := os.join_path(root, 'main.v') + other := os.join_path(root, 'other.v') + os.write_file(program, 'module main\n')! + os.write_file(other, 'module main\n')! + program_files := { + os.real_path(program): true + } + // This spelling only matches after resolution (join_path would drop the `.`). + dotted := root + os.path_separator + '.' + os.path_separator + 'main.v' + assert !program_files[dotted] + mut memo := map[string]bool{} + assert cache_program_file_matches(program_files, dotted, mut memo) + assert !cache_program_file_matches(program_files, other, mut memo) + assert memo == { + dotted: true + other: false + } + // A memoized answer is used as is; the path is not resolved again. + mut seeded := { + other: true + } + assert cache_program_file_matches(program_files, other, mut seeded) + // Without program files nothing matches, and nothing is resolved or memoized. + mut empty_memo := map[string]bool{} + assert !cache_program_file_matches(map[string]bool{}, other, mut empty_memo) + assert empty_memo.len == 0 +} diff --git a/vlib/v/gen/c/cleanc.v b/vlib/v/gen/c/cleanc.v index e0e64018189b46..768d37239bf905 100644 --- a/vlib/v/gen/c/cleanc.v +++ b/vlib/v/gen/c/cleanc.v @@ -1926,11 +1926,13 @@ pub fn cache_external_input_snapshot_with_resolved_flags(a &flat.FlatAst, vroot mut preinclude_context_directives := []string{} mut conditional_context_mutations := map[string]bool{} mut conditionals := []CCacheConditional{} + mut program_file_memo := map[string]bool{} for node_id in c_cache_external_input_node_order(a) { node := a.nodes[node_id] if node.kind == .file { cur_file = node.value - cur_file_is_program = program_files[cur_file] || program_files[os.real_path(cur_file)] + cur_file_is_program = cache_program_file_matches(program_files, cur_file, mut + program_file_memo) cur_module = '' conditionals.clear() continue diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 93eef1bd979f1e..2e218c30c60797 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -544,13 +544,19 @@ fn (mut g FlatGen) gen_fn_items(items []FlatFnGenItem) { // file_is_cache_program_file reports whether `file`, as written or resolved, // is one of the cached program files, memoizing the answer per file. fn (g &FlatGen) file_is_cache_program_file(file string, mut memo map[string]bool) bool { - if g.cache_program_files.len == 0 { + return cache_program_file_matches(g.cache_program_files, file, mut memo) +} + +// cache_program_file_matches reports whether `file`, as written or resolved, is +// one of `program_files`. It resolves each written path at most once per memo. +fn cache_program_file_matches(program_files map[string]bool, file string, mut memo map[string]bool) bool { + if program_files.len == 0 { return false } if known := memo[file] { return known } - is_program := g.cache_program_files[file] || g.cache_program_files[os.real_path(file)] + is_program := program_files[file] || program_files[os.real_path(file)] memo[file] = is_program return is_program }