From 4bcc4247c00d3d96ca1b746cc6561d7f9c76de09 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Mon, 16 Mar 2026 01:10:15 +0300 Subject: [PATCH] v2: fix self host with paralell + regalloc; symlink without sudo --- cmd/tools/vsymlink/vsymlink_nix.c.v | 30 ++++++- cmd/v2/test_all.sh | 109 ++++++++++------------ cmd/v2/test_v2_self.sh | 14 ++- vlib/v/help/installation/symlink.txt | 4 +- vlib/v2/builder/gen_arm64_parallel.v | 47 ++++++---- vlib/v2/builder/ssa_build_parallel.v | 47 +++++++--- vlib/v2/builder/transform_parallel.v | 17 +++- vlib/v2/gen/arm64/arm64.v | 119 +++++++++++++++++-------- vlib/v2/gen/arm64/linker.v | 94 ++++++++++++++----- vlib/v2/gen/cleanc/assign.v | 3 +- vlib/v2/gen/cleanc/fn.v | 6 ++ vlib/v2/gen/cleanc/types.v | 4 +- vlib/v2/pref/pref.v | 4 +- vlib/v2/ssa/builder.v | 14 +-- vlib/v2/ssa/module.v | 8 +- vlib/v2/transformer/expr.v | 55 ++++++++++-- vlib/v2/transformer/fn.v | 15 ++-- vlib/v2/transformer/struct.v | 3 +- vlib/v2/transformer/transformer.v | 4 +- vlib/v2/transformer/transformer_test.v | 4 +- vlib/v2/transformer/types.v | 11 ++- vlib/v2/types/checker.v | 2 - 22 files changed, 418 insertions(+), 196 deletions(-) diff --git a/cmd/tools/vsymlink/vsymlink_nix.c.v b/cmd/tools/vsymlink/vsymlink_nix.c.v index b648e9740..5d954bbb6 100644 --- a/cmd/tools/vsymlink/vsymlink_nix.c.v +++ b/cmd/tools/vsymlink/vsymlink_nix.c.v @@ -11,7 +11,33 @@ fn setup_symlink() { } os.rm(link_path) or {} os.symlink(vexe, link_path) or { - eprintln('Failed to create symlink "${link_path}". Try again with sudo.') - exit(1) + // Try ~/.local/bin as a fallback when /usr/local/bin is not writable. + home := os.home_dir() + if home == '' { + eprintln('Failed to create symlink "${link_path}": ${err}') + eprintln('Try again with sudo.') + exit(1) + } + local_bin := os.join_path(home, '.local', 'bin') + if !os.exists(local_bin) { + os.mkdir_all(local_bin) or { + eprintln('Failed to create symlink "${link_path}": ${err}') + eprintln('Try again with sudo.') + exit(1) + } + } + link_path = os.join_path(local_bin, 'v') + os.rm(link_path) or {} + os.symlink(vexe, link_path) or { + eprintln('Failed to create symlink "${link_path}": ${err}') + eprintln('Try again with sudo.') + exit(1) + } + eprintln('Note: Symlink created in "${local_bin}" instead of "/usr/local/bin".') + if path := os.getenv_opt('PATH') { + if !path.contains(local_bin) { + eprintln('Make sure "${local_bin}" is in your PATH.') + } + } } } diff --git a/cmd/v2/test_all.sh b/cmd/v2/test_all.sh index 1105acad7..66ad0a74e 100755 --- a/cmd/v2/test_all.sh +++ b/cmd/v2/test_all.sh @@ -3,83 +3,73 @@ set -euo pipefail cd "$(dirname "$0")" -# Files that V1 may clobber during rebuild — backup and restore around v builds. -# Use unique suffixes to avoid name collisions (e.g. two types.v files). +# V1's formatter may clobber v2 source files during rebuild. +# Back up the entire v2 tree and restore after each V1 build. +V2_SRC="../../vlib/v2" +V2_BAK="/tmp/v2_src_bak_test_all" + backup_v2_src() { - cp ../../vlib/v2/gen/cleanc/consts_and_globals.v /tmp/bak_ta_cleanc_consts.v - cp ../../vlib/v2/gen/cleanc/assign.v /tmp/bak_ta_cleanc_assign.v - cp ../../vlib/v2/ssa/module.v /tmp/bak_ta_ssa_module.v - cp ../../vlib/v2/ssa/optimize/mem2reg.v /tmp/bak_ta_ssa_mem2reg.v - cp ../../vlib/v2/transformer/struct.v /tmp/bak_ta_tr_struct.v - cp ../../vlib/v2/transformer/transformer.v /tmp/bak_ta_tr_transformer.v - cp ../../vlib/v2/transformer/types.v /tmp/bak_ta_tr_types.v + rm -rf "$V2_BAK" + cp -R "$V2_SRC" "$V2_BAK" } restore_v2_src() { - cp /tmp/bak_ta_cleanc_consts.v ../../vlib/v2/gen/cleanc/consts_and_globals.v - cp /tmp/bak_ta_cleanc_assign.v ../../vlib/v2/gen/cleanc/assign.v - cp /tmp/bak_ta_ssa_module.v ../../vlib/v2/ssa/module.v - cp /tmp/bak_ta_ssa_mem2reg.v ../../vlib/v2/ssa/optimize/mem2reg.v - cp /tmp/bak_ta_tr_struct.v ../../vlib/v2/transformer/struct.v - cp /tmp/bak_ta_tr_transformer.v ../../vlib/v2/transformer/transformer.v - cp /tmp/bak_ta_tr_types.v ../../vlib/v2/transformer/types.v + rsync -a --delete "$V2_BAK/" "$V2_SRC/" } KNOWN_FAILURES=0 -echo "=== 1/13: ARM64 self-host hello world ===" -if v -o v2 v2.v && ./v2 -backend arm64 -nocache -o v3 v2.v && ./v3 -o hello_arm hello.v && ./hello_arm; then - echo "[PASS]" -else - echo "[KNOWN FAILURE] ARM64 self-host — skipping" - KNOWN_FAILURES=$((KNOWN_FAILURES + 1)) -fi +echo "=== 1/14: ARM64 self-host hello world ===" +backup_v2_src +v -skip-unused -cc cc -o v2 v2.v +restore_v2_src +./v2 -backend arm64 -nocache -o v3 v2.v && ./v3 -o hello_arm hello.v && ./hello_arm echo "" -echo "=== 2/13: Self-host test ===" -if bash test_v2_self.sh; then - echo "[PASS]" +echo "=== 2/14: ARM64 self-host chain (v2->v3->v4->v5, parallel) ===" +echo " Building v3 from v2..." +./v2 -nocache -backend arm64 -o v3_chain v2.v +echo " Building v4 from v3..." +./v3_chain -nocache -gc none -backend arm64 -o v4_chain v2.v +echo " Building v5 from v4..." +./v4_chain -nocache -gc none -backend arm64 -o v5_chain v2.v +V4_SIZE=$(wc -c < v4_chain) +V5_SIZE=$(wc -c < v5_chain) +if [ "$V4_SIZE" -eq "$V5_SIZE" ]; then + echo " v4=v5 ($V4_SIZE bytes) — chain converged" else - echo "[KNOWN FAILURE] Self-host test — skipping" - KNOWN_FAILURES=$((KNOWN_FAILURES + 1)) + echo " FAIL: v4 ($V4_SIZE) != v5 ($V5_SIZE)" + exit 1 fi +rm -f v3_chain v4_chain v5_chain + +echo "" +echo "=== 3/14: Self-host test ===" +bash test_v2_self.sh echo "" -echo "=== 3/13: Rebuild v2 and run builtin test files ===" +echo "=== 4/14: Builtin test files (cleanc) ===" rm -rf /tmp/v2_cleanc_obj_cache -backup_v2_src -v self && v -skip-unused -cc cc -o v2 v2.v -restore_v2_src ./v2 ../../vlib/builtin/array_test.v ./v2 ../../vlib/builtin/string_test.v ./v2 ../../vlib/builtin/map_test.v echo "" -echo "=== 4/13: Builtin test files (arm64) ===" -if ./v2 -backend arm64 ../../vlib/builtin/array_test.v \ - && ./v2 -backend arm64 ../../vlib/builtin/string_test.v \ - && ./v2 -backend arm64 ../../vlib/builtin/map_test.v; then - echo "[PASS]" -else - echo "[KNOWN FAILURE] ARM64 builtin tests — skipping" - KNOWN_FAILURES=$((KNOWN_FAILURES + 1)) -fi +echo "=== 5/14: Builtin test files (arm64) ===" +./v2 -backend arm64 ../../vlib/builtin/array_test.v +./v2 -backend arm64 ../../vlib/builtin/string_test.v +./v2 -backend arm64 ../../vlib/builtin/map_test.v echo "" -echo "=== 5/13: Math test ===" +echo "=== 6/14: Math test ===" ./v2 ../../vlib/math/math_test.v echo "" -echo "=== 6/13: Math test (arm64) ===" -if ./v2 -backend arm64 ../../vlib/math/math_test.v; then - echo "[PASS]" -else - echo "[KNOWN FAILURE] ARM64 math test — skipping" - KNOWN_FAILURES=$((KNOWN_FAILURES + 1)) -fi +echo "=== 7/14: Math test (arm64) ===" +./v2 -backend arm64 ../../vlib/math/math_test.v echo "" -echo "=== 7/13: Sumtype tests ===" +echo "=== 8/14: Sumtype tests ===" ./v2 test_sumtype.v ./v2 test_sumtype2.v ./v2 test_sumtype3.v @@ -92,7 +82,7 @@ echo "=== 7/13: Sumtype tests ===" ./v2 test_sumtype_global.v echo "" -echo "=== 8/13: Sumtype tests (arm64) ===" +echo "=== 9/14: Sumtype tests (arm64) ===" ./v2 -backend arm64 test_sumtype.v ./v2 -backend arm64 test_sumtype2.v ./v2 -backend arm64 test_sumtype3.v @@ -105,28 +95,23 @@ echo "=== 8/13: Sumtype tests (arm64) ===" ./v2 -backend arm64 test_sumtype_global.v echo "" -echo "=== 9/13: SSA backends test (arm64) ===" +echo "=== 10/14: SSA backends test (arm64) ===" v -gc none run test_ssa_backends.v arm64 echo "" -echo "=== 10/13: SSA backends test (cleanc) ===" +echo "=== 11/14: SSA backends test (cleanc) ===" v -gc none run test_ssa_backends.v cleanc echo "" -echo "=== 11/13: Transformer unit tests ===" -if v ../../vlib/v2/transformer/transformer_test.v; then - echo "[PASS]" -else - echo "[KNOWN FAILURE] Transformer unit tests — skipping" - KNOWN_FAILURES=$((KNOWN_FAILURES + 1)) -fi +echo "=== 12/14: Transformer unit tests ===" +v ../../vlib/v2/transformer/transformer_test.v echo "" -echo "=== 12/13: Transformer integration test ===" +echo "=== 13/14: Transformer integration test ===" v ../../vlib/v2/transformer/transformer_v2_darwin_test.v echo "" -echo "=== 13/13: Cleanc runtime tests ===" +echo "=== 14/14: Cleanc runtime tests ===" v -gc none run ../../vlib/v2/gen/cleanc/tests/run_tests.v echo "" diff --git a/cmd/v2/test_v2_self.sh b/cmd/v2/test_v2_self.sh index a8a26aaf0..f783cba48 100755 --- a/cmd/v2/test_v2_self.sh +++ b/cmd/v2/test_v2_self.sh @@ -23,9 +23,21 @@ if [[ ! -x "${v1_compiler}" ]]; then exit 1 fi +# V1's formatter may clobber v2 source files — backup and restore. +v2_src="${repo_root}/vlib/v2" +v2_bak="/tmp/v2_src_bak_self_test" +rm -rf "${v2_bak}" +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}" -gc none -o "${v2_bin}" "${v2_source}" +"${v1_compiler}" -skip-unused -cc cc -o "${v2_bin}" "${v2_source}" + +# Restore v2 sources after V1 build. +rsync -a --delete "${v2_bak}/" "${v2_src}/" + +# Use clang instead of TCC for v2-compiled C code. +export V2CC="${V2CC:-cc}" # Use v2 to compile itself to v3 (using defined backend). "${v2_bin}" -gc none -o "${v3_bin}" -backend "${backend}" "${v2_source}" diff --git a/vlib/v/help/installation/symlink.txt b/vlib/v/help/installation/symlink.txt index af28b39a0..9fff5e55d 100644 --- a/vlib/v/help/installation/symlink.txt +++ b/vlib/v/help/installation/symlink.txt @@ -3,7 +3,7 @@ This command adds a symlink for the V compiler executable. Usage: v symlink [OPTIONS] -Note that on Unix systems this command requires write permissions to -/usr/local/bin to work. +On Unix systems this command creates a symlink in /usr/local/bin. If that +is not writable, it falls back to ~/.local/bin without requiring sudo. For GitHub Actions, the option -githubci needs to be specified. \ No newline at end of file diff --git a/vlib/v2/builder/gen_arm64_parallel.v b/vlib/v2/builder/gen_arm64_parallel.v index a2e597698..dd821aa09 100644 --- a/vlib/v2/builder/gen_arm64_parallel.v +++ b/vlib/v2/builder/gen_arm64_parallel.v @@ -5,27 +5,27 @@ module builder import runtime import v2.gen.arm64 +import v2.mir struct GenARM64ChunkArgs { - gen voidptr // &arm64.Gen (main gen, used as template for cloning) + worker voidptr // &arm64.Gen — pre-cloned worker (created on main thread) + mod_ptr voidptr // &mir.Module — shared MIR module start_idx int end_idx int - worker voidptr // &voidptr — output slot for worker Gen pointer } fn C.pthread_create(thread voidptr, attr voidptr, start_routine fn (voidptr) voidptr, arg voidptr) int - fn C.pthread_join(thread voidptr, 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 gen_arm64_chunk_thread(arg voidptr) voidptr { a := unsafe { &GenARM64ChunkArgs(arg) } - g := unsafe { &arm64.Gen(a.gen) } - mut w := g.new_worker_clone() + mut w := unsafe { &arm64.Gen(a.worker) } + m := unsafe { &mir.Module(a.mod_ptr) } for fi := a.start_idx; fi < a.end_idx; fi++ { - w.gen_func(g.mod.funcs[fi]) - } - unsafe { - *(&voidptr(a.worker)) = voidptr(w) + w.gen_func(m.funcs[fi]) } return unsafe { nil } } @@ -45,27 +45,42 @@ fn (mut b Builder) gen_arm64_parallel(mut gen arm64.Gen) { return } - // Split functions into chunks and spawn workers via pthreads + // Split functions into chunks chunk_size := (n_funcs + n_jobs - 1) / n_jobs - mut worker_ptrs := []voidptr{len: n_jobs, init: unsafe { nil }} mut thread_ids := []voidptr{len: n_jobs, init: unsafe { nil }} mut args := []GenARM64ChunkArgs{cap: n_jobs} + + // Pre-create all workers on the main thread to avoid concurrent .clone() races. + // Each worker gets its own deep copy of maps/arrays. + mut workers := []voidptr{cap: n_jobs} + mut chunk_idx := 0 mut i := 0 for i < n_funcs { end := if i + chunk_size < n_funcs { i + chunk_size } else { n_funcs } + w := gen.new_worker_clone() + workers << voidptr(w) args << GenARM64ChunkArgs{ - gen: unsafe { voidptr(&gen) } + worker: voidptr(w) + mod_ptr: unsafe { voidptr(gen.mod) } start_idx: i end_idx: end - worker: unsafe { voidptr(&worker_ptrs[chunk_idx]) } } - C.pthread_create(unsafe { voidptr(&thread_ids[chunk_idx]) }, unsafe { nil }, - gen_arm64_chunk_thread, unsafe { voidptr(&args[chunk_idx]) }) i = end chunk_idx++ } + 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 < chunk_idx; ci++ { + C.pthread_create(unsafe { voidptr(&thread_ids[ci]) }, attr, gen_arm64_chunk_thread, + unsafe { voidptr(&args[ci]) }) + } + C.pthread_attr_destroy(attr) + // Wait for all workers for ci := 0; ci < chunk_idx; ci++ { C.pthread_join(thread_ids[ci], unsafe { nil }) @@ -73,7 +88,7 @@ fn (mut b Builder) gen_arm64_parallel(mut gen arm64.Gen) { // Merge worker results in order for ci := 0; ci < chunk_idx; ci++ { - w := unsafe { &arm64.Gen(worker_ptrs[ci]) } + w := unsafe { &arm64.Gen(workers[ci]) } gen.merge_worker(w) } diff --git a/vlib/v2/builder/ssa_build_parallel.v b/vlib/v2/builder/ssa_build_parallel.v index 827ae0f85..b6f1f5f8c 100644 --- a/vlib/v2/builder/ssa_build_parallel.v +++ b/vlib/v2/builder/ssa_build_parallel.v @@ -15,16 +15,18 @@ struct FnDeclRef { } struct SSABuildChunkArgs { - worker voidptr // &ssa.Builder (pre-created worker builder) - files voidptr // &[]ast.File - fn_refs voidptr // &[]FnDeclRef + worker voidptr // &ssa.Builder (pre-created worker builder) + files voidptr // &[]ast.File + fn_refs voidptr // &[]FnDeclRef start_idx int end_idx int } fn C.pthread_create(thread voidptr, attr voidptr, start_routine fn (voidptr) voidptr, arg voidptr) int - fn C.pthread_join(thread voidptr, 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 ssa_build_chunk_thread(arg voidptr) voidptr { a := unsafe { &SSABuildChunkArgs(arg) } @@ -32,11 +34,16 @@ fn ssa_build_chunk_thread(arg voidptr) voidptr { files := unsafe { &[]ast.File(a.files) } fn_refs := unsafe { &[]FnDeclRef(a.fn_refs) } - // Build assigned functions + // Build assigned functions. + // Avoid chained array access like files[i].stmts[j] — in ARM64-compiled + // binaries, chained indexing returns copies with potentially corrupted fields. + // Instead, copy to a local first, then access fields. for fi := a.start_idx; fi < a.end_idx; fi++ { ref := unsafe { fn_refs[fi] } worker_b.cur_module = ref.mod_name - decl := unsafe { files[ref.file_idx].stmts[ref.stmt_idx] } as ast.FnDecl + file := unsafe { (*files)[ref.file_idx] } + stmt := file.stmts[ref.stmt_idx] + decl := stmt as ast.FnDecl worker_b.build_fn(decl) } return unsafe { nil } @@ -46,14 +53,18 @@ fn (mut b Builder) ssa_build_parallel(mut ssa_builder ssa.Builder, files []ast.F n_jobs := runtime.nr_jobs() mut mod := ssa_builder.mod - // Collect all function declarations that need building + // Collect all function declarations that need building. + // Avoid chained array access like files[fi].stmts[si] — copy to locals first. has_markused := ssa_builder.used_fn_keys.len > 0 mut fn_refs := []FnDeclRef{cap: 4096} - for fi, file in files { + for fi in 0 .. files.len { + file := files[fi] mod_name := ssa.file_module_name(file) - for si in 0 .. file.stmts.len { - if file.stmts[si] is ast.FnDecl { - decl := unsafe { files[fi].stmts[si] } as ast.FnDecl + nstmts := file.stmts.len + for si in 0 .. nstmts { + stmt := file.stmts[si] + if stmt is ast.FnDecl { + decl := stmt as ast.FnDecl if decl.language == .c && decl.stmts.len == 0 { continue } @@ -109,6 +120,12 @@ fn (mut b Builder) ssa_build_parallel(mut ssa_builder ssa.Builder, files []ast.F // Spawn worker threads mut thread_ids := []voidptr{len: actual_chunks, init: unsafe { nil }} mut args := []SSABuildChunkArgs{cap: actual_chunks} + + attr_buf := [64]u8{} + attr := unsafe { voidptr(&attr_buf[0]) } + C.pthread_attr_init(attr) + C.pthread_attr_setstacksize(attr, 64 * 1024 * 1024) + mut chunk_idx := 0 i = 0 for i < n_fns { @@ -120,11 +137,12 @@ fn (mut b Builder) ssa_build_parallel(mut ssa_builder ssa.Builder, files []ast.F start_idx: i end_idx: end } - C.pthread_create(unsafe { voidptr(&thread_ids[chunk_idx]) }, unsafe { nil }, - ssa_build_chunk_thread, unsafe { voidptr(&args[chunk_idx]) }) + C.pthread_create(unsafe { voidptr(&thread_ids[chunk_idx]) }, attr, ssa_build_chunk_thread, + unsafe { voidptr(&args[chunk_idx]) }) i = end chunk_idx++ } + C.pthread_attr_destroy(attr) // Wait for all workers for ci := 0; ci < chunk_idx; ci++ { @@ -147,6 +165,7 @@ fn (mut b Builder) ssa_build_parallel(mut ssa_builder ssa.Builder, files []ast.F } } } - mod.merge_worker_module(w_mod, func_data, seed_values, seed_instrs, seed_blocks, seed_types) + mod.merge_worker_module(w_mod, func_data, seed_values, seed_instrs, seed_blocks, + seed_types) } } diff --git a/vlib/v2/builder/transform_parallel.v b/vlib/v2/builder/transform_parallel.v index b89e78e98..96a95f5e1 100644 --- a/vlib/v2/builder/transform_parallel.v +++ b/vlib/v2/builder/transform_parallel.v @@ -17,6 +17,9 @@ struct TransformChunkArgs { fn C.pthread_create(thread voidptr, attr voidptr, start_routine fn (voidptr) voidptr, arg voidptr) int fn C.pthread_join(thread voidptr, 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 transform_chunk_thread(arg voidptr) voidptr { a := unsafe { &TransformChunkArgs(arg) } @@ -55,6 +58,15 @@ fn (mut b Builder) transform_files_parallel(mut trans transformer.Transformer) [ mut worker_ptrs := []voidptr{len: n_jobs, init: unsafe { nil }} mut thread_ids := []voidptr{len: n_jobs, init: unsafe { nil }} mut args := []TransformChunkArgs{cap: n_jobs} + + // ARM64-compiled code uses much more stack per function (one slot per SSA + // value, no reuse). Increase worker thread stack size to 64 MB so deeply + // recursive transform functions don't overflow the default 512 KB stack. + attr_buf := [64]u8{} + attr := unsafe { voidptr(&attr_buf[0]) } + C.pthread_attr_init(attr) + C.pthread_attr_setstacksize(attr, 64 * 1024 * 1024) + mut chunk_idx := 0 mut i := 0 for i < n_files { @@ -67,11 +79,12 @@ fn (mut b Builder) transform_files_parallel(mut trans transformer.Transformer) [ worker_ptr: unsafe { voidptr(&worker_ptrs[chunk_idx]) } worker_idx: chunk_idx } - C.pthread_create(unsafe { voidptr(&thread_ids[chunk_idx]) }, unsafe { nil }, - transform_chunk_thread, unsafe { voidptr(&args[chunk_idx]) }) + C.pthread_create(unsafe { voidptr(&thread_ids[chunk_idx]) }, attr, transform_chunk_thread, + unsafe { voidptr(&args[chunk_idx]) }) i = end chunk_idx++ } + C.pthread_attr_destroy(attr) // Wait for all workers for ci := 0; ci < chunk_idx; ci++ { diff --git a/vlib/v2/gen/arm64/arm64.v b/vlib/v2/gen/arm64/arm64.v index 313e338bc..ec28ae5d0 100644 --- a/vlib/v2/gen/arm64/arm64.v +++ b/vlib/v2/gen/arm64/arm64.v @@ -29,8 +29,8 @@ pub mut: total_resolved int // Register allocation - reg_map map[int]int - used_regs []int + reg_map map[int]int + used_regs []int next_blk int cur_blk_id int // current block being generated (for phi copy emission) @@ -318,16 +318,19 @@ pub fn (mut g Gen) pre_populate_type_caches() { } pub fn (g &Gen) new_worker_clone() &Gen { + // Clone all maps and arrays to avoid COW data races between threads. + // V's map/array assignment shares internal data; concurrent reads can + // trigger internal rehashing/COW writes that race with other threads. return &Gen{ mod: g.mod macho: MachOObject.new() - func_by_name: g.func_by_name - global_by_name: g.global_by_name - val_to_block: g.val_to_block - type_size_cache: g.type_size_cache - type_align_cache: g.type_align_cache - type_size_stack: g.type_size_stack - type_align_stack: g.type_align_stack + func_by_name: g.func_by_name.clone() + global_by_name: g.global_by_name.clone() + val_to_block: g.val_to_block.clone() + type_size_cache: g.type_size_cache.clone() + type_align_cache: g.type_align_cache.clone() + type_size_stack: g.type_size_stack.clone() + type_align_stack: g.type_align_stack.clone() env_dump_funcrefs: g.env_dump_funcrefs env_trace_skip_dead: g.env_trace_skip_dead env_dump_stackmap: g.env_dump_stackmap @@ -4816,7 +4819,10 @@ fn (g &Gen) scalar_value_is_pointer_payload(val_id int, depth int) bool { fn (mut g Gen) get_dest_reg(val_id int) int { if val_id in g.reg_map { - return g.reg_map[val_id] + r := g.reg_map[val_id] + if r != 0xFF { + return r + } } return 8 } @@ -4824,7 +4830,10 @@ fn (mut g Gen) get_dest_reg(val_id int) int { fn (mut g Gen) get_operand_reg(val_id int, fallback int) int { // If value is in a register, return it if val_id in g.reg_map { - return g.reg_map[val_id] + r := g.reg_map[val_id] + if r != 0xFF { + return r + } } // Otherwise load it into fallback g.load_val_to_reg(fallback, val_id) @@ -5400,10 +5409,12 @@ fn (mut g Gen) get_const_int(val_id int) i64 { fn (mut g Gen) load_val_to_reg(reg int, val_id int) { if val_id in g.reg_map { r := g.reg_map[val_id] - if r != reg { - g.emit_mov_reg(reg, r) + if r != 0xFF { + if r != reg { + g.emit_mov_reg(reg, r) + } + return } - return } if val_id <= 0 || val_id >= g.mod.values.len { g.emit_mov_imm64(reg, 0) @@ -5682,10 +5693,12 @@ fn (mut g Gen) store_reg_to_val(reg int, val_id int) { && (g.env_trace_storeval == '*' || g.cur_func_name == g.env_trace_storeval) if val_id in g.reg_map { reg_idx := g.reg_map[val_id] - if reg_idx != reg { - g.emit_mov_reg(reg_idx, reg) + if reg_idx != 0xFF { + if reg_idx != reg { + g.emit_mov_reg(reg_idx, reg) + } + stored_reg = reg_idx } - stored_reg = reg_idx } if offset := g.stack_map[val_id] { if val_id > 0 && val_id < g.mod.values.len { @@ -7712,8 +7725,7 @@ fn (mut g Gen) allocate_registers(func mir.Function) { // Skip instructions that build results directly on the stack. // These ops write to the stack slot without going through a register, // so register-allocating them leaves the register uninitialized. - if instr.op in [.struct_init, .insertvalue, .inline_string_init, - .call_sret] { + if instr.op in [.struct_init, .insertvalue, .inline_string_init, .call_sret] { skip_interval = true } if instr.op in [.call, .call_indirect, .call_sret] { @@ -7826,13 +7838,42 @@ fn (mut g Gen) allocate_registers(func mir.Function) { } } - mut sorted := []&Interval{cap: intervals.len} - for _, i in intervals { - sorted << i + // Flatten intervals into parallel arrays to avoid pointer-deref and map-access + // patterns that break in ARM64-compiled binaries (chained-access bug). + mut iv_val_ids := []int{cap: intervals.len} + mut iv_starts := []int{cap: intervals.len} + mut iv_ends := []int{cap: intervals.len} + mut iv_has_call := []bool{cap: intervals.len} + for vid, iv in intervals { + iv_val_ids << vid + iv_starts << iv.start + iv_ends << iv.end + iv_has_call << iv.has_call } - sorted.sort(a.start < b.start) - mut active := []&Interval{cap: 32} + // Sort by start time using insertion sort (avoids closure-based sort issues) + for si in 1 .. iv_val_ids.len { + key_vid := iv_val_ids[si] + key_start := iv_starts[si] + key_end := iv_ends[si] + key_call := iv_has_call[si] + mut sj := si - 1 + for sj >= 0 && iv_starts[sj] > key_start { + iv_val_ids[sj + 1] = iv_val_ids[sj] + iv_starts[sj + 1] = iv_starts[sj] + iv_ends[sj + 1] = iv_ends[sj] + iv_has_call[sj + 1] = iv_has_call[sj] + sj-- + } + iv_val_ids[sj + 1] = key_vid + iv_starts[sj + 1] = key_start + iv_ends[sj + 1] = key_end + iv_has_call[sj + 1] = key_call + } + + // Active list as parallel arrays (end time, assigned register) + mut act_ends := []int{cap: 32} + mut act_regs := []int{cap: 32} // Registers // Caller-saved (Temporaries): x9..x15 @@ -7847,12 +7888,18 @@ fn (mut g Gen) allocate_registers(func mir.Function) { mut used := []bool{len: 32, init: false} mut used_regs_set := []bool{len: 32, init: false} - for i in sorted { + for si2 in 0 .. iv_val_ids.len { + cur_vid := iv_val_ids[si2] + cur_start := iv_starts[si2] + cur_end := iv_ends[si2] + cur_has_call := iv_has_call[si2] + // Remove expired intervals from active list mut j := 0 - for j < active.len { - if active[j].end < i.start { - active.delete(j) + for j < act_ends.len { + if act_ends[j] < cur_start { + act_ends.delete(j) + act_regs.delete(j) } else { j++ } @@ -7862,17 +7909,18 @@ fn (mut g Gen) allocate_registers(func mir.Function) { for k in 0 .. 32 { used[k] = false } - for a in active { - used[g.reg_map[a.val_id]] = true + for ar in act_regs { + used[ar] = true } - // Decide which pool to use (avoid clone) - pool := if i.has_call { long_regs } else { short_regs } + // Decide which pool to use + pool := if cur_has_call { long_regs } else { short_regs } for r in pool { if !used[r] { - g.reg_map[i.val_id] = r - active << i + g.reg_map[cur_vid] = r + act_ends << cur_end + act_regs << r // Only track used callee-saved regs for prologue saving if r >= 19 && r <= 28 && !used_regs_set[r] { used_regs_set[r] = true @@ -7882,9 +7930,10 @@ fn (mut g Gen) allocate_registers(func mir.Function) { } } } + g.used_regs.sort() if trace_ra { - eprintln('REGALLOC fn=${func.name} intervals=${sorted.len} calls=${call_indices.len} allocated=${g.reg_map.len} used_regs=${g.used_regs} total_instrs=${total_instrs}') + eprintln('REGALLOC fn=${func.name} intervals=${iv_val_ids.len} calls=${call_indices.len} allocated=${g.reg_map.len} used_regs=${g.used_regs} total_instrs=${total_instrs}') for val_id, reg in g.reg_map { if mut iv := intervals[val_id] { eprintln(' val=${val_id} -> x${reg} [${iv.start},${iv.end}] has_call=${iv.has_call}') diff --git a/vlib/v2/gen/arm64/linker.v b/vlib/v2/gen/arm64/linker.v index 5f3b2bbf6..fbd86aa77 100644 --- a/vlib/v2/gen/arm64/linker.v +++ b/vlib/v2/gen/arm64/linker.v @@ -81,7 +81,8 @@ const force_external_syms = ['_malloc', '_free', '_calloc', '_realloc', '_exit', '_rand', '_srand', '_isdigit', '_isspace', '_tolower', '_toupper', '_setenv', '_unsetenv', '_sysconf', '_uname', '_gethostname', '_pthread_mutex_init', '_pthread_mutex_lock', '_pthread_mutex_unlock', '_pthread_mutex_destroy', '_pthread_self', '_pthread_create', - '_pthread_join', '_arc4random_buf', + '_pthread_join', '_pthread_attr_init', '_pthread_attr_setstacksize', '_pthread_attr_destroy', + '_arc4random_buf', '_proc_pidpath', '_backtrace', '_backtrace_symbols_fd', // macOS specific '_dispatch_semaphore_create', '_dispatch_semaphore_signal', @@ -313,7 +314,6 @@ pub fn (mut l Linker) link(output_path string, entry_name string) { text_content_end := l.stubs_offset + l.stubs_size l.text_size = (text_content_end + page_size - 1) & ~(page_size - 1) - // Data segment follows text l.data_fileoff = l.text_size l.data_vmaddr = base_addr + u64(l.text_size) @@ -1261,22 +1261,70 @@ fn (mut l Linker) write_zeros(n int) { // uses fixed-size arrays and operates on raw pointers. const sha256_k = [ - u32(0x428a2f98), 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, - 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, - 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, - 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, - 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, - 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, - 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, - 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, - 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + u32(0x428a2f98), + 0x71374491, + 0xb5c0fbcf, + 0xe9b5dba5, + 0x3956c25b, + 0x59f111f1, + 0x923f82a4, + 0xab1c5ed5, + 0xd807aa98, + 0x12835b01, + 0x243185be, + 0x550c7dc3, + 0x72be5d74, + 0x80deb1fe, + 0x9bdc06a7, + 0xc19bf174, + 0xe49b69c1, + 0xefbe4786, + 0x0fc19dc6, + 0x240ca1cc, + 0x2de92c6f, + 0x4a7484aa, + 0x5cb0a9dc, + 0x76f988da, + 0x983e5152, + 0xa831c66d, + 0xb00327c8, + 0xbf597fc7, + 0xc6e00bf3, + 0xd5a79147, + 0x06ca6351, + 0x14292967, + 0x27b70a85, + 0x2e1b2138, + 0x4d2c6dfc, + 0x53380d13, + 0x650a7354, + 0x766a0abb, + 0x81c2c92e, + 0x92722c85, + 0xa2bfe8a1, + 0xa81a664b, + 0xc24b8b70, + 0xc76c51a3, + 0xd192e819, + 0xd6990624, + 0xf40e3585, + 0x106aa070, + 0x19a4c116, + 0x1e376c08, + 0x2748774c, + 0x34b0bcb5, + 0x391c0cb3, + 0x4ed8aa4a, + 0x5b9cca4f, + 0x682e6ff3, + 0x748f82ee, + 0x78a5636f, + 0x84c87814, + 0x8cc70208, + 0x90befffa, + 0xa4506ceb, + 0xbef9a3f7, + 0xc67178f2, ]! @[inline] @@ -1305,8 +1353,8 @@ fn sha256_hash_pages(data &u8, hashes &u8, page_start int, page_end int, code_li // No heap allocations — uses fixed-size arrays on the stack. @[direct_array_access] fn sha256_hash(data &u8, data_len int, out_ptr &u8) { - mut state := [u32(0x6A09E667), 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, - 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19]! + mut state := [u32(0x6A09E667), 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, + 0x1F83D9AB, 0x5BE0CD19]! mut w := [64]u32{} // Process complete 64-byte blocks directly from input @@ -1316,7 +1364,8 @@ fn sha256_hash(data &u8, data_len int, out_ptr &u8) { for i in 0 .. 16 { j := off + i * 4 unsafe { - w[i] = (u32(data[j]) << 24) | (u32(data[j + 1]) << 16) | (u32(data[j + 2]) << 8) | u32(data[j + 3]) + w[i] = (u32(data[j]) << 24) | (u32(data[j + 1]) << 16) | (u32(data[j + 2]) << 8) | u32(data[ + j + 3]) } } sha256_compress(&state[0], &w[0]) @@ -1354,7 +1403,8 @@ fn sha256_hash(data &u8, data_len int, out_ptr &u8) { off := blk * 64 for i in 0 .. 16 { j := off + i * 4 - w[i] = (u32(pad[j]) << 24) | (u32(pad[j + 1]) << 16) | (u32(pad[j + 2]) << 8) | u32(pad[j + 3]) + w[i] = (u32(pad[j]) << 24) | (u32(pad[j + 1]) << 16) | (u32(pad[j + 2]) << 8) | u32(pad[ + j + 3]) } sha256_compress(&state[0], &w[0]) } diff --git a/vlib/v2/gen/cleanc/assign.v b/vlib/v2/gen/cleanc/assign.v index 3938e6799..922815474 100644 --- a/vlib/v2/gen/cleanc/assign.v +++ b/vlib/v2/gen/cleanc/assign.v @@ -605,7 +605,8 @@ fn (mut g Gen) gen_assign_stmt(node ast.AssignStmt) { return } } - if lhs_fixed_type.starts_with('Array_fixed_') && lhs !is ast.IndexExpr && rhs is ast.CallExpr { + if lhs_fixed_type.starts_with('Array_fixed_') && lhs !is ast.IndexExpr + && rhs is ast.CallExpr { if call_ret := g.get_call_return_type(rhs.lhs, rhs.args.len) { if call_ret == lhs_fixed_type { wrapper_type := g.c_fn_return_type_from_v(lhs_fixed_type) diff --git a/vlib/v2/gen/cleanc/fn.v b/vlib/v2/gen/cleanc/fn.v index 18de66d68..f690311ca 100644 --- a/vlib/v2/gen/cleanc/fn.v +++ b/vlib/v2/gen/cleanc/fn.v @@ -591,6 +591,12 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl) { if node.name == 'main' { g.write_indent() g.sb.writeln('return 0;') + } else if g.cur_fn_ret_type.starts_with('_result_') { + g.write_indent() + g.sb.writeln('return (${g.cur_fn_ret_type}){0};') + } else if g.cur_fn_ret_type.starts_with('_option_') { + g.write_indent() + g.sb.writeln('return (${g.cur_fn_ret_type}){0};') } g.indent-- diff --git a/vlib/v2/gen/cleanc/types.v b/vlib/v2/gen/cleanc/types.v index e670b2960..8a52f922f 100644 --- a/vlib/v2/gen/cleanc/types.v +++ b/vlib/v2/gen/cleanc/types.v @@ -1452,8 +1452,8 @@ fn (mut g Gen) get_expr_type(node ast.Expr) string { } else if t == 'bool' && node is ast.BasicLiteral && node.kind == .number { // Numeric literals mistyped as bool by env (e.g. `1 in map` context). return 'int' - } else if node is ast.IndexExpr && node.expr !is ast.RangeExpr - && t.starts_with('Array_') && !t.starts_with('Array_fixed_') { + } else if node is ast.IndexExpr && node.expr !is ast.RangeExpr && t.starts_with('Array_') + && !t.starts_with('Array_fixed_') { // Env may return the container type instead of the element type for IndexExpr. // Cross-check against raw type of the LHS container: if the LHS is an Array // whose element type is known, prefer that. diff --git a/vlib/v2/pref/pref.v b/vlib/v2/pref/pref.v index cd60fcc76..ebd10510f 100644 --- a/vlib/v2/pref/pref.v +++ b/vlib/v2/pref/pref.v @@ -244,8 +244,8 @@ pub fn new_preferences_from_args(args []string) Preferences { known_boolean_flags := ['--debug', '--verbose', '-v', '--skip-genv', '--skip-builtin', '--skip-imports', '--skip-type-check', '--no-parallel', '-nocache', '--nocache', '-nomarkused', '--nomarkused', '-showcc', '--showcc', '-stats', '--stats', - '-print-parsed-files', '--print-parsed-files', '-keepc', '--profile-alloc', - '-profile-alloc', '-enable-globals', '--enable-globals', '-shared', '--shared', '-O0'] + '-print-parsed-files', '--print-parsed-files', '-keepc', '--profile-alloc', '-profile-alloc', + '-enable-globals', '--enable-globals', '-shared', '--shared', '-O0'] for opt in options { if opt !in known_flags_with_values && opt !in known_boolean_flags { eprintln('error: unknown flag `${opt}`') diff --git a/vlib/v2/ssa/builder.v b/vlib/v2/ssa/builder.v index a5adc9a20..51603f0d2 100644 --- a/vlib/v2/ssa/builder.v +++ b/vlib/v2/ssa/builder.v @@ -26,9 +26,9 @@ pub mut: // When set, only build functions whose decl key is in this map (dead code elimination). used_fn_keys map[string]bool mut: - env &types.Environment = unsafe { nil } - cur_func int = -1 - cur_block BlockID = -1 + env &types.Environment = unsafe { nil } + cur_func int = -1 + cur_block BlockID = -1 // Variable name -> SSA ValueID (alloca pointer) vars map[string]ValueID // Loop break/continue targets @@ -128,10 +128,10 @@ pub fn (mut b Builder) new_worker_clone(worker_mod &Module) &Builder { option_wrapper_types: b.option_wrapper_types.clone() result_wrapper_types: b.result_wrapper_types.clone() // Per-function state is reset at start of each build_fn, so empty init is fine - fn_refs: map[string]ValueID{} - vars: map[string]ValueID{} - loop_stack: []LoopInfo{} - label_blocks: map[string]BlockID{} + fn_refs: map[string]ValueID{} + vars: map[string]ValueID{} + loop_stack: []LoopInfo{} + label_blocks: map[string]BlockID{} mut_ptr_params: map[string]bool{} } } diff --git a/vlib/v2/ssa/module.v b/vlib/v2/ssa/module.v index 81bd53778..886f8b198 100644 --- a/vlib/v2/ssa/module.v +++ b/vlib/v2/ssa/module.v @@ -413,7 +413,7 @@ pub fn (mut m Module) new_worker_module() &Module { // FuncSSAData holds the SSA data produced by a worker for a single function. pub struct FuncSSAData { pub: - func_idx int // Index into main module's funcs[] + func_idx int // Index into main module's funcs[] blocks []BlockID // Worker-local block IDs params []ValueID // Worker-local param ValueIDs } @@ -577,7 +577,11 @@ pub fn (mut m Module) merge_worker_module(w &Module, func_data []FuncSSAData, se } m.instrs << Instruction{ op: instr.op - block: if instr.block >= seed_blocks { instr.block + block_off } else { instr.block } + block: if instr.block >= seed_blocks { + instr.block + block_off + } else { + instr.block + } typ: new_typ operands: new_ops pos: instr.pos diff --git a/vlib/v2/transformer/expr.v b/vlib/v2/transformer/expr.v index c95dac171..ab69bfeda 100644 --- a/vlib/v2/transformer/expr.v +++ b/vlib/v2/transformer/expr.v @@ -1927,22 +1927,61 @@ fn (mut t Transformer) transform_infix_expr(expr ast.InfixExpr) ast.Expr { } else { map_ptr = t.addr_of_expr_with_temp(expr.rhs, map_typ) } - key_ptr := t.addr_of_expr_with_temp(expr.lhs, map_typ.key_type) - exists_call := ast.CallExpr{ + // For the key, declare a temp variable in the same scope as the + // map__exists call so the pointer stays valid during the call. + // Using addr_of_expr_with_temp would nest the temp inside a + // statement expression whose scope ends before map__exists reads it. + lhs_trans := t.transform_expr(expr.lhs) + mut key_ptr := ast.Expr(ast.empty_expr) + mut key_stmts := []ast.Stmt{} + if t.can_take_address_expr(lhs_trans) + && !t.is_enum_rvalue(lhs_trans, map_typ.key_type) { + key_ptr = ast.PrefixExpr{ + op: .amp + expr: lhs_trans + } + } else { + key_tmp := t.gen_temp_name() + key_ident := ast.Ident{ + name: key_tmp + } + t.register_temp_var(key_tmp, map_typ.key_type) + key_stmts << ast.Stmt(ast.AssignStmt{ + op: .decl_assign + lhs: [ast.Expr(key_ident)] + rhs: [lhs_trans] + }) + key_ptr = ast.PrefixExpr{ + op: .amp + expr: key_ident + } + } + exists_call := ast.Expr(ast.CallExpr{ lhs: ast.Ident{ name: 'map__exists' } args: [map_ptr, key_ptr] pos: expr.pos - } + }) + mut result_expr := exists_call if expr.op == .not_in { - return ast.PrefixExpr{ + result_expr = ast.PrefixExpr{ op: .not expr: exists_call pos: expr.pos } } - return exists_call + if key_stmts.len > 0 { + // Wrap in UnsafeExpr so the temp key variable is in scope + // for the entire map__exists call. + key_stmts << ast.Stmt(ast.ExprStmt{ + expr: result_expr + }) + return ast.UnsafeExpr{ + stmts: key_stmts + } + } + return result_expr } } // For inline array literals, expand to a chain of equality checks @@ -2302,10 +2341,8 @@ fn (mut t Transformer) transform_infix_expr(expr ast.InfixExpr) ast.Expr { // (V is type-checked). This handles cases where is_string_expr fails on // complex expressions like Result data access selectors. should_transform := lhs_is_str || rhs_is_str - || (lhs_is_str_literal && (expr.rhs is ast.Ident - || expr.rhs is ast.SelectorExpr)) - || (rhs_is_str_literal && (expr.lhs is ast.Ident - || expr.lhs is ast.SelectorExpr)) + || (lhs_is_str_literal && (expr.rhs is ast.Ident || expr.rhs is ast.SelectorExpr)) + || (rhs_is_str_literal && (expr.lhs is ast.Ident || expr.lhs is ast.SelectorExpr)) if should_transform { // Transform string comparisons to function calls match expr.op { diff --git a/vlib/v2/transformer/fn.v b/vlib/v2/transformer/fn.v index 0fe537672..9aa643883 100644 --- a/vlib/v2/transformer/fn.v +++ b/vlib/v2/transformer/fn.v @@ -202,16 +202,13 @@ fn (t &Transformer) method_key_matches_type_name(method_key string, type_name st if short_key == short_type { return true } - if method_key.len > short_type.len + 2 - && method_key[method_key.len - short_type.len - 2] == `_` + if method_key.len > short_type.len + 2 && method_key[method_key.len - short_type.len - 2] == `_` && method_key[method_key.len - short_type.len - 1] == `_` && method_key.ends_with(short_type) { return true } - if type_name.len > short_key.len + 2 - && type_name[type_name.len - short_key.len - 2] == `_` - && type_name[type_name.len - short_key.len - 1] == `_` - && type_name.ends_with(short_key) { + if type_name.len > short_key.len + 2 && type_name[type_name.len - short_key.len - 2] == `_` + && type_name[type_name.len - short_key.len - 1] == `_` && type_name.ends_with(short_key) { return true } return false @@ -611,7 +608,11 @@ fn (mut t Transformer) transform_fn_decl(decl ast.FnDecl) ast.FnDecl { } else { decl.name } - fn_scope_key := if t.cur_module == '' { scope_fn_name } else { '${t.cur_module}__${scope_fn_name}' } + fn_scope_key := if t.cur_module == '' { + scope_fn_name + } else { + '${t.cur_module}__${scope_fn_name}' + } if fn_scope := t.cached_fn_scopes[fn_scope_key] { t.scope = types.new_scope(fn_scope) t.fn_root_scope = t.scope diff --git a/vlib/v2/transformer/struct.v b/vlib/v2/transformer/struct.v index eb96618f0..7291ebfbc 100644 --- a/vlib/v2/transformer/struct.v +++ b/vlib/v2/transformer/struct.v @@ -1505,8 +1505,7 @@ fn (t &Transformer) get_struct_field_type_name(struct_name string, field_name st } // Prefer the current module scope for non-qualified names // to avoid collisions (e.g., ast.FnType vs types.FnType both named "FnType"). - if dunder < 0 && t.cur_module != '' && t.cur_module != 'main' - && t.cur_module != 'builtin' { + if dunder < 0 && t.cur_module != '' && t.cur_module != 'main' && t.cur_module != 'builtin' { if cur_scope := t.cached_scopes[t.cur_module] { if obj := cur_scope.objects[struct_name] { if obj is types.Type { diff --git a/vlib/v2/transformer/transformer.v b/vlib/v2/transformer/transformer.v index ba50201f9..a3207cacd 100644 --- a/vlib/v2/transformer/transformer.v +++ b/vlib/v2/transformer/transformer.v @@ -6879,8 +6879,8 @@ fn (t &Transformer) is_string_returning_fn(fn_name string) bool { // Known string-returning functions (hardcoded to avoid scope lookup failures // in ARM64-compiled binaries where the checker's type store may be unreliable) if fn_name in ['string__plus', 'string__plus_two', 'string__substr', 'string__substr_unsafe', - 'string__repeat', 'tos', 'tos2', 'tos3', 'tos4', 'tos5', 'tos_clone', - 'cstring_to_vstring', 'string_clone'] { + 'string__repeat', 'tos', 'tos2', 'tos3', 'tos4', 'tos5', 'tos_clone', 'cstring_to_vstring', + 'string_clone'] { return true } // String module functions generally return strings (except bytes/vbytes which return []u8) diff --git a/vlib/v2/transformer/transformer_test.v b/vlib/v2/transformer/transformer_test.v index 04130ce49..80edf3c33 100644 --- a/vlib/v2/transformer/transformer_test.v +++ b/vlib/v2/transformer/transformer_test.v @@ -345,7 +345,9 @@ fn test_transform_init_expr_resolves_imported_enum_shorthand() { pref: &vpref.Preferences{} env: unsafe { env } cur_module: 'main' - cached_scopes: {'ast': ast_scope} + cached_scopes: { + 'ast': ast_scope + } needed_array_contains_fns: map[string]ArrayMethodInfo{} needed_array_index_fns: map[string]ArrayMethodInfo{} needed_array_last_index_fns: map[string]ArrayMethodInfo{} diff --git a/vlib/v2/transformer/types.v b/vlib/v2/transformer/types.v index a61739853..398852c6d 100644 --- a/vlib/v2/transformer/types.v +++ b/vlib/v2/transformer/types.v @@ -209,7 +209,8 @@ fn (t &Transformer) is_fn_ident(ident ast.Ident) bool { return t.is_callable_type(typ) } // Fallback: check if the name exists as a registered function - return t.lookup_fn_cached('', ident.name) != none || t.lookup_fn_cached('builtin', ident.name) != none + return t.lookup_fn_cached('', ident.name) != none + || t.lookup_fn_cached('builtin', ident.name) != none } // is_interface_type checks if a type is an Interface @@ -726,7 +727,9 @@ fn (t &Transformer) zero_value_expr_for_type(typ types.Type) ast.Expr { 'i${typ.size}' } return ast.Expr(ast.CastExpr{ - typ: ast.Ident{name: prim_name} + typ: ast.Ident{ + name: prim_name + } expr: ast.BasicLiteral{ kind: .number value: '0' @@ -734,7 +737,9 @@ fn (t &Transformer) zero_value_expr_for_type(typ types.Type) ast.Expr { }) } else if typ.size > 32 && typ.props.has(.float) { return ast.Expr(ast.CastExpr{ - typ: ast.Ident{name: 'f${typ.size}'} + typ: ast.Ident{ + name: 'f${typ.size}' + } expr: ast.BasicLiteral{ kind: .number value: '0' diff --git a/vlib/v2/types/checker.v b/vlib/v2/types/checker.v index b1ef30a5c..892c6ad23 100644 --- a/vlib/v2/types/checker.v +++ b/vlib/v2/types/checker.v @@ -34,8 +34,6 @@ pub fn Environment.new() &Environment { } } - - // set_expr_type stores the computed type for an expression by its unique ID. pub fn (mut e Environment) set_expr_type(id int, typ Type) { if id >= 0 { -- 2.39.5