v / vlib / v2 / builder / ssa_build_parallel.v
204 lines · 187 sloc · 6.59 KB · ddb021b9866c3b4523b746fa2f4c16a594f8bd89
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module builder
5
6import runtime
7import v2.ast
8import v2.ssa
9
10// FnDeclRef references a function declaration within the files array.
11struct FnDeclRef {
12 file_idx int
13 stmt_idx int
14 mod_name string
15 selective_import_fn_names map[string]string
16 selective_import_fn_candidates map[string][]string
17 module_import_aliases map[string]string
18}
19
20$if !windows {
21 struct SSABuildChunkArgs {
22 worker voidptr // &ssa.Builder (pre-created worker builder)
23 files voidptr // &[]ast.File
24 fn_refs voidptr // &[]FnDeclRef
25 start_idx int
26 end_idx int
27 }
28
29 fn C.pthread_create(thread &C.pthread_t, attr voidptr, start_routine fn (voidptr) voidptr, arg voidptr) int
30 fn C.pthread_join(thread C.pthread_t, retval voidptr) int
31 fn C.pthread_attr_init(attr voidptr) int
32 fn C.pthread_attr_setstacksize(attr voidptr, stacksize usize) int
33 fn C.pthread_attr_destroy(attr voidptr) int
34
35 fn ssa_build_chunk_thread(arg voidptr) voidptr {
36 a := unsafe { &SSABuildChunkArgs(arg) }
37 mut worker_b := unsafe { &ssa.Builder(a.worker) }
38 files := unsafe { &[]ast.File(a.files) }
39 fn_refs := unsafe { &[]FnDeclRef(a.fn_refs) }
40
41 // Build assigned functions.
42 // Avoid chained array access like files[i].stmts[j] — in ARM64-compiled
43 // binaries, chained indexing returns copies with potentially corrupted fields.
44 // Instead, copy to a local first, then access fields.
45 for fi := a.start_idx; fi < a.end_idx; fi++ {
46 ref := unsafe { fn_refs[fi] }
47 worker_b.cur_module = ref.mod_name
48 worker_b.set_selective_import_fn_names(ref.selective_import_fn_names)
49 worker_b.set_selective_import_fn_candidates(ref.selective_import_fn_candidates)
50 worker_b.set_module_import_aliases(ref.module_import_aliases)
51 file := unsafe { (*files)[ref.file_idx] }
52 stmt := file.stmts[ref.stmt_idx]
53 decl := stmt as ast.FnDecl
54 worker_b.build_fn(decl)
55 }
56 return unsafe { nil }
57 }
58}
59
60fn (mut b Builder) ssa_build_parallel(mut ssa_builder ssa.Builder, files []ast.File) {
61 n_jobs := runtime.nr_jobs()
62 mut mod := ssa_builder.mod
63
64 // Collect all function declarations that need building.
65 // Avoid chained array access like files[fi].stmts[si] — copy to locals first.
66 has_markused := ssa_builder.used_fn_keys.len > 0
67 mut fn_refs := []FnDeclRef{cap: 4096}
68 for fi in 0 .. files.len {
69 file := files[fi]
70 mod_name := ssa.file_module_name(file)
71 selective_import_fn_names := ssa.selective_import_fn_names_from_imports(file.imports)
72 selective_import_fn_candidates :=
73 ssa.selective_import_fn_candidates_from_imports(file.imports)
74 module_import_aliases := ssa.module_import_aliases_from_imports(file.imports)
75 nstmts := file.stmts.len
76 for si in 0 .. nstmts {
77 stmt := file.stmts[si]
78 if stmt is ast.FnDecl {
79 decl := stmt as ast.FnDecl
80 if decl.language == .c && decl.stmts.len == 0 {
81 continue
82 }
83 if decl.typ.generic_params.len > 0 {
84 continue
85 }
86 // Dead code elimination: skip unreachable functions
87 if has_markused {
88 ssa_builder.cur_module = mod_name
89 if !ssa_builder.should_build_fn(file.name, decl) {
90 continue
91 }
92 }
93 fn_refs << FnDeclRef{
94 file_idx: fi
95 stmt_idx: si
96 mod_name: mod_name
97 selective_import_fn_names: selective_import_fn_names.clone()
98 selective_import_fn_candidates: selective_import_fn_candidates.clone()
99 module_import_aliases: module_import_aliases.clone()
100 }
101 }
102 }
103 }
104
105 n_fns := fn_refs.len
106 $if windows {
107 ssa_builder.build_all_fn_bodies(files)
108 ssa_builder.generate_referenced_synthetic_runtime_stubs()
109 return
110 } $else {
111 if n_fns <= 1 || n_jobs <= 1 {
112 // Fallback to sequential
113 ssa_builder.build_all_fn_bodies(files)
114 ssa_builder.generate_referenced_synthetic_runtime_stubs()
115 return
116 }
117
118 // Pre-create all worker modules and builders on the main thread
119 // to avoid COW races on shared data structures.
120 chunk_size := (n_fns + n_jobs - 1) / n_jobs
121 mut actual_chunks := 0
122 mut i := 0
123 for i < n_fns {
124 actual_chunks++
125 i += chunk_size
126 }
127
128 // Record seed lengths for merge — workers' new data starts beyond these.
129 seed_values := mod.values.len
130 seed_instrs := mod.instrs.len
131 seed_blocks := mod.blocks.len
132 seed_types := mod.type_store.types.len
133 seed_funcs := mod.funcs.len
134
135 mut workers := []voidptr{cap: actual_chunks}
136 for ci := 0; ci < actual_chunks; ci++ {
137 mut worker_mod := mod.new_worker_module()
138 mut worker_b := ssa_builder.new_worker_clone(worker_mod, ci)
139 workers << voidptr(worker_b)
140 }
141
142 // Spawn worker threads
143 mut thread_ids := []C.pthread_t{len: actual_chunks}
144 mut args := []SSABuildChunkArgs{cap: actual_chunks}
145
146 attr_buf := [64]u8{}
147 attr := unsafe { voidptr(&attr_buf[0]) }
148 C.pthread_attr_init(attr)
149 C.pthread_attr_setstacksize(attr, 64 * 1024 * 1024)
150
151 mut chunk_idx := 0
152 i = 0
153 for i < n_fns {
154 end := if i + chunk_size < n_fns { i + chunk_size } else { n_fns }
155 args << SSABuildChunkArgs{
156 worker: workers[chunk_idx]
157 files: unsafe { voidptr(&files) }
158 fn_refs: unsafe { voidptr(&fn_refs) }
159 start_idx: i
160 end_idx: end
161 }
162 C.pthread_create(unsafe { &thread_ids[chunk_idx] }, attr, ssa_build_chunk_thread,
163 unsafe { voidptr(&args[chunk_idx]) })
164 i = end
165 chunk_idx++
166 }
167 C.pthread_attr_destroy(attr)
168
169 // Wait for all workers
170 for ci := 0; ci < chunk_idx; ci++ {
171 C.pthread_join(thread_ids[ci], unsafe { nil })
172 }
173
174 // Merge worker results in order
175 for ci := 0; ci < chunk_idx; ci++ {
176 w := unsafe { &ssa.Builder(workers[ci]) }
177 w_mod := w.mod
178 // Collect func_data from worker's modified funcs[].
179 // Only include functions that were actually built by this worker,
180 // not pre-seeded synthetic functions (array__eq, wyhash*, etc.)
181 // whose blocks already exist in the main module from Phase 3.5.
182 mut func_data := []ssa.FuncSSAData{cap: 512}
183 for fi2 := 0; fi2 < w_mod.funcs.len; fi2++ {
184 wf := w_mod.funcs[fi2]
185 if wf.blocks.len == 0 {
186 continue
187 }
188 // Seeded funcs (fi2 < seed_funcs) that already had blocks before
189 // workers started are not new work — skip them.
190 if fi2 < seed_funcs && wf.blocks[0] < seed_blocks {
191 continue
192 }
193 func_data << ssa.FuncSSAData{
194 func_idx: fi2
195 blocks: wf.blocks
196 params: wf.params
197 }
198 }
199 mod.merge_worker_module(w_mod, func_data, seed_values, seed_instrs, seed_blocks,
200 seed_types, seed_funcs)
201 }
202 ssa_builder.generate_referenced_synthetic_runtime_stubs()
203 }
204}
205