| 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. |
| 4 | module builder |
| 5 | |
| 6 | import runtime |
| 7 | import v2.ast |
| 8 | import v2.ssa |
| 9 | |
| 10 | // FnDeclRef references a function declaration within the files array. |
| 11 | struct 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 | |
| 60 | fn (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 | |