From 9e3e02d5ea5b1cb300b5f5304cd821f47337b008 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 7 Jun 2026 02:43:02 +0300 Subject: [PATCH] v2: speed up v2 C codegen (#27370) --- vlib/v2/builder/builder.v | 52 ++- vlib/v2/builder/gen_cleanc_parallel.v | 21 +- vlib/v2/builder/gen_cleanc_parallel_test.v | 9 + vlib/v2/gen/arm64/arm64.v | 2 +- vlib/v2/gen/cleanc/cleanc.v | 34 +- vlib/v2/gen/cleanc/generic_struct_scan_test.v | 9 + vlib/v2/gen/cleanc/struct.v | 386 +++++++++++++++++- vlib/v2/gen/cleanc/types.v | 24 +- vlib/v2/ssa/optimize/dce.v | 3 +- vlib/v2/ssa/optimize/fold.v | 2 +- vlib/v2/ssa/optimize/mem2reg.v | 2 +- vlib/v2/ssa/optimize/phi.v | 2 +- vlib/v2/transformer/monomorphize.v | 13 +- vlib/v2/transformer/transformer.v | 29 +- 14 files changed, 511 insertions(+), 77 deletions(-) create mode 100644 vlib/v2/builder/gen_cleanc_parallel_test.v create mode 100644 vlib/v2/gen/cleanc/generic_struct_scan_test.v diff --git a/vlib/v2/builder/builder.v b/vlib/v2/builder/builder.v index 2236f35de..f95fed576 100644 --- a/vlib/v2/builder/builder.v +++ b/vlib/v2/builder/builder.v @@ -31,25 +31,27 @@ const staged_main_obj_file = '/tmp/v2_codegen.tmp.main.o' struct Builder { pref &pref.Preferences mut: - files []ast.File - user_files []string // original user-provided files (for output name) - file_set &token.FileSet = token.FileSet.new() - env &types.Environment = unsafe { nil } // Type checker environment - parsed_full_files_n int - parsed_vh_files_n int - entry_v_lines_n int - parsed_v_lines_n int - parsed_full_files []string - parsed_vh_files []string - used_fn_keys map[string]bool - cached_called_fn_names map[string]bool - used_vh_for_parse bool - used_import_vh_for_parse bool - used_virtual_vh_for_parse bool - flat_roundtrip_enabled bool // V2_FLAT_ROUNDTRIP=1: route parses through streaming + to_files() - flat_check_enabled bool // V2_CHECK_FLAT=1: route type-check through Checker.check_flat - markused_flat_enabled bool // V2_MARKUSED_FLAT=1: route markused through mark_used_flat shim - flat_ssa_enabled bool // V2_FLAT_SSA=1: route the (sequential) native SSA build through build_all_from_flat on the post-transform b.flat. Requires V2_CHECK_FLAT + V2_MARKUSED_FLAT so b.flat is post-transform-populated. Default off. + files []ast.File + user_files []string // original user-provided files (for output name) + file_set &token.FileSet = token.FileSet.new() + env &types.Environment = unsafe { nil } // Type checker environment + parsed_full_files_n int + parsed_vh_files_n int + entry_v_lines_n int + parsed_v_lines_n int + parsed_full_files []string + parsed_vh_files []string + used_fn_keys map[string]bool + cached_called_fn_names map[string]bool + v2compiler_generic_setup_snapshot cleanc.GenericSetupSnapshot + has_v2compiler_generic_setup_snapshot bool + used_vh_for_parse bool + used_import_vh_for_parse bool + used_virtual_vh_for_parse bool + flat_roundtrip_enabled bool // V2_FLAT_ROUNDTRIP=1: route parses through streaming + to_files() + flat_check_enabled bool // V2_CHECK_FLAT=1: route type-check through Checker.check_flat + markused_flat_enabled bool // V2_MARKUSED_FLAT=1: route markused through mark_used_flat shim + flat_ssa_enabled bool // V2_FLAT_SSA=1: route the (sequential) native SSA build through build_all_from_flat on the post-transform b.flat. Requires V2_CHECK_FLAT + V2_MARKUSED_FLAT so b.flat is post-transform-populated. Default off. // flat caches the FlatAst representation of b.files. When // flat_check_enabled is set, parse_batch streams directly into // flat_builder so b.flat is built incrementally during parsing rather @@ -840,9 +842,17 @@ fn (mut b Builder) gen_cleanc_source_with_options(modules []string, emit_files [ if cache_bundle_name.len == 0 && cached_init_calls.len > 0 && b.cached_called_fn_names.len > 0 { gen.add_called_fn_names(b.cached_called_fn_name_list()) } + if cache_bundle_name.len == 0 && cached_init_calls.len > 0 + && b.has_v2compiler_generic_setup_snapshot { + gen.use_generic_setup_snapshot(b.v2compiler_generic_setup_snapshot) + } use_parallel := b.pref != unsafe { nil } && !b.pref.no_parallel if use_parallel { gen.gen_passes_1_to_4() + if cache_bundle_name == v2compiler_cache_name { + b.v2compiler_generic_setup_snapshot = gen.generic_setup_snapshot() + b.has_v2compiler_generic_setup_snapshot = true + } b.gen_cleanc_parallel(mut gen) source := gen.gen_finalize() if cache_bundle_name.len > 0 { @@ -854,6 +864,10 @@ fn (mut b Builder) gen_cleanc_source_with_options(modules []string, emit_files [ return source } source := gen.gen() + if cache_bundle_name == v2compiler_cache_name { + b.v2compiler_generic_setup_snapshot = gen.generic_setup_snapshot() + b.has_v2compiler_generic_setup_snapshot = true + } if cache_bundle_name.len > 0 { b.add_cached_called_fn_names(gen.external_called_fn_names()) } diff --git a/vlib/v2/builder/gen_cleanc_parallel.v b/vlib/v2/builder/gen_cleanc_parallel.v index eb1028eee..8120d5c5d 100644 --- a/vlib/v2/builder/gen_cleanc_parallel.v +++ b/vlib/v2/builder/gen_cleanc_parallel.v @@ -7,6 +7,8 @@ import runtime import time import v2.gen.cleanc +const max_cleanc_pass5_jobs = 4 + struct GenCleancWeightedFile { idx int cost int @@ -60,11 +62,8 @@ fn (mut b Builder) gen_cleanc_parallel(mut gen cleanc.Gen) { stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, 'pass 5 pre') n_files := emit_indices.len - // Pass 5 workers clone sizeable lookup maps and accumulate C output before - // merging. Keep the default fan-out conservative until those maps can be - // shared without data races; otherwise large projects can hit multi-GB RSS. n_runtime_jobs := runtime.nr_jobs() - n_jobs := if n_runtime_jobs > 2 { 2 } else { n_runtime_jobs } + n_jobs := cleanc_parallel_pass5_job_count(n_runtime_jobs, n_files) $if windows { gen.gen_pass5_files(emit_indices) @@ -163,3 +162,17 @@ fn (mut b Builder) gen_cleanc_parallel(mut gen cleanc.Gen) { _ = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, 'pass 5 post') } } + +fn cleanc_parallel_pass5_job_count(n_runtime_jobs int, n_files int) int { + if n_runtime_jobs <= 0 || n_files <= 0 { + return 0 + } + mut n_jobs := n_runtime_jobs + if n_jobs > max_cleanc_pass5_jobs { + n_jobs = max_cleanc_pass5_jobs + } + if n_jobs > n_files { + n_jobs = n_files + } + return n_jobs +} diff --git a/vlib/v2/builder/gen_cleanc_parallel_test.v b/vlib/v2/builder/gen_cleanc_parallel_test.v new file mode 100644 index 000000000..2f3797d7f --- /dev/null +++ b/vlib/v2/builder/gen_cleanc_parallel_test.v @@ -0,0 +1,9 @@ +module builder + +fn test_cleanc_parallel_pass5_job_count_caps_runtime_jobs() { + assert cleanc_parallel_pass5_job_count(128, 1000) == max_cleanc_pass5_jobs + assert cleanc_parallel_pass5_job_count(64, 3) == 3 + assert cleanc_parallel_pass5_job_count(2, 1000) == 2 + assert cleanc_parallel_pass5_job_count(4, 0) == 0 + assert cleanc_parallel_pass5_job_count(0, 4) == 0 +} diff --git a/vlib/v2/gen/arm64/arm64.v b/vlib/v2/gen/arm64/arm64.v index 38b6bf432..a26e423c5 100644 --- a/vlib/v2/gen/arm64/arm64.v +++ b/vlib/v2/gen/arm64/arm64.v @@ -6150,7 +6150,7 @@ fn (g &Gen) call_targets_current_function(callee_id int) bool { return callee.name != '' && callee.name == g.cur_func_name } -fn (mut g Gen) load_struct_src_address_to_reg(reg int, val_id int, expected_struct_typ ssa.TypeID, call_callee_id int) { +fn (mut g Gen) load_struct_src_address_to_reg(reg int, val_id int, expected_struct_typ ssa.TypeID, _call_callee_id int) { if val_id <= 0 || val_id >= g.mod.values.len { g.emit_mov_imm64(reg, 0) return diff --git a/vlib/v2/gen/cleanc/cleanc.v b/vlib/v2/gen/cleanc/cleanc.v index 48adcff7d..140be41b4 100644 --- a/vlib/v2/gen/cleanc/cleanc.v +++ b/vlib/v2/gen/cleanc/cleanc.v @@ -177,12 +177,14 @@ mut: // Multi-instantiation support: maps base struct C name (e.g. "json2__Node") to // a list of (suffix, bindings) pairs for each distinct concrete instantiation. // E.g. [("json2__ValueInfo", {T: ValueInfo}), ("json2__StructFieldInfo", {T: StructFieldInfo})] - generic_struct_instances map[string][]GenericStructInstance - c_file_fn_keys map[string]bool // fn_key -> emitted from a .c.v file, so plain .v fallback should be skipped - c_struct_types map[string]bool // C struct names declared with `struct C.Name` - typedef_c_types map[string]bool // C struct names with @[typedef] attribute (emit without 'struct' prefix) - blocked_fn_keys map[string]bool // worker-only fn keys reserved to other pass5 chunks - cached_vhash string // cached git short hash for @VHASH/@VCURRENTHASH + generic_struct_instances map[string][]GenericStructInstance + generic_setup_snapshot GenericSetupSnapshot + has_generic_setup_snapshot bool + c_file_fn_keys map[string]bool // fn_key -> emitted from a .c.v file, so plain .v fallback should be skipped + c_struct_types map[string]bool // C struct names declared with `struct C.Name` + typedef_c_types map[string]bool // C struct names with @[typedef] attribute (emit without 'struct' prefix) + blocked_fn_keys map[string]bool // worker-only fn keys reserved to other pass5 chunks + cached_vhash string // cached git short hash for @VHASH/@VCURRENTHASH } struct GenericStructInstance { @@ -191,6 +193,23 @@ struct GenericStructInstance { c_name string // full C struct name, e.g. "json2__Node_T_json2__StructFieldInfo" } +pub struct GenericSetupSnapshot { +mut: + ready bool + tuple_aliases map[string][]string + array_aliases map[string]bool + map_aliases map[string]bool + result_aliases map[string]bool + option_aliases map[string]bool + collected_fixed_array_types map[string]FixedArrayInfo + collected_map_types map[string]MapTypeInfo + generic_body_scan_cache map[string]bool + generic_spec_index map[string][]string + late_generic_specs map[string][]map[string]types.Type + generic_struct_bindings map[string]map[string]types.Type + generic_struct_instances map[string][]GenericStructInstance +} + struct LiveFnInfo { c_name string // C function name (e.g., 'main__frame', 'Game__update_model') ret_type string // C return type (e.g., 'void', 'string') @@ -895,6 +914,9 @@ pub fn (mut g Gen) gen_passes_1_to_4() { g.build_generic_fn_decl_index() stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'setup.generic_fn_decl_index') + if g.has_generic_setup_snapshot { + g.apply_generic_setup_snapshot() + } g.collect_generic_struct_bindings() stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'setup.generic_struct_bindings') diff --git a/vlib/v2/gen/cleanc/generic_struct_scan_test.v b/vlib/v2/gen/cleanc/generic_struct_scan_test.v new file mode 100644 index 000000000..363c3ee32 --- /dev/null +++ b/vlib/v2/gen/cleanc/generic_struct_scan_test.v @@ -0,0 +1,9 @@ +module cleanc + +fn test_generic_struct_scan_job_count_caps_runtime_jobs() { + assert generic_struct_scan_job_count(128, 1000) == max_generic_struct_scan_jobs + assert generic_struct_scan_job_count(64, 15) == 3 + assert generic_struct_scan_job_count(2, 1000) == 2 + assert generic_struct_scan_job_count(4, 0) == 0 + assert generic_struct_scan_job_count(0, 4) == 0 +} diff --git a/vlib/v2/gen/cleanc/struct.v b/vlib/v2/gen/cleanc/struct.v index 2abcc8393..da97e484c 100644 --- a/vlib/v2/gen/cleanc/struct.v +++ b/vlib/v2/gen/cleanc/struct.v @@ -4,9 +4,36 @@ module cleanc +import runtime import v2.ast import v2.types +const max_generic_struct_scan_jobs = 4 + +$if !windows { + struct GenericStructScanChunkArgs { + worker voidptr // &Gen - pre-cloned worker + file_indices_ptr voidptr // &[]int - worker-owned full-file indexes + } + + @[typedef] + struct C.pthread_t {} + + fn C.pthread_create(thread &C.pthread_t, attr voidptr, start_routine fn (voidptr) voidptr, arg voidptr) int + fn C.pthread_join(thread C.pthread_t, retval voidptr) int + fn C.pthread_attr_init(attr voidptr) int + fn C.pthread_attr_setstacksize(attr voidptr, stacksize usize) int + fn C.pthread_attr_destroy(attr voidptr) int + + fn generic_struct_scan_chunk_thread(arg voidptr) voidptr { + a := unsafe { &GenericStructScanChunkArgs(arg) } + mut w := unsafe { &Gen(a.worker) } + indices := unsafe { &[]int(a.file_indices_ptr) } + w.collect_generic_struct_bindings_file_indices(*indices) + return unsafe { nil } + } +} + fn (mut g Gen) has_explicit_str_method_for_c_type(c_type_name string) bool { old_file := g.cur_file_name old_module := g.cur_module @@ -64,35 +91,59 @@ fn (mut g Gen) collect_generic_struct_bindings() { g.cur_fn_name = prev_fn_name g.cur_fn_c_name = prev_fn_c_name } - for file in g.files { - g.set_file_module(file) - for stmt in file.stmts { - if stmt is ast.StructDecl { - for field in stmt.fields { - g.scan_expr_for_generic_types(field.typ) - } + if !g.collect_generic_struct_bindings_file_scan_parallel() { + g.collect_generic_struct_bindings_file_scan(g.files) + } + g.propagate_generic_struct_bindings_for_files(g.files) + g.scan_receiver_generic_struct_bindings_for_files(g.files) +} + +fn (mut g Gen) collect_generic_struct_bindings_file_scan(files []ast.File) { + for file in files { + g.collect_generic_struct_bindings_file(file) + } +} + +fn (mut g Gen) collect_generic_struct_bindings_file_indices(file_indices []int) { + for file_idx in file_indices { + if file_idx < 0 || file_idx >= g.files.len { + continue + } + g.collect_generic_struct_bindings_file(g.files[file_idx]) + } +} + +fn (mut g Gen) collect_generic_struct_bindings_file(file ast.File) { + g.set_file_module(file) + for stmt in file.stmts { + if stmt is ast.StructDecl { + for field in stmt.fields { + g.scan_expr_for_generic_types(field.typ) } - // Also scan function bodies for generic type references - // (e.g. LinkedList[StructFieldInfo]{} in decode_value) - if stmt is ast.FnDecl { - if stmt.receiver.typ !is ast.EmptyExpr { - g.scan_expr_for_generic_types(stmt.receiver.typ) - } - for param in stmt.typ.params { - g.scan_expr_for_generic_types(param.typ) - } - if stmt.typ.return_type !is ast.EmptyExpr { - g.scan_expr_for_generic_types(stmt.typ.return_type) - } - g.scan_fn_body_for_generic_types_with_clean_locals(stmt, '') + } + // Also scan function bodies for generic type references + // (e.g. LinkedList[StructFieldInfo]{} in decode_value) + if stmt is ast.FnDecl { + if stmt.receiver.typ !is ast.EmptyExpr { + g.scan_expr_for_generic_types(stmt.receiver.typ) + } + for param in stmt.typ.params { + g.scan_expr_for_generic_types(param.typ) } + if stmt.typ.return_type !is ast.EmptyExpr { + g.scan_expr_for_generic_types(stmt.typ.return_type) + } + g.scan_fn_body_for_generic_types_with_clean_locals(stmt, '') } } +} + +fn (mut g Gen) propagate_generic_struct_bindings_for_files(files []ast.File) { // Propagation pass: for each generic struct with recorded bindings, // look for nested generic type references (e.g. Node[T] inside // LinkedList[T]) and record concrete bindings by substituting the // parent struct's known bindings for placeholder params. - for file in g.files { + for file in files { g.set_file_module(file) for stmt in file.stmts { if stmt is ast.StructDecl { @@ -113,11 +164,14 @@ fn (mut g Gen) collect_generic_struct_bindings() { } } } +} + +fn (mut g Gen) scan_receiver_generic_struct_bindings_for_files(files []ast.File) { // Generic receiver methods can contain explicit calls like // `helper[T](...)` even when the receiver itself is emitted with v2's // fallback placeholder type. Rescan those bodies with concrete receiver // bindings, or with the same f64 fallback used by placeholder C types. - for file in g.files { + for file in files { g.set_file_module(file) for stmt in file.stmts { if stmt is ast.FnDecl { @@ -150,6 +204,185 @@ fn (mut g Gen) collect_generic_struct_bindings() { } } +fn (mut g Gen) collect_generic_struct_bindings_file_scan_parallel() bool { + if g.pref == unsafe { nil } || g.pref.no_parallel || g.files.len < 2 { + return false + } + $if windows { + return false + } $else { + n_files := g.files.len + n_runtime_jobs := runtime.nr_jobs() + n_jobs := generic_struct_scan_job_count(n_runtime_jobs, n_files) + if n_jobs <= 1 { + return false + } + + mut thread_ids := []C.pthread_t{len: n_jobs} + mut args := []GenericStructScanChunkArgs{cap: n_jobs} + mut workers := []voidptr{cap: n_jobs} + mut chunk_indices := [][]int{cap: n_jobs} + for ci := 0; ci < n_jobs; ci++ { + start := ci * n_files / n_jobs + end := (ci + 1) * n_files / n_jobs + mut indices := []int{cap: end - start} + for file_idx := start; file_idx < end; file_idx++ { + indices << file_idx + } + chunk_indices << indices + w := g.new_generic_struct_scan_worker(ci) + workers << voidptr(w) + } + for ci := 0; ci < n_jobs; ci++ { + args << GenericStructScanChunkArgs{ + worker: workers[ci] + file_indices_ptr: unsafe { voidptr(&chunk_indices[ci]) } + } + } + + attr_buf := [64]u8{} + attr := unsafe { voidptr(&attr_buf[0]) } + C.pthread_attr_init(attr) + C.pthread_attr_setstacksize(attr, 64 * 1024 * 1024) + for ci := 0; ci < n_jobs; ci++ { + C.pthread_create(unsafe { &thread_ids[ci] }, attr, generic_struct_scan_chunk_thread, + unsafe { voidptr(&args[ci]) }) + } + C.pthread_attr_destroy(attr) + for ci := 0; ci < n_jobs; ci++ { + C.pthread_join(thread_ids[ci], unsafe { nil }) + } + for ci := 0; ci < n_jobs; ci++ { + w := unsafe { &Gen(workers[ci]) } + g.merge_generic_struct_scan_worker(w) + } + return true + } +} + +fn generic_struct_scan_job_count(n_runtime_jobs int, n_files int) int { + if n_runtime_jobs <= 0 || n_files <= 0 { + return 0 + } + mut n_jobs := n_runtime_jobs + if n_jobs > max_generic_struct_scan_jobs { + n_jobs = max_generic_struct_scan_jobs + } + if n_jobs > n_files { + n_jobs = n_files + } + // Worker Gen clones are sizeable; avoid spending more time cloning than scanning + // when the current bundle is already small because .vh caches did most work. + if n_files < n_jobs * 4 { + n_jobs = if n_files >= 4 { n_files / 4 } else { 1 } + } + return n_jobs +} + +fn (g &Gen) new_generic_struct_scan_worker(worker_id int) &Gen { + mut w := g.new_pass5_worker([], worker_id) + w.generic_body_scan_cache = g.generic_body_scan_cache.clone() + w.generic_spec_index = clone_generic_spec_index(g.generic_spec_index) + w.late_generic_specs = clone_late_generic_specs(g.late_generic_specs) + w.generic_struct_bindings = clone_generic_struct_bindings(g.generic_struct_bindings) + w.generic_struct_instances = clone_generic_struct_instances(g.generic_struct_instances) + w.active_generic_types = map[string]types.Type{} + w.runtime_local_types = map[string]string{} + w.runtime_decl_types = map[string]string{} + w.not_local_var_cache = map[string]bool{} + w.is_module_ident_cache = map[string]bool{} + w.resolved_module_names = map[string]string{} + w.cur_fn_generic_params = map[string]string{} + w.cur_fn_name = '' + w.cur_fn_c_name = '' + return w +} + +fn (mut g Gen) merge_generic_struct_scan_worker(w &Gen) { + for struct_c_name, instances in w.generic_struct_instances { + for inst in instances { + g.merge_generic_struct_scan_instance(struct_c_name, inst) + } + } + for key, bindings in w.generic_struct_bindings { + if key !in g.generic_struct_bindings && key !in g.generic_struct_instances { + g.generic_struct_bindings[key] = bindings.clone() + } + } + for key, specs in w.late_generic_specs { + for bindings in specs { + g.record_late_generic_call_spec_unchecked(key, bindings) + } + } + for key, scanned in w.generic_body_scan_cache { + if scanned { + g.generic_body_scan_cache[key] = true + } + } + g.merge_generic_setup_alias_maps(w) +} + +fn (mut g Gen) merge_generic_struct_scan_instance(struct_c_name string, inst GenericStructInstance) { + mut instances := g.generic_struct_instances[struct_c_name] + for existing in instances { + if existing.params_key == inst.params_key { + return + } + } + mut c_name := inst.c_name + if instances.len > 0 && c_name == struct_c_name { + c_name = '${struct_c_name}_T_${inst.params_key}' + } + merged := GenericStructInstance{ + params_key: inst.params_key + bindings: inst.bindings.clone() + c_name: c_name + } + instances << merged + g.generic_struct_instances[struct_c_name] = instances + if struct_c_name !in g.generic_struct_bindings { + g.generic_struct_bindings[struct_c_name] = merged.bindings.clone() + } +} + +fn (mut g Gen) merge_generic_setup_alias_maps(w &Gen) { + for key, values in w.tuple_aliases { + if key !in g.tuple_aliases { + g.tuple_aliases[key] = values.clone() + } + } + for key, value in w.array_aliases { + if value { + g.array_aliases[key] = true + } + } + for key, value in w.map_aliases { + if value { + g.map_aliases[key] = true + } + } + for key, value in w.result_aliases { + if value { + g.result_aliases[key] = true + } + } + for key, value in w.option_aliases { + if value { + g.option_aliases[key] = true + } + } + for key, value in w.collected_fixed_array_types { + if key !in g.collected_fixed_array_types { + g.collected_fixed_array_types[key] = value + } + } + for key, value in w.collected_map_types { + if key !in g.collected_map_types { + g.collected_map_types[key] = value + } + } +} + fn (mut g Gen) scan_fn_body_for_generic_types_with_clean_locals(node ast.FnDecl, spec_name string) { prev_runtime_local_types := g.runtime_local_types.clone() prev_runtime_decl_types := g.runtime_decl_types.clone() @@ -178,6 +411,115 @@ fn (mut g Gen) scan_fn_body_for_generic_types_with_clean_locals(node ast.FnDecl, g.cur_fn_c_name = prev_fn_c_name } +fn clone_tuple_aliases(src map[string][]string) map[string][]string { + mut out := map[string][]string{} + for key, values in src { + out[key] = values.clone() + } + return out +} + +fn clone_generic_spec_index(src map[string][]string) map[string][]string { + mut out := map[string][]string{} + for key, values in src { + out[key] = values.clone() + } + return out +} + +fn clone_late_generic_specs(src map[string][]map[string]types.Type) map[string][]map[string]types.Type { + mut out := map[string][]map[string]types.Type{} + for key, specs in src { + mut cloned_specs := []map[string]types.Type{cap: specs.len} + for spec in specs { + cloned_specs << spec.clone() + } + out[key] = cloned_specs + } + return out +} + +fn clone_generic_struct_bindings(src map[string]map[string]types.Type) map[string]map[string]types.Type { + mut out := map[string]map[string]types.Type{} + for key, bindings in src { + out[key] = bindings.clone() + } + return out +} + +fn clone_generic_struct_instances(src map[string][]GenericStructInstance) map[string][]GenericStructInstance { + mut out := map[string][]GenericStructInstance{} + for key, instances in src { + mut cloned_instances := []GenericStructInstance{cap: instances.len} + for inst in instances { + cloned_instances << GenericStructInstance{ + params_key: inst.params_key + bindings: inst.bindings.clone() + c_name: inst.c_name + } + } + out[key] = cloned_instances + } + return out +} + +fn (s &GenericSetupSnapshot) clone() GenericSetupSnapshot { + return GenericSetupSnapshot{ + ready: s.ready + tuple_aliases: clone_tuple_aliases(s.tuple_aliases) + array_aliases: s.array_aliases.clone() + map_aliases: s.map_aliases.clone() + result_aliases: s.result_aliases.clone() + option_aliases: s.option_aliases.clone() + collected_fixed_array_types: s.collected_fixed_array_types.clone() + collected_map_types: s.collected_map_types.clone() + generic_body_scan_cache: s.generic_body_scan_cache.clone() + generic_spec_index: clone_generic_spec_index(s.generic_spec_index) + late_generic_specs: clone_late_generic_specs(s.late_generic_specs) + generic_struct_bindings: clone_generic_struct_bindings(s.generic_struct_bindings) + generic_struct_instances: clone_generic_struct_instances(s.generic_struct_instances) + } +} + +pub fn (mut g Gen) use_generic_setup_snapshot(snapshot GenericSetupSnapshot) { + g.generic_setup_snapshot = snapshot.clone() + g.has_generic_setup_snapshot = snapshot.ready +} + +pub fn (g &Gen) generic_setup_snapshot() GenericSetupSnapshot { + return GenericSetupSnapshot{ + ready: true + tuple_aliases: clone_tuple_aliases(g.tuple_aliases) + array_aliases: g.array_aliases.clone() + map_aliases: g.map_aliases.clone() + result_aliases: g.result_aliases.clone() + option_aliases: g.option_aliases.clone() + collected_fixed_array_types: g.collected_fixed_array_types.clone() + collected_map_types: g.collected_map_types.clone() + generic_body_scan_cache: g.generic_body_scan_cache.clone() + generic_spec_index: clone_generic_spec_index(g.generic_spec_index) + late_generic_specs: clone_late_generic_specs(g.late_generic_specs) + generic_struct_bindings: clone_generic_struct_bindings(g.generic_struct_bindings) + generic_struct_instances: clone_generic_struct_instances(g.generic_struct_instances) + } +} + +fn (mut g Gen) apply_generic_setup_snapshot() { + snapshot := g.generic_setup_snapshot + g.tuple_aliases = clone_tuple_aliases(snapshot.tuple_aliases) + g.array_aliases = snapshot.array_aliases.clone() + g.map_aliases = snapshot.map_aliases.clone() + g.result_aliases = snapshot.result_aliases.clone() + g.option_aliases = snapshot.option_aliases.clone() + g.collected_fixed_array_types = snapshot.collected_fixed_array_types.clone() + g.collected_map_types = snapshot.collected_map_types.clone() + g.generic_body_scan_cache = snapshot.generic_body_scan_cache.clone() + g.generic_spec_index = clone_generic_spec_index(snapshot.generic_spec_index) + g.late_generic_specs = clone_late_generic_specs(snapshot.late_generic_specs) + g.generic_struct_bindings = clone_generic_struct_bindings(snapshot.generic_struct_bindings) + g.generic_struct_instances = clone_generic_struct_instances(snapshot.generic_struct_instances) +} + // propagate_generic_bindings finds nested generic type references and records // concrete bindings by substituting placeholder params from parent bindings. fn (mut g Gen) propagate_generic_bindings(e ast.Expr, parent_bindings map[string]types.Type) { diff --git a/vlib/v2/gen/cleanc/types.v b/vlib/v2/gen/cleanc/types.v index b2832fd20..b5f91fbf9 100644 --- a/vlib/v2/gen/cleanc/types.v +++ b/vlib/v2/gen/cleanc/types.v @@ -4575,14 +4575,14 @@ fn (mut g Gen) record_generic_struct_bindings(struct_base_name string, struct_c_ if env_struct.generic_params.len == 0 { env_struct = g.lookup_struct_type(struct_base_name) } - generic_param_names := runtime_generic_param_names(env_struct.generic_params) - if generic_param_names.len == 0 || generic_param_names.len != filtered_params.len { + runtime_param_names := runtime_generic_param_names(env_struct.generic_params) + if runtime_param_names.len == 0 || runtime_param_names.len != filtered_params.len { return } // Check that all concrete params are non-placeholder types. mut bindings := map[string]types.Type{} mut param_c_names := []string{cap: filtered_params.len} - for i, param_name in generic_param_names { + for i, param_name in runtime_param_names { concrete_expr := filtered_params[i] if is_generic_placeholder_type_name(concrete_expr.name()) { return @@ -4593,7 +4593,7 @@ fn (mut g Gen) record_generic_struct_bindings(struct_base_name string, struct_c_ bindings[param_name] = concrete_type } } - if bindings.len != generic_param_names.len { + if bindings.len != runtime_param_names.len { return } if !g.generic_specialization_belongs_to_emit_modules(bindings) { @@ -4646,13 +4646,13 @@ fn (mut g Gen) record_generic_struct_bindings_with_parent(struct_base_name strin if env_struct.generic_params.len == 0 { env_struct = g.lookup_struct_type(struct_base_name) } - generic_param_names := runtime_generic_param_names(env_struct.generic_params) - if generic_param_names.len == 0 || generic_param_names.len != filtered_params.len { + runtime_param_names := runtime_generic_param_names(env_struct.generic_params) + if runtime_param_names.len == 0 || runtime_param_names.len != filtered_params.len { return } mut bindings := map[string]types.Type{} mut param_c_names := []string{cap: filtered_params.len} - for i, param_name in generic_param_names { + for i, param_name in runtime_param_names { concrete_expr := filtered_params[i] expr_name := concrete_expr.name() if is_generic_placeholder_type_name(expr_name) { @@ -4675,7 +4675,7 @@ fn (mut g Gen) record_generic_struct_bindings_with_parent(struct_base_name strin bindings[param_name] = concrete_type } } - if bindings.len != generic_param_names.len { + if bindings.len != runtime_param_names.len { return } if !g.generic_specialization_belongs_to_emit_modules(bindings) { @@ -6343,10 +6343,10 @@ fn (mut g Gen) resolve_generic_struct_field_name(expr ast.Expr) string { // Build the params_key from active_generic_types // Find the struct's generic params to know the order env_struct := g.lookup_struct_type(c_name.all_after_last('__')) - generic_param_names := runtime_generic_param_names(env_struct.generic_params) - if generic_param_names.len > 0 { - mut param_c_names := []string{cap: generic_param_names.len} - for param_name in generic_param_names { + runtime_param_names := runtime_generic_param_names(env_struct.generic_params) + if runtime_param_names.len > 0 { + mut param_c_names := []string{cap: runtime_param_names.len} + for param_name in runtime_param_names { if concrete := g.active_generic_types[param_name] { param_c_names << mangle_alias_component(g.types_type_to_c(concrete)) } else { diff --git a/vlib/v2/ssa/optimize/dce.v b/vlib/v2/ssa/optimize/dce.v index 6de7e8ace..a568833f5 100644 --- a/vlib/v2/ssa/optimize/dce.v +++ b/vlib/v2/ssa/optimize/dce.v @@ -152,7 +152,8 @@ fn find_dead_stores(m &ssa.Module, func &ssa.Function) map[int]bool { // Mark stores to non-escaping allocas as dead for alloca_id, store_ids in alloca_stores { if !alloca_escapes[alloca_id] { - for store_id in store_ids { + for i := 0; i < store_ids.len; i++ { + store_id := store_ids[i] dead_stores[store_id] = true } } diff --git a/vlib/v2/ssa/optimize/fold.v b/vlib/v2/ssa/optimize/fold.v index 27a7ced55..2108bc36d 100644 --- a/vlib/v2/ssa/optimize/fold.v +++ b/vlib/v2/ssa/optimize/fold.v @@ -219,7 +219,7 @@ fn branch_fold(mut m ssa.Module) bool { // Algebraic simplifications: x+0=x, x*1=x, x*0=0, x-x=0, x^x=0, x&x=x, x|x=x, x*2=x<<1, etc. // Returns (replacement_id, needs_zero) - if needs_zero is true, caller should create zero constant -fn try_algebraic_simplify(m &ssa.Module, val_id int, instr ssa.Instruction, lhs ssa.Value, rhs ssa.Value) (int, bool) { +fn try_algebraic_simplify(_m &ssa.Module, val_id int, instr ssa.Instruction, lhs ssa.Value, rhs ssa.Value) (int, bool) { lhs_id := instr.operands[0] rhs_id := instr.operands[1] diff --git a/vlib/v2/ssa/optimize/mem2reg.v b/vlib/v2/ssa/optimize/mem2reg.v index 286f87cc9..8ffeead81 100644 --- a/vlib/v2/ssa/optimize/mem2reg.v +++ b/vlib/v2/ssa/optimize/mem2reg.v @@ -402,7 +402,7 @@ mut: processed bool // whether steps 1-3 have been run for this block } -fn rename_iterative(mut m ssa.Module, root_blk int, mut ctx Mem2RegCtx, promotable []int, mut stack_counts []int, dom &DomInfo, cfg &CfgData) (int, int) { +fn rename_iterative(mut m ssa.Module, root_blk int, mut ctx Mem2RegCtx, _promotable []int, mut _stack_counts []int, dom &DomInfo, cfg &CfgData) (int, int) { // Pre-build block_val_ids[] by scanning values for basic_block kind. // This avoids m.blocks[blk_id].val_id which produces wrong results // in ARM64-compiled binaries (large struct field access bug). diff --git a/vlib/v2/ssa/optimize/phi.v b/vlib/v2/ssa/optimize/phi.v index 685c41a2e..f7d9b828b 100644 --- a/vlib/v2/ssa/optimize/phi.v +++ b/vlib/v2/ssa/optimize/phi.v @@ -107,7 +107,7 @@ fn remove_phi_use(mut m ssa.Module, val_id int, user_id int) { // get_block_val_id returns the value ID for a block, using a pre-built lookup table. // This avoids m.blocks[bid].val_id which can return wrong values for the large // BasicBlock struct (168 bytes) in ARM64-compiled binaries. -fn get_block_val_id(m &ssa.Module, bid int, block_val_ids []int) int { +fn get_block_val_id(_m &ssa.Module, bid int, block_val_ids []int) int { if bid >= 0 && bid < block_val_ids.len { return block_val_ids[bid] } diff --git a/vlib/v2/transformer/monomorphize.v b/vlib/v2/transformer/monomorphize.v index 4d700b490..ed925836f 100644 --- a/vlib/v2/transformer/monomorphize.v +++ b/vlib/v2/transformer/monomorphize.v @@ -1007,7 +1007,10 @@ pub fn (mut t Transformer) monomorphize_pass(files []ast.File) []ast.File { } t.cur_generic_call_file_idx = old_owner_file t.cur_import_aliases = old_import_aliases.clone() - mut bucket := per_file_clones[clone_file] or { []ast.Stmt{} } + mut bucket := []ast.Stmt{} + if clone_file in per_file_clones { + bucket = per_file_clones[clone_file] + } bucket << ast.Stmt(cloned) per_file_clones[clone_file] = bucket } @@ -1023,10 +1026,11 @@ pub fn (mut t Transformer) monomorphize_pass(files []ast.File) []ast.File { } mut new_files := []ast.File{cap: files.len} for fi, file in files { - extra := per_file_clones[fi] or { + if fi !in per_file_clones { new_files << file continue } + extra := per_file_clones[fi] mut stmts := file.stmts.clone() stmts << extra new_files << ast.File{ @@ -2033,7 +2037,10 @@ fn (mut t Transformer) collect_generic_call_specs_in_new_clones(files []ast.File old_import_aliases := t.cur_import_aliases.clone() t.build_generic_fn_decl_index(files) for fi, file in files { - clones := t.last_mono_clones[fi] or { continue } + if fi !in t.last_mono_clones { + continue + } + clones := t.last_mono_clones[fi] if clones.len == 0 { continue } diff --git a/vlib/v2/transformer/transformer.v b/vlib/v2/transformer/transformer.v index 559677c0e..bc118c009 100644 --- a/vlib/v2/transformer/transformer.v +++ b/vlib/v2/transformer/transformer.v @@ -3442,16 +3442,26 @@ fn (t &Transformer) order_runtime_const_inits(inits []RuntimeConstInit) []Runtim continue } if dep_idx := index_by_name[dep_name] { - mut dep := dependents[dep_idx] or { []int{} } + mut dep := []int{} + if dep_idx in dependents { + dep = dependents[dep_idx] + } dep << i dependents[dep_idx] = dep - indegree[i] = (indegree[i] or { 0 }) + 1 + mut deg := 0 + if i in indegree { + deg = indegree[i] + } + indegree[i] = deg + 1 } } } mut ready := []int{} for i := 0; i < inits.len; i++ { - deg := indegree[i] or { 0 } + mut deg := 0 + if i in indegree { + deg = indegree[i] + } if deg == 0 { ready << i } @@ -3478,10 +3488,17 @@ fn (t &Transformer) order_runtime_const_inits(inits []RuntimeConstInit) []Runtim cur := ready[best_pos] ready.delete(best_pos) ordered_idx << cur - deps := dependents[cur] or { []int{} } + mut deps := []int{} + if cur in dependents { + deps = dependents[cur] + } for dep in deps { - indegree[dep] = (indegree[dep] or { 0 }) - 1 - deg := indegree[dep] or { 0 } + mut deg := 0 + if dep in indegree { + deg = indegree[dep] + } + deg-- + indegree[dep] = deg if deg == 0 { ready << dep } -- 2.39.5