@@ -73,34 +73,48 @@ fn (mut g FlatGen) gen_fns_dispatch(no_parallel bool) {
7373 g.prepare_parallel_items (items)
7474 mut chunk_items := split_flat_cgen_items (items, n_jobs)
7575 chunk_count := chunk_items.len
76- mut thread_ids := []C.pthread_t{len: chunk_count}
77- mut args := []FlatCgenChunkArgs{cap: chunk_count}
78- mut workers := []voidptr {cap: chunk_count}
76+ // chunk[0] is emitted by the master (this thread) directly into its own builder
77+ // — no worker clone. Only chunks[1..] get helper threads with a cloned FlatGen.
78+ // This drops one full FlatGen clone from the peak and uses the master thread that
79+ // would otherwise just block in join.
80+ thread_count := chunk_count - 1
81+ mut thread_ids := []C.pthread_t{len: thread_count}
82+ mut args := []FlatCgenChunkArgs{cap: thread_count}
83+ mut workers := []voidptr {cap: thread_count}
7984
80- for ci := 0 ; ci < chunk_count; ci++ {
81- w := g.new_parallel_worker (ci)
85+ // Helper workers keep the same temp-name base they had when every chunk was a
86+ // worker (worker_id ci+1 -> base (ci+2)*100_000), and the master adopts what was
87+ // worker 0's base. This keeps each chunk in its own disjoint _tN range AND makes
88+ // the output byte-identical to the all-workers version.
89+ for ci := 0 ; ci < thread_count; ci++ {
90+ w := g.new_parallel_worker (ci + 1 )
8291 workers << voidptr (w)
8392 }
84- for ci := 0 ; ci < chunk_count ; ci++ {
93+ for ci := 0 ; ci < thread_count ; ci++ {
8594 args << FlatCgenChunkArgs{
8695 worker: workers[ci]
87- work_items_ptr: unsafe { voidptr (& chunk_items[ci]) }
96+ work_items_ptr: unsafe { voidptr (& chunk_items[ci + 1 ]) }
8897 }
8998 }
9099
91100 attr_buf := [64 ]u8 {}
92101 attr := unsafe { voidptr (& attr_buf[0 ]) }
93102 C.pthread_attr_init (attr)
94103 C.pthread_attr_setstacksize (attr, 64 * 1024 * 1024 )
95- for ci := 0 ; ci < chunk_count ; ci++ {
104+ for ci := 0 ; ci < thread_count ; ci++ {
96105 C.pthread_create (unsafe { & thread_ids[ci] }, attr, flat_cgen_chunk_thread,
97106 unsafe { voidptr (& args[ci]) })
98107 }
99108 C.pthread_attr_destroy (attr)
100- for ci := 0 ; ci < chunk_count; ci++ {
109+ // Master emits chunk[0] into g.sb while the helper threads run, using worker 0's
110+ // old temp base so its emitted temps match the all-workers numbering.
111+ g.tmp_count = 100_000
112+ g.gen_fn_items (chunk_items[0 ])
113+ for ci := 0 ; ci < thread_count; ci++ {
101114 C.pthread_join (thread_ids[ci], unsafe { nil })
102115 }
103- for ci := 0 ; ci < chunk_count; ci++ {
116+ // Merge helper output after the master's chunk[0], in fixed order.
117+ for ci := 0 ; ci < thread_count; ci++ {
104118 w := unsafe { & FlatGen (workers[ci]) }
105119 g.merge_parallel_worker (w)
106120 }
@@ -324,39 +338,50 @@ fn (mut g FlatGen) fn_ptr_type_key(typ types.FnType) string {
324338 return 'fn_ptr:${ret }|${params .join (', ')}'
325339}
326340
327- // new_parallel_worker supports new parallel worker handling for FlatGen.
341+ // new_parallel_worker builds a per-worker FlatGen for parallel codegen.
342+ //
343+ // The lookup tables populated before gen_fns_dispatch (in collect_gen_info,
344+ // collect_interface_impls and the precompute_* passes) are READ-ONLY during codegen, so
345+ // they are SHARED by reference instead of cloned — V maps/arrays are reference types and
346+ // concurrent readers are safe. Only the state a worker actually mutates while emitting is
347+ // kept private: the output builder; the string-literal table (interned during gen); the
348+ // fn_ptr_types / needed_optional_types / emitted_* sets and the param_types_cache /
349+ // array_method_cache memoization caches (all written during gen); the per-function
350+ // cur_param_* scratch; and runtime_inits (kept private out of caution). This drops the
351+ // bulk of each worker's clone cost — previously the whole table set was duplicated per
352+ // worker and, under -gc none, never freed.
328353fn (g &FlatGen) new_parallel_worker (worker_id int ) & FlatGen {
329354 return & FlatGen{
330355 sb: strings.new_builder (64_000 )
331356 a: unsafe { g.a }
332- used_fns: g.used_fns. clone ()
333- used_fn_names: g.used_fn_names. clone ()
357+ used_fns: g.used_fns
358+ used_fn_names: g.used_fn_names
334359 str_lits: g.str_lits.clone ()
335360 str_lit_ids: g.str_lit_ids.clone ()
336- global_types: g.global_types. clone ()
337- enum_vals: g.enum_vals. clone ()
338- interfaces: g.interfaces. clone ()
339- const_vals: g.const_vals. clone ()
340- const_modules: g.const_modules. clone ()
341- const_init_order: g.const_init_order. clone ()
342- global_modules: g.global_modules. clone ()
343- global_inits: g.global_inits. clone ()
344- global_init_order: g.global_init_order. clone ()
345- iface_impls: g.iface_impls. clone ()
346- iface_type_ids: g.iface_type_ids. clone ()
347- module_init_fns: g.module_init_fns. clone ()
348- module_init_fn_modules: g.module_init_fn_modules. clone ()
349- module_imports: g.module_imports. clone ()
361+ global_types: g.global_types
362+ enum_vals: g.enum_vals
363+ interfaces: g.interfaces
364+ const_vals: g.const_vals
365+ const_modules: g.const_modules
366+ const_init_order: g.const_init_order
367+ global_modules: g.global_modules
368+ global_inits: g.global_inits
369+ global_init_order: g.global_init_order
370+ iface_impls: g.iface_impls
371+ iface_type_ids: g.iface_type_ids
372+ module_init_fns: g.module_init_fns
373+ module_init_fn_modules: g.module_init_fn_modules
374+ module_imports: g.module_imports
350375 tc: g.clone_parallel_type_checker ()
351376 has_builtins: g.has_builtins
352377 tmp_count: (worker_id + 1 ) * 100_000
353378 line_start: true
354- modules: g.modules. clone ()
379+ modules: g.modules
355380 fn_ptr_types: g.fn_ptr_types.clone ()
356- fn_decl_param_types: g.fn_decl_param_types. clone ()
357- fn_decl_ret_types: g.fn_decl_ret_types. clone ()
358- struct_decl_infos: g.struct_decl_infos. clone ()
359- struct_decl_short_infos: g.struct_decl_short_infos. clone ()
381+ fn_decl_param_types: g.fn_decl_param_types
382+ fn_decl_ret_types: g.fn_decl_ret_types
383+ struct_decl_infos: g.struct_decl_infos
384+ struct_decl_short_infos: g.struct_decl_short_infos
360385 runtime_inits: g.runtime_inits.clone ()
361386 compiler_vroot: g.compiler_vroot
362387 cur_param_names: g.cur_param_names.clone ()
@@ -372,57 +397,69 @@ fn (g &FlatGen) new_parallel_worker(worker_id int) &FlatGen {
372397 emitted_fns: g.emitted_fns.clone ()
373398 array_method_cache: g.array_method_cache.clone ()
374399 param_types_cache: g.param_types_cache.clone ()
375- embedded_fields_by_type: g.embedded_fields_by_type. clone ()
376- param_types_by_short: g.param_types_by_short. clone ()
400+ embedded_fields_by_type: g.embedded_fields_by_type
401+ param_types_by_short: g.param_types_by_short
377402 }
378403}
379404
380- // clone_parallel_type_checker supports clone parallel type checker handling for FlatGen.
405+ // clone_parallel_type_checker builds a per-worker TypeChecker for parallel codegen.
406+ //
407+ // During codegen the checker's lookup tables are READ-ONLY: cgen only ever assigns the
408+ // scalar `cur_file`/`cur_module` fields, and the read paths it uses (expr_type, c_type,
409+ // parse_type, resolve_type, cached_resolved_call) never write into the big maps — the only
410+ // memoizing write is into `type_cache`, which is left nil here so workers take the uncached
411+ // path. V maps and arrays are reference types, so the read-only tables are SHARED by
412+ // reference (no `.clone()`), exactly like the already-shared `a` FlatAst. This avoids
413+ // deep-copying the program-wide `expr_type_*`/`structs`/signature tables once per worker,
414+ // which was the bulk of parallel cgen's extra RAM and serial setup time.
415+ //
416+ // Only genuinely per-worker mutable state is given its own copy: the scope chain (gen pushes
417+ // child scopes) and `errors` (avoid a concurrent append race, though gen does not emit any).
381418fn (g &FlatGen) clone_parallel_type_checker () & types.TypeChecker {
382419 mut fs := types.new_scope (unsafe { nil })
383420 fs.names = g.tc.file_scope.names.clone ()
384421 fs.types = g.tc.file_scope.types.clone ()
385422 return & types.TypeChecker{
386423 a: unsafe { g.tc.a }
387- fn_ret_types: g.tc.fn_ret_types. clone ()
388- fn_param_types: g.tc.fn_param_types. clone ()
389- fn_variadic: g.tc.fn_variadic. clone ()
390- structs: g.tc.structs. clone ()
391- unions: g.tc.unions. clone ()
392- type_aliases: g.tc.type_aliases. clone ()
393- sum_types: g.tc.sum_types. clone ()
394- enum_names: g.tc.enum_names. clone ()
395- enum_fields: g.tc.enum_fields. clone ()
396- flag_enums: g.tc.flag_enums. clone ()
397- interface_names: g.tc.interface_names. clone ()
398- interface_fields: g.tc.interface_fields. clone ()
399- interface_embeds: g.tc.interface_embeds. clone ()
400- interface_abstract_methods: g.tc.interface_abstract_methods. clone ()
401- c_globals: g.tc.c_globals. clone ()
402- const_types: g.tc.const_types. clone ()
403- const_exprs: g.tc.const_exprs. clone ()
404- const_modules: g.tc.const_modules. clone ()
405- const_suffixes: g.tc.const_suffixes. clone ()
406- imports: g.tc.imports. clone ()
407- file_imports: g.tc.file_imports. clone ()
408- file_modules: g.tc.file_modules. clone ()
424+ fn_ret_types: g.tc.fn_ret_types
425+ fn_param_types: g.tc.fn_param_types
426+ fn_variadic: g.tc.fn_variadic
427+ structs: g.tc.structs
428+ unions: g.tc.unions
429+ type_aliases: g.tc.type_aliases
430+ sum_types: g.tc.sum_types
431+ enum_names: g.tc.enum_names
432+ enum_fields: g.tc.enum_fields
433+ flag_enums: g.tc.flag_enums
434+ interface_names: g.tc.interface_names
435+ interface_fields: g.tc.interface_fields
436+ interface_embeds: g.tc.interface_embeds
437+ interface_abstract_methods: g.tc.interface_abstract_methods
438+ c_globals: g.tc.c_globals
439+ const_types: g.tc.const_types
440+ const_exprs: g.tc.const_exprs
441+ const_modules: g.tc.const_modules
442+ const_suffixes: g.tc.const_suffixes
443+ imports: g.tc.imports
444+ file_imports: g.tc.file_imports
445+ file_modules: g.tc.file_modules
409446 file_scope: fs
410447 cur_scope: fs
411448 scope_pool: []& types.Scope{}
412449 has_builtins: g.tc.has_builtins
413450 cur_module: g.tc.cur_module
414451 cur_file: g.tc.cur_file
415452 errors: g.tc.errors.clone ()
416- resolved_call_names: g.tc.resolved_call_names. clone ()
417- resolved_call_set: g.tc.resolved_call_set. clone ()
418- expr_type_values: g.tc.expr_type_values. clone ()
419- expr_type_set: g.tc.expr_type_set. clone ()
420- checking_nodes: g.tc.checking_nodes. clone ()
453+ resolved_call_names: g.tc.resolved_call_names
454+ resolved_call_set: g.tc.resolved_call_set
455+ expr_type_values: g.tc.expr_type_values
456+ expr_type_set: g.tc.expr_type_set
457+ checking_nodes: g.tc.checking_nodes
421458 diagnose_unknown_calls: g.tc.diagnose_unknown_calls
422459 reject_unlowered_map_mutation: g.tc.reject_unlowered_map_mutation
423- diagnostic_files: g.tc.diagnostic_files. clone ()
460+ diagnostic_files: g.tc.diagnostic_files
424461 cur_fn_ret_type: g.tc.cur_fn_ret_type
425- smartcasts: g.tc.smartcasts. clone ()
462+ smartcasts: g.tc.smartcasts
426463 }
427464}
428465
0 commit comments