v4 / vlib / v3 / gen / c / fn_d_parallel.v
541 lines · 513 sloc · 19.04 KB · bd87753646a4c2ae1384399584858dc14cb90b28
Raw
1module c
2
3import runtime
4import strings
5import v3.flat
6import v3.types
7
8const max_flat_cgen_jobs = 2
9const min_flat_cgen_parallel_items = 1024
10
11// FlatFnGenItem represents flat fn gen item data used by c.
12struct FlatFnGenItem {
13 node_id flat.NodeId
14 file string
15 module string
16 cost int
17}
18
19$if !windows {
20 // FlatCgenChunkArgs represents flat cgen chunk args data used by c.
21 struct FlatCgenChunkArgs {
22 worker voidptr
23 work_items_ptr voidptr
24 }
25
26 // C.pthread_t declares C pthread t data used by c.
27 @[typedef]
28 struct C.pthread_t {}
29
30 // C.pthread_create declares the C pthread_create symbol used by c.
31 fn C.pthread_create(thread &C.pthread_t, attr voidptr, start_routine fn (voidptr) voidptr, arg voidptr) int
32
33 // C.pthread_join declares the C pthread_join symbol used by c.
34 fn C.pthread_join(thread C.pthread_t, retval voidptr) int
35
36 // C.pthread_attr_init declares the C pthread_attr_init symbol used by c.
37 fn C.pthread_attr_init(attr voidptr) int
38
39 // C.pthread_attr_setstacksize declares the C pthread_attr_setstacksize symbol used by c.
40 fn C.pthread_attr_setstacksize(attr voidptr, stacksize usize) int
41
42 // C.pthread_attr_destroy declares the C pthread_attr_destroy symbol used by c.
43 fn C.pthread_attr_destroy(attr voidptr) int
44
45 // flat_cgen_chunk_thread supports flat cgen chunk thread handling for c.
46 fn flat_cgen_chunk_thread(arg voidptr) voidptr {
47 a := unsafe { &FlatCgenChunkArgs(arg) }
48 mut w := unsafe { &FlatGen(a.worker) }
49 items := unsafe { &[]FlatFnGenItem(a.work_items_ptr) }
50 w.gen_fn_items(*items)
51 return unsafe { nil }
52 }
53}
54
55// gen_fns_dispatch emits fns dispatch output for c.
56fn (mut g FlatGen) gen_fns_dispatch(no_parallel bool) {
57 if no_parallel {
58 g.gen_fns()
59 g.gen_synthetic_main_after_fns()
60 return
61 }
62 items := g.collect_fn_gen_items()
63 n_items := items.len
64 n_jobs := flat_cgen_job_count(runtime.nr_jobs(), n_items)
65 $if windows {
66 g.gen_fn_items(items)
67 g.gen_synthetic_main_after_fns()
68 return
69 } $else {
70 if n_items < min_flat_cgen_parallel_items || n_jobs <= 1 {
71 g.gen_fn_items(items)
72 g.gen_synthetic_main_after_fns()
73 return
74 }
75 g.parallel_used = true
76 g.prepare_parallel_items(items)
77 mut chunk_items := split_flat_cgen_items(items, n_jobs)
78 chunk_count := chunk_items.len
79 // chunk[0] is emitted by the master (this thread) directly into its own builder
80 // — no worker clone. Only chunks[1..] get helper threads with a cloned FlatGen.
81 // This drops one full FlatGen clone from the peak and uses the master thread that
82 // would otherwise just block in join.
83 thread_count := chunk_count - 1
84 mut thread_ids := []C.pthread_t{len: thread_count}
85 mut args := []FlatCgenChunkArgs{cap: thread_count}
86 mut workers := []voidptr{cap: thread_count}
87
88 // Helper workers keep the same temp-name base they had when every chunk was a
89 // worker (worker_id ci+1 -> base (ci+2)*100_000), and the master adopts what was
90 // worker 0's base. This keeps each chunk in its own disjoint _tN range AND makes
91 // the output byte-identical to the all-workers version.
92 for ci := 0; ci < thread_count; ci++ {
93 w := g.new_parallel_worker(ci + 1)
94 workers << voidptr(w)
95 }
96 for ci := 0; ci < thread_count; ci++ {
97 args << FlatCgenChunkArgs{
98 worker: workers[ci]
99 work_items_ptr: unsafe { voidptr(&chunk_items[ci + 1]) }
100 }
101 }
102
103 attr_buf := [64]u8{}
104 attr := unsafe { voidptr(&attr_buf[0]) }
105 C.pthread_attr_init(attr)
106 C.pthread_attr_setstacksize(attr, 64 * 1024 * 1024)
107 for ci := 0; ci < thread_count; ci++ {
108 C.pthread_create(unsafe { &thread_ids[ci] }, attr, flat_cgen_chunk_thread,
109 unsafe { voidptr(&args[ci]) })
110 }
111 C.pthread_attr_destroy(attr)
112 // Master emits chunk[0] into g.sb while the helper threads run, using worker 0's
113 // old temp base so its emitted temps match the all-workers numbering.
114 g.tmp_count = 100_000
115 g.gen_fn_items(chunk_items[0])
116 for ci := 0; ci < thread_count; ci++ {
117 C.pthread_join(thread_ids[ci], unsafe { nil })
118 }
119 // Merge helper output after the master's chunk[0], in fixed order.
120 for ci := 0; ci < thread_count; ci++ {
121 w := unsafe { &FlatGen(workers[ci]) }
122 g.merge_parallel_worker(w)
123 }
124 g.gen_synthetic_main_after_fns()
125 }
126}
127
128// flat_cgen_job_count supports flat cgen job count handling for c.
129fn flat_cgen_job_count(n_runtime_jobs int, n_items int) int {
130 if n_runtime_jobs <= 0 || n_items <= 0 {
131 return 0
132 }
133 mut n_jobs := n_runtime_jobs
134 if n_jobs > max_flat_cgen_jobs {
135 n_jobs = max_flat_cgen_jobs
136 }
137 if n_jobs > n_items {
138 n_jobs = n_items
139 }
140 return n_jobs
141}
142
143// split_flat_cgen_items supports split flat cgen items handling for c.
144fn split_flat_cgen_items(items []FlatFnGenItem, n_jobs int) [][]FlatFnGenItem {
145 mut chunks := [][]FlatFnGenItem{}
146 if n_jobs <= 0 || items.len == 0 {
147 return chunks
148 }
149 mut total_cost := 0
150 for item in items {
151 total_cost += item.cost
152 }
153 mut target_cost := total_cost / n_jobs
154 if total_cost % n_jobs != 0 {
155 target_cost++
156 }
157 if target_cost < 1 {
158 target_cost = 1
159 }
160 mut current := []FlatFnGenItem{}
161 mut current_cost := 0
162 mut chunks_left := n_jobs
163 for idx, item in items {
164 remaining_items := items.len - idx
165 if current.len > 0 && current_cost >= target_cost && chunks_left > 1
166 && remaining_items >= chunks_left {
167 chunks << current
168 current = []FlatFnGenItem{}
169 current_cost = 0
170 chunks_left--
171 }
172 current << item
173 current_cost += item.cost
174 }
175 if current.len > 0 {
176 chunks << current
177 }
178 return chunks
179}
180
181// collect_fn_gen_items updates collect fn gen items state for c.
182fn (mut g FlatGen) collect_fn_gen_items() []FlatFnGenItem {
183 mut items := []FlatFnGenItem{}
184 mut cur_module := ''
185 mut cur_file := ''
186 for i in 0 .. g.a.nodes.len {
187 node := g.a.nodes[i]
188 kind_id := node_kind_id(node)
189 if kind_id == 77 {
190 cur_file = node.value
191 g.tc.cur_file = cur_file
192 cur_module = ''
193 g.tc.cur_module = cur_module
194 continue
195 }
196 if kind_id == 73 {
197 cur_module = node.value
198 g.tc.cur_file = cur_file
199 g.tc.cur_module = cur_module
200 continue
201 }
202
203 if kind_id != 61 {
204 continue
205 }
206 if !g.should_emit_fn_node_in_module(node, i, cur_module) {
207 continue
208 }
209 qfn := qualified_fn_name_in_module(cur_module, node.value)
210 if g.emitted_fn_contains(qfn) {
211 continue
212 }
213 g.emitted_fns[qfn] = true
214 items << FlatFnGenItem{
215 node_id: flat.NodeId(i)
216 file: cur_file
217 module: cur_module
218 cost: node.children_count + 1
219 }
220 }
221 return items
222}
223
224// gen_fn_items emits fn items output for c.
225fn (mut g FlatGen) gen_fn_items(items []FlatFnGenItem) {
226 for item in items {
227 if int(item.node_id) < 0 || int(item.node_id) >= g.a.nodes.len {
228 continue
229 }
230 g.tc.cur_file = item.file
231 g.tc.cur_module = item.module
232 node := g.a.nodes[int(item.node_id)]
233 g.gen_fn_in_module(node, item.module)
234 }
235}
236
237// prepare_parallel_items supports prepare parallel items handling for FlatGen.
238fn (mut g FlatGen) prepare_parallel_items(items []FlatFnGenItem) {
239 for item in items {
240 g.tc.cur_file = item.file
241 g.tc.cur_module = item.module
242 g.prepare_parallel_node(item.node_id)
243 }
244 g.register_interface_strings()
245}
246
247// prepare_parallel_node supports prepare parallel node handling for FlatGen.
248fn (mut g FlatGen) prepare_parallel_node(id flat.NodeId) {
249 if int(id) < 0 || int(id) >= g.a.nodes.len {
250 return
251 }
252 node := g.a.nodes[int(id)]
253 if node.kind == .string_literal {
254 g.intern_string(node.value)
255 }
256 if g.should_preseed_parallel_type_text(node.typ) {
257 g.preseed_parallel_fn_ptr_type(g.tc.parse_type(node.typ))
258 }
259 if expr_type := g.tc.expr_type(id) {
260 g.preseed_parallel_fn_ptr_type(expr_type)
261 }
262 for i in 0 .. node.children_count {
263 g.prepare_parallel_node(g.a.child(&node, i))
264 }
265}
266
267// should_preseed_parallel_type_text reports whether should preseed parallel type text applies in c.
268fn (g &FlatGen) should_preseed_parallel_type_text(typ string) bool {
269 if typ.len == 0 {
270 return false
271 }
272 clean := g.parallel_base_type_text(typ)
273 if clean.starts_with('fn(') || clean.starts_with('fn (') {
274 return true
275 }
276 if clean in g.tc.type_aliases {
277 return true
278 }
279 qtyp := g.tc.qualify_name(clean)
280 return qtyp in g.tc.type_aliases
281}
282
283// parallel_base_type_text supports parallel base type text handling for FlatGen.
284fn (g &FlatGen) parallel_base_type_text(typ string) string {
285 mut clean := typ.trim_space()
286 for clean.len > 0 {
287 if clean.starts_with('shared ') {
288 clean = clean[7..].trim_space()
289 } else if clean[0] == `&` || clean[0] == `?` || clean[0] == `!` {
290 clean = clean[1..].trim_space()
291 } else if clean.starts_with('...') {
292 clean = clean[3..].trim_space()
293 } else if clean.starts_with('[]') {
294 clean = clean[2..].trim_space()
295 } else {
296 break
297 }
298 }
299 return clean
300}
301
302// preseed_parallel_fn_ptr_type supports preseed parallel fn ptr type handling for FlatGen.
303fn (mut g FlatGen) preseed_parallel_fn_ptr_type(typ types.Type) {
304 if typ is types.FnType {
305 g.resolve_fn_ptr_type(g.fn_ptr_type_key(typ))
306 for param in typ.params {
307 g.preseed_parallel_fn_ptr_type(param)
308 }
309 g.preseed_parallel_fn_ptr_type(typ.return_type)
310 } else if typ is types.Pointer {
311 g.preseed_parallel_fn_ptr_type(typ.base_type)
312 } else if typ is types.Array {
313 g.preseed_parallel_fn_ptr_type(typ.elem_type)
314 } else if typ is types.ArrayFixed {
315 g.preseed_parallel_fn_ptr_type(typ.elem_type)
316 } else if typ is types.Map {
317 g.preseed_parallel_fn_ptr_type(typ.key_type)
318 g.preseed_parallel_fn_ptr_type(typ.value_type)
319 } else if typ is types.OptionType {
320 g.preseed_parallel_fn_ptr_type(typ.base_type)
321 } else if typ is types.ResultType {
322 g.preseed_parallel_fn_ptr_type(typ.base_type)
323 } else if typ is types.Alias {
324 g.preseed_parallel_fn_ptr_type(typ.base_type)
325 } else if typ is types.MultiReturn {
326 for item in typ.types {
327 g.preseed_parallel_fn_ptr_type(item)
328 }
329 }
330}
331
332// fn_ptr_type_key supports fn ptr type key handling for FlatGen.
333fn (mut g FlatGen) fn_ptr_type_key(typ types.FnType) string {
334 ret := if typ.return_type is types.Void { 'void' } else { g.tc.c_type(typ.return_type) }
335 if typ.params.len == 0 {
336 return 'fn_ptr:${ret}|void'
337 }
338 mut params := []string{}
339 for param in typ.params {
340 params << g.tc.c_type(param)
341 }
342 return 'fn_ptr:${ret}|${params.join(', ')}'
343}
344
345// new_parallel_worker builds a per-worker FlatGen for parallel codegen.
346//
347// The lookup tables populated before gen_fns_dispatch (in collect_gen_info,
348// collect_interface_impls and the precompute_* passes) are READ-ONLY during codegen, so
349// they are SHARED by reference instead of cloned — V maps/arrays are reference types and
350// concurrent readers are safe. Only the state a worker actually mutates while emitting is
351// kept private: the output builder; the string-literal table (interned during gen); the
352// fn_ptr_types / needed_optional_types / emitted_* sets and the param_types_cache /
353// array_method_cache memoization caches (all written during gen); the per-function
354// cur_param_* scratch; and runtime_inits (kept private out of caution). This drops the
355// bulk of each worker's clone cost — previously the whole table set was duplicated per
356// worker and, under -gc none, never freed.
357fn (g &FlatGen) new_parallel_worker(worker_id int) &FlatGen {
358 return &FlatGen{
359 sb: strings.new_builder(64_000)
360 a: unsafe { g.a }
361 used_fns: g.used_fns
362 used_fn_names: g.used_fn_names
363 test_files: g.test_files.clone()
364 str_lits: g.str_lits.clone()
365 str_lit_ids: g.str_lit_ids.clone()
366 global_types: g.global_types
367 enum_vals: g.enum_vals
368 interfaces: g.interfaces
369 const_vals: g.const_vals
370 const_modules: g.const_modules
371 const_init_order: g.const_init_order
372 global_modules: g.global_modules
373 global_inits: g.global_inits
374 global_init_order: g.global_init_order
375 iface_impls: g.iface_impls
376 iface_type_ids: g.iface_type_ids
377 module_init_fns: g.module_init_fns
378 module_init_fn_modules: g.module_init_fn_modules
379 module_imports: g.module_imports
380 libc_compat_fns: g.libc_compat_fns.clone()
381 tc: g.clone_parallel_type_checker()
382 has_builtins: g.has_builtins
383 tmp_count: (worker_id + 1) * 100_000
384 line_start: true
385 modules: g.modules
386 fn_ptr_types: g.fn_ptr_types.clone()
387 fixed_array_ret_wrappers: g.fixed_array_ret_wrappers
388 fn_decl_param_types: g.fn_decl_param_types
389 fn_decl_ret_types: g.fn_decl_ret_types
390 struct_decl_infos: g.struct_decl_infos
391 struct_decl_short_infos: g.struct_decl_short_infos
392 runtime_inits: g.runtime_inits.clone()
393 compiler_vroot: g.compiler_vroot
394 cur_param_names: g.cur_param_names.clone()
395 cur_param_type_values: g.cur_param_type_values.clone()
396 cur_param_types: g.cur_param_types.clone()
397 cur_fn_ret: g.cur_fn_ret
398 cur_fn_ret_is_optional: g.cur_fn_ret_is_optional
399 cur_fn_ret_base: g.cur_fn_ret_base
400 expected_expr_type: g.expected_expr_type
401 expected_enum: g.expected_enum
402 needed_optional_types: g.needed_optional_types.clone()
403 emitted_optional_types: g.emitted_optional_types.clone()
404 emitted_fns: g.emitted_fns.clone()
405 array_method_cache: g.array_method_cache.clone()
406 param_types_cache: g.param_types_cache.clone()
407 embedded_fields_by_type: g.embedded_fields_by_type
408 param_types_by_short: g.param_types_by_short
409 spawn_wrapper_names: g.spawn_wrapper_names.clone()
410 spawn_wrapper_defs: g.spawn_wrapper_defs.clone()
411 callback_wrapper_names: g.callback_wrapper_names.clone()
412 callback_wrapper_defs: g.callback_wrapper_defs.clone()
413 }
414}
415
416// clone_parallel_type_checker builds a per-worker TypeChecker for parallel codegen.
417//
418// During codegen the checker's lookup tables are READ-ONLY: cgen only ever assigns the
419// scalar `cur_file`/`cur_module` fields, and the read paths it uses (expr_type, c_type,
420// parse_type, resolve_type, cached_resolved_call) never write into the big maps — the only
421// memoizing write is into `type_cache`, which is left nil here so workers take the uncached
422// path. V maps and arrays are reference types, so the read-only tables are SHARED by
423// reference (no `.clone()`), exactly like the already-shared `a` FlatAst. This avoids
424// deep-copying the program-wide `expr_type_*`/`structs`/signature tables once per worker,
425// which was the bulk of parallel cgen's extra RAM and serial setup time.
426//
427// Only genuinely per-worker mutable state is given its own copy: the scope chain (gen pushes
428// child scopes) and `errors` (avoid a concurrent append race, though gen does not emit any).
429fn (g &FlatGen) clone_parallel_type_checker() &types.TypeChecker {
430 mut fs := types.new_scope(unsafe { nil })
431 fs.names = g.tc.file_scope.names.clone()
432 fs.types = g.tc.file_scope.types.clone()
433 return &types.TypeChecker{
434 a: unsafe { g.tc.a }
435 fn_ret_types: g.tc.fn_ret_types
436 fn_param_types: g.tc.fn_param_types
437 fn_ret_type_texts: g.tc.fn_ret_type_texts
438 fn_param_type_texts: g.tc.fn_param_type_texts
439 fn_type_files: g.tc.fn_type_files
440 fn_type_modules: g.tc.fn_type_modules
441 fn_variadic: g.tc.fn_variadic
442 fn_implicit_veb_ctx: g.tc.fn_implicit_veb_ctx
443 c_variadic_fns: g.tc.c_variadic_fns
444 structs: g.tc.structs
445 struct_generic_params: g.tc.struct_generic_params
446 struct_field_c_abi_fns: g.tc.struct_field_c_abi_fns
447 unions: g.tc.unions
448 type_aliases: g.tc.type_aliases
449 type_alias_c_abi_fns: g.tc.type_alias_c_abi_fns
450 sum_types: g.tc.sum_types
451 enum_names: g.tc.enum_names
452 enum_fields: g.tc.enum_fields
453 flag_enums: g.tc.flag_enums
454 interface_names: g.tc.interface_names
455 interface_fields: g.tc.interface_fields
456 interface_embeds: g.tc.interface_embeds
457 interface_abstract_methods: g.tc.interface_abstract_methods
458 c_globals: g.tc.c_globals
459 const_types: g.tc.const_types
460 const_exprs: g.tc.const_exprs
461 const_modules: g.tc.const_modules
462 const_suffixes: g.tc.const_suffixes
463 imports: g.tc.imports
464 file_imports: g.tc.file_imports
465 file_selective_imports: g.tc.file_selective_imports
466 file_modules: g.tc.file_modules
467 file_scope: fs
468 cur_scope: fs
469 scope_pool: []&types.Scope{}
470 has_builtins: g.tc.has_builtins
471 cur_module: g.tc.cur_module
472 cur_file: g.tc.cur_file
473 errors: g.tc.errors.clone()
474 resolved_call_names: g.tc.resolved_call_names
475 resolved_call_set: g.tc.resolved_call_set
476 resolved_fn_value_names: g.tc.resolved_fn_value_names
477 resolved_fn_value_set: g.tc.resolved_fn_value_set
478 expr_type_values: g.tc.expr_type_values
479 expr_type_set: g.tc.expr_type_set
480 checking_nodes: g.tc.checking_nodes
481 diagnose_unknown_calls: g.tc.diagnose_unknown_calls
482 reject_unlowered_map_mutation: g.tc.reject_unlowered_map_mutation
483 diagnostic_files: g.tc.diagnostic_files
484 cur_fn_ret_type: g.tc.cur_fn_ret_type
485 smartcasts: g.tc.smartcasts
486 // Read-only map cgen uses to recover substituted signatures for generic-receiver
487 // method values (`Box[int].method` as a callback); without it a parallel worker
488 // sees an empty map and gen_method_value_closure falls through.
489 generic_method_value_info: g.tc.generic_method_value_info
490 }
491}
492
493// merge_parallel_worker supports merge parallel worker handling for FlatGen.
494fn (mut g FlatGen) merge_parallel_worker(w &FlatGen) {
495 mut ww := unsafe { w }
496 worker_output := ww.sb.str()
497 if worker_output.len > 0 {
498 g.sb.write_string(worker_output)
499 }
500 // The master builder has copied the worker output; release the worker buffers.
501 unsafe {
502 worker_output.free()
503 ww.sb.free()
504 }
505 for opt_name, val_type in w.needed_optional_types {
506 g.needed_optional_types[opt_name] = val_type
507 }
508 for encoded, name in w.fn_ptr_types {
509 if encoded !in g.fn_ptr_types {
510 g.fn_ptr_types[encoded] = name
511 }
512 }
513 for name, enabled in w.libc_compat_fns {
514 if enabled {
515 g.libc_compat_fns[name] = true
516 }
517 }
518 // Spawn wrappers (thread arg structs + trampoline fns) are generated on demand
519 // inside fn bodies, so a worker that emits a `spawn` produces wrapper defs the
520 // master must also emit. Deduplicate by their deterministic key/def.
521 for key, name in w.spawn_wrapper_names {
522 if key !in g.spawn_wrapper_names {
523 g.spawn_wrapper_names[key] = name
524 }
525 }
526 for def in w.spawn_wrapper_defs {
527 if def !in g.spawn_wrapper_defs {
528 g.spawn_wrapper_defs << def
529 }
530 }
531 for key, name in w.callback_wrapper_names {
532 if key !in g.callback_wrapper_names {
533 g.callback_wrapper_names[key] = name
534 }
535 }
536 for def in w.callback_wrapper_defs {
537 if def !in g.callback_wrapper_defs {
538 g.callback_wrapper_defs << def
539 }
540 }
541}
542