From 11af49ebc0927ba5baec8260d26040444ae48b61 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 25 Apr 2026 14:29:29 +0300 Subject: [PATCH] v2: 3x cgen speedup (caching) --- cmd/v2/test_all.sh | 2 +- cmd/v2/test_v2_self.sh | 2 +- vlib/v2/builder/gen_cleanc_parallel.v | 33 +++++++++++++ vlib/v2/gen/cleanc/cheaders.v | 14 ++++++ vlib/v2/gen/cleanc/cleanc.v | 70 +++++++++++++++++++++++---- vlib/v2/gen/cleanc/fn.v | 31 ++++++++++++ vlib/v2/gen/cleanc/struct.v | 2 + vlib/v2/gen/cleanc/types.v | 35 +++++++++++++- 8 files changed, 176 insertions(+), 13 deletions(-) diff --git a/cmd/v2/test_all.sh b/cmd/v2/test_all.sh index 26f8c140c..516f264eb 100755 --- a/cmd/v2/test_all.sh +++ b/cmd/v2/test_all.sh @@ -31,7 +31,7 @@ rm -rf /tmp/v2_cleanc_obj_cache echo "=== 1/14: ARM64 self-host hello world ===" backup_v2_src -"${v1_compiler}" -cc cc -o v2 v2.v +"${v1_compiler}" -gc none -cc cc -o v2 v2.v restore_v2_src ./v2 --no-parallel -backend arm64 -gc none -nocache -o v3 v2.v ./v3 --no-parallel -backend arm64 -o hello_arm hello.v diff --git a/cmd/v2/test_v2_self.sh b/cmd/v2/test_v2_self.sh index 18655de76..1ad625381 100755 --- a/cmd/v2/test_v2_self.sh +++ b/cmd/v2/test_v2_self.sh @@ -32,7 +32,7 @@ cp -R "${v2_src}" "${v2_bak}" # Build v2 with v1. rm -f "${v2_bin}" "${v3_bin}" "${v3_bin}.c" "${v4_bin}" "${v4_bin}.c" "${v5_bin}" "${v5_bin}.c" -"${v1_compiler}" -cc cc -o "${v2_bin}" "${v2_source}" +"${v1_compiler}" -gc none -cc cc -o "${v2_bin}" "${v2_source}" # Restore v2 sources after V1 build. rsync -a --delete "${v2_bak}/" "${v2_src}/" diff --git a/vlib/v2/builder/gen_cleanc_parallel.v b/vlib/v2/builder/gen_cleanc_parallel.v index a46324a26..79d6e8d12 100644 --- a/vlib/v2/builder/gen_cleanc_parallel.v +++ b/vlib/v2/builder/gen_cleanc_parallel.v @@ -4,6 +4,7 @@ module builder import runtime +import time import v2.gen.cleanc struct GenCleancChunkArgs { @@ -28,8 +29,28 @@ fn gen_cleanc_chunk_thread(arg voidptr) voidptr { return unsafe { nil } } +fn print_cleanc_parallel_step_time(stats_enabled bool, step string, elapsed time.Duration) { + if !stats_enabled { + return + } + println(' - C Gen/full ${step}: ${elapsed.milliseconds()}ms') +} + +fn mark_cleanc_parallel_step(stats_enabled bool, mut sw time.StopWatch, stage_start time.Duration, step string) time.Duration { + if !stats_enabled { + return stage_start + } + now := sw.elapsed() + print_cleanc_parallel_step_time(true, step, time.Duration(now - stage_start)) + return now +} + fn (mut b Builder) gen_cleanc_parallel(mut gen cleanc.Gen) { + stats_enabled := b.pref != unsafe { nil } && b.pref.stats + mut stats_sw := time.new_stopwatch() + mut stage_start := stats_sw.elapsed() emit_indices := gen.gen_pass5_pre() + stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, 'pass 5 pre') n_files := emit_indices.len n_jobs := runtime.nr_jobs() @@ -37,7 +58,10 @@ fn (mut b Builder) gen_cleanc_parallel(mut gen cleanc.Gen) { if n_files <= 1 || n_jobs <= 1 { // Fallback to sequential gen.gen_pass5_files(emit_indices) + stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, + 'pass 5 files') gen.gen_pass5_post() + _ = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, 'pass 5 post') return } @@ -59,10 +83,14 @@ fn (mut b Builder) gen_cleanc_parallel(mut gen cleanc.Gen) { for i := 0; i < n_files; i++ { chunk_indices[i % chunk_idx] << emit_indices[i] } + stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, + 'pass 5 chunk split') for ci := 0; ci < chunk_idx; ci++ { w := gen.new_pass5_worker(chunk_indices[ci], ci) workers << voidptr(w) } + stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, + 'pass 5 worker setup') // Set up args after all chunk_indices are stable for ci := 0; ci < chunk_idx; ci++ { @@ -87,12 +115,17 @@ fn (mut b Builder) gen_cleanc_parallel(mut gen cleanc.Gen) { for ci := 0; ci < chunk_idx; ci++ { C.pthread_join(thread_ids[ci], unsafe { nil }) } + stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, + 'pass 5 worker run') // Merge worker results in order for ci := 0; ci < chunk_idx; ci++ { w := unsafe { &cleanc.Gen(workers[ci]) } gen.merge_pass5_worker(w) } + stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, + 'pass 5 merge') gen.gen_pass5_post() + _ = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, 'pass 5 post') } diff --git a/vlib/v2/gen/cleanc/cheaders.v b/vlib/v2/gen/cleanc/cheaders.v index 79a07d65e..06cc8542e 100644 --- a/vlib/v2/gen/cleanc/cheaders.v +++ b/vlib/v2/gen/cleanc/cheaders.v @@ -857,6 +857,13 @@ fn (mut g Gen) lookup_struct_type_by_c_name(c_name string) types.Struct { if g.env == unsafe { nil } { return types.Struct{} } + cache_key := '${g.cur_module}|${c_name}' + if cached := g.struct_type_lookup_cache[cache_key] { + return cached + } + if cache_key in g.struct_type_lookup_miss { + return types.Struct{} + } // Try extracting module from mangled name (e.g. "os__File" -> module "os", name "File") mut mod_name := '' mut struct_name := c_name @@ -870,9 +877,11 @@ fn (mut g Gen) lookup_struct_type_by_c_name(c_name string) types.Struct { typ := obj.typ() // Skip sum types - their merged fields aren't safe for direct access if typ is types.Alias || typ is types.SumType { + g.struct_type_lookup_miss[cache_key] = true return types.Struct{} } if typ is types.Struct { + g.struct_type_lookup_cache[cache_key] = typ return typ } } @@ -890,9 +899,11 @@ fn (mut g Gen) lookup_struct_type_by_c_name(c_name string) types.Struct { if obj := scope.lookup_parent(struct_name, 0) { typ := obj.typ() if typ is types.Alias { + g.struct_type_lookup_miss[cache_key] = true return types.Struct{} } if typ is types.Struct { + g.struct_type_lookup_cache[cache_key] = typ return typ } } @@ -909,14 +920,17 @@ fn (mut g Gen) lookup_struct_type_by_c_name(c_name string) types.Struct { if obj := scope.lookup_parent(struct_name, 0) { typ := obj.typ() if typ is types.Alias { + g.struct_type_lookup_miss[cache_key] = true return types.Struct{} } if typ is types.Struct { + g.struct_type_lookup_cache[cache_key] = typ return typ } } } } + g.struct_type_lookup_miss[cache_key] = true return types.Struct{} } diff --git a/vlib/v2/gen/cleanc/cleanc.v b/vlib/v2/gen/cleanc/cleanc.v index 9fac44fe6..118616be2 100644 --- a/vlib/v2/gen/cleanc/cleanc.v +++ b/vlib/v2/gen/cleanc/cleanc.v @@ -84,6 +84,14 @@ mut: not_local_var_cache map[string]bool // per-function negative cache for get_local_var_c_type resolved_module_names map[string]string // per-function cache for resolve_module_name cached_env_scopes map[string]voidptr // cache of env_scope results (avoids repeated locking) + struct_field_lookup_cache map[string]string + struct_field_lookup_miss map[string]bool + struct_type_lookup_cache map[string]types.Struct + struct_type_lookup_miss map[string]bool + struct_decl_info_cache map[string]StructDeclInfo + struct_decl_info_miss map[string]bool + alias_base_lookup_cache map[string]string + alias_base_lookup_miss map[string]bool const_exprs map[string]string // const name → C expression string (for inlining) const_types map[string]string // const name → C type string @@ -217,16 +225,24 @@ pub fn Gen.new_with_env(files []ast.File, env &types.Environment) &Gen { pub fn Gen.new_with_env_and_pref(files []ast.File, env &types.Environment, p &pref.Preferences) &Gen { return &Gen{ - files: files - env: unsafe { env } - pref: unsafe { p } - sb: strings.new_builder(10_000) - fn_param_is_ptr: map[string][]bool{} - fn_param_types: map[string][]string{} - fn_return_types: map[string]string{} - runtime_local_types: map[string]string{} - cur_fn_returned_idents: map[string]bool{} - active_generic_types: map[string]types.Type{} + files: files + env: unsafe { env } + pref: unsafe { p } + sb: strings.new_builder(10_000) + fn_param_is_ptr: map[string][]bool{} + fn_param_types: map[string][]string{} + fn_return_types: map[string]string{} + runtime_local_types: map[string]string{} + cur_fn_returned_idents: map[string]bool{} + active_generic_types: map[string]types.Type{} + struct_field_lookup_cache: map[string]string{} + struct_field_lookup_miss: map[string]bool{} + struct_type_lookup_cache: map[string]types.Struct{} + struct_type_lookup_miss: map[string]bool{} + struct_decl_info_cache: map[string]StructDeclInfo{} + struct_decl_info_miss: map[string]bool{} + alias_base_lookup_cache: map[string]string{} + alias_base_lookup_miss: map[string]bool{} fixed_array_fields: map[string]bool{} fixed_array_field_elem: map[string]string{} @@ -1080,6 +1096,11 @@ fn (g &Gen) should_skip_plain_v_fallback_fn(fn_key string) bool { // gen_finalize runs post-pass-5 finalization and returns the complete C source string. pub fn (mut g Gen) gen_finalize() string { + stats_enabled := g.cgen_stats_enabled() + stats_scope := g.cgen_stats_scope_label() + mut stats_sw := time.new_stopwatch() + mut stage_start := stats_sw.elapsed() + // Generate test runner main if this is a test file (has test_ functions but no main) // Skip when generating cached module sources (cache_bundle_name is set) - main belongs only in the main module source if !g.has_main && g.test_fn_names.len > 0 && g.cache_bundle_name.len == 0 { @@ -1126,11 +1147,15 @@ pub fn (mut g Gen) gen_finalize() string { g.sb.writeln('\treturn 0;') g.sb.writeln('}') } + stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, + 'finalize test main') g.emit_missing_array_contains_fallbacks() g.emit_missing_runtime_fallbacks() g.emit_cached_module_init_function() g.emit_exported_const_symbols() + stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, + 'finalize fallbacks') mut out := '' // Emit deferred str macros for late-discovered generic struct instances. @@ -1142,9 +1167,13 @@ pub fn (mut g Gen) gen_finalize() string { g.late_struct_defs << '#define ${inst_name}__str(v) ((string){.str = "${label}", .len = ${label.len}, .is_lit = 1})\n#define ${inst_name}_str(v) ${inst_name}__str(v)\n' } } + stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, + 'finalize late str macros') if g.anon_fn_defs.len > 0 || g.spawn_wrapper_defs.len > 0 || g.trampoline_defs.len > 0 || g.late_struct_defs.len > 0 || g.pending_late_body_keys.len > 0 { full := g.sb.str() + stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, + 'finalize snapshot') mut out_sb := strings.new_builder(full.len + 4096) unsafe { out_sb.write_ptr(full.str, g.pass5_start_pos) } // Late-discovered generic struct definitions (discovered during setup/pass 4 codegen) @@ -1183,12 +1212,20 @@ pub fn (mut g Gen) gen_finalize() string { } else { out = g.sb.str() } + _ = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'finalize output') return out } // gen_pass5 generates Pass 5 (function bodies, globals, etc.) sequentially. fn (mut g Gen) gen_pass5() { + stats_enabled := g.cgen_stats_enabled() + stats_scope := g.cgen_stats_scope_label() + mut stats_sw := time.new_stopwatch() + mut stage_start := stats_sw.elapsed() + g.pass5_start_pos = g.sb.len + g.struct_field_lookup_cache = map[string]string{} + g.struct_field_lookup_miss = map[string]bool{} g.collect_force_emit_str_fns() // Pre-pass: emit extern forward declarations for all globals across all modules for file in g.files { @@ -1203,13 +1240,18 @@ fn (mut g Gen) gen_pass5() { } g.gen_file(file) } + stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, + 'pass 5 files') g.gen_pass5_post() + _ = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'pass 5 post') } // gen_pass5_pre runs Pass 5 sequential pre-work: extern globals and extern consts // for non-emitted modules. Returns the list of file indices that need gen_file(). pub fn (mut g Gen) gen_pass5_pre() []int { g.pass5_start_pos = g.sb.len + g.struct_field_lookup_cache = map[string]string{} + g.struct_field_lookup_miss = map[string]bool{} g.collect_force_emit_str_fns() for file in g.files { g.set_file_module(file) @@ -1353,6 +1395,14 @@ pub fn (g &Gen) new_pass5_worker(file_indices []int, worker_id int) &Gen { resolved_module_names: map[string]string{} cur_fn_mut_params: map[string]bool{} cached_env_scopes: map[string]voidptr{} + struct_field_lookup_cache: map[string]string{} + struct_field_lookup_miss: map[string]bool{} + struct_type_lookup_cache: map[string]types.Struct{} + struct_type_lookup_miss: map[string]bool{} + struct_decl_info_cache: map[string]StructDeclInfo{} + struct_decl_info_miss: map[string]bool{} + alias_base_lookup_cache: map[string]string{} + alias_base_lookup_miss: map[string]bool{} needed_interface_wrappers: map[string]bool{} needed_ierror_wrapper_bases: map[string]bool{} spawned_fns: map[string]bool{} diff --git a/vlib/v2/gen/cleanc/fn.v b/vlib/v2/gen/cleanc/fn.v index d8f33f802..3527593de 100644 --- a/vlib/v2/gen/cleanc/fn.v +++ b/vlib/v2/gen/cleanc/fn.v @@ -4621,8 +4621,19 @@ fn (g &Gen) alias_base_c_type(type_name string) ?string { if type_name == '' { return none } + cache_key := '${g.cur_module}|${type_name}' + if cached := g.alias_base_lookup_cache[cache_key] { + return cached + } + if cache_key in g.alias_base_lookup_miss { + return none + } if base_name := g.alias_base_types[type_name] { if base_name != '' && base_name != type_name { + unsafe { + mut self := g + self.alias_base_lookup_cache[cache_key] = base_name + } return base_name } } @@ -4641,12 +4652,20 @@ fn (g &Gen) alias_base_c_type(type_name string) ?string { } base_name := stmt.base_type.name().replace('.', '__') if base_name != '' && base_name != type_name { + unsafe { + mut self := g + self.alias_base_lookup_cache[cache_key] = base_name + } return base_name } } } } if g.env == unsafe { nil } { + unsafe { + mut self := g + self.alias_base_lookup_miss[cache_key] = true + } return none } mut modules := []string{} @@ -4673,6 +4692,10 @@ fn (g &Gen) alias_base_c_type(type_name string) ?string { alias_obj := obj as types.Alias base_name := g.types_type_to_c(alias_obj.base_type) if base_name != '' && base_name != type_name { + unsafe { + mut self := g + self.alias_base_lookup_cache[cache_key] = base_name + } return base_name } } @@ -4691,12 +4714,20 @@ fn (g &Gen) alias_base_c_type(type_name string) ?string { alias_obj := obj as types.Alias base_name := g.types_type_to_c(alias_obj.base_type) if base_name != '' && base_name != type_name { + unsafe { + mut self := g + self.alias_base_lookup_cache[cache_key] = base_name + } return base_name } } } } } + unsafe { + mut self := g + self.alias_base_lookup_miss[cache_key] = true + } return none } diff --git a/vlib/v2/gen/cleanc/struct.v b/vlib/v2/gen/cleanc/struct.v index 75ca636d3..a252fbf61 100644 --- a/vlib/v2/gen/cleanc/struct.v +++ b/vlib/v2/gen/cleanc/struct.v @@ -750,6 +750,8 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) { // Register field types for this instantiation g.struct_field_types['${inst.c_name}.${field_name}'] = field_type } + g.struct_field_lookup_cache = map[string]string{} + g.struct_field_lookup_miss = map[string]bool{} if node.fields.len == 0 { g.sb.writeln('\tu8 _dummy;') } diff --git a/vlib/v2/gen/cleanc/types.v b/vlib/v2/gen/cleanc/types.v index c04193807..29853be3c 100644 --- a/vlib/v2/gen/cleanc/types.v +++ b/vlib/v2/gen/cleanc/types.v @@ -1146,6 +1146,12 @@ fn (mut g Gen) find_struct_decl_info_by_c_name(c_name string) ?StructDeclInfo { if c_name == '' { return none } + if cached := g.struct_decl_info_cache[c_name] { + return cached + } + if c_name in g.struct_decl_info_miss { + return none + } saved_module := g.cur_module defer { g.cur_module = saved_module @@ -1159,14 +1165,17 @@ fn (mut g Gen) find_struct_decl_info_by_c_name(c_name string) ?StructDeclInfo { } struct_name := g.get_struct_name(stmt) if struct_name == c_name || short_type_name(struct_name) == c_name { - return StructDeclInfo{ + info := StructDeclInfo{ decl: stmt mod: g.cur_module } + g.struct_decl_info_cache[c_name] = info + return info } } } } + g.struct_decl_info_miss[c_name] = true return none } @@ -2789,6 +2798,8 @@ fn (mut g Gen) emit_late_generic_struct(base_name string, inst GenericStructInst def.writeln('\t${field_type} ${field_name};') g.struct_field_types['${inst.c_name}.${field_name}'] = field_type } + g.struct_field_lookup_cache = map[string]string{} + g.struct_field_lookup_miss = map[string]bool{} if struct_node.fields.len == 0 { def.writeln('\tu8 _dummy;') } @@ -3580,6 +3591,16 @@ fn (mut g Gen) lookup_struct_field_type_by_name(struct_name string, field_name s if struct_name == '' || field_name == '' { return none } + use_cache := g.active_generic_types.len == 0 + cache_key := if use_cache { '${g.cur_module}|${struct_name}.${field_name}' } else { '' } + if use_cache { + if cached := g.struct_field_lookup_cache[cache_key] { + return cached + } + if cache_key in g.struct_field_lookup_miss { + return none + } + } mut candidates := []string{} candidates << struct_name base_name := strip_pointer_type_name(struct_name) @@ -3590,6 +3611,9 @@ fn (mut g Gen) lookup_struct_field_type_by_name(struct_name string, field_name s full_key := '${candidate}.${field_name}' if field_type := g.struct_field_types[full_key] { if field_type != '' { + if use_cache { + g.struct_field_lookup_cache[cache_key] = field_type + } return field_type } } @@ -3598,16 +3622,25 @@ fn (mut g Gen) lookup_struct_field_type_by_name(struct_name string, field_name s short_key := '${short_name}.${field_name}' if field_type := g.struct_field_types[short_key] { if field_type != '' { + if use_cache { + g.struct_field_lookup_cache[cache_key] = field_type + } return field_type } } } if info := g.lookup_embedded_field_info(candidate, field_name) { if info.field_type != '' { + if use_cache { + g.struct_field_lookup_cache[cache_key] = info.field_type + } return info.field_type } } } + if use_cache { + g.struct_field_lookup_miss[cache_key] = true + } return none } -- 2.39.5