v / vlib / v2 / gen / cleanc / cleanc.v
4809 lines · 4607 sloc · 153.99 KB
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.
4
5module cleanc
6
7import v2.ast
8import v2.pref
9import v2.types
10import os
11import strings
12import time
13
14pub struct Gen {
15mut:
16 files []ast.File
17 flat &ast.FlatAst = unsafe { nil }
18 env &types.Environment = unsafe { nil }
19 pref &pref.Preferences = unsafe { nil }
20 sb strings.Builder
21 indent int
22 cur_fn_scope &types.Scope = unsafe { nil }
23 cur_fn_name string
24 cur_fn_c_name string
25 cur_fn_ret_type string
26 cur_fn_c_ret_type string
27 expected_init_expr_type string
28 cur_module string
29 cur_fn_scope_miss_key string
30 // Per-return drop snapshots for the current fn, populated by the
31 // checker. Indexed positionally by `cur_fn_return_index`, which the
32 // ReturnStmt handler increments. Reset at every fn entry. Empty for
33 // fns the checker didn't see (e.g., plain V without `-d ownership`).
34 cur_fn_return_drops [][]types.DropEntry
35 cur_fn_return_index int
36 // Set true while gen_return_if_branch synthesizes ReturnStmts for
37 // `return if cond { x } else { y }`. The synthesized statements go
38 // through the same ReturnStmt handler, but they represent the SAME
39 // source-level return — emitting drops for them again would consume
40 // the next snapshot (belonging to a later source return) and break
41 // the parallel counter the checker relies on.
42 suppress_return_drop_emit bool
43 emitted_types map[string]bool
44 fn_param_is_ptr map[string][]bool
45 fn_param_types map[string][]string
46 fn_return_types map[string]string
47 v_fn_return_types map[string]string
48 runtime_local_types map[string]string
49 runtime_decl_types map[string]string
50 runtime_fn_pointer_types map[string]types.Type
51 cur_fn_returned_idents map[string]bool
52 active_generic_types map[string]types.Type
53 cur_fn_generic_params map[string]string
54 // Comptime $for field iteration state
55 comptime_field_var string // variable name (e.g., 'field')
56 comptime_field_name string // current field name (e.g., 'id')
57 comptime_field_type string // current field C type name
58 comptime_field_raw_type types.Type = types.Struct{} // raw types.Type for comptime checks
59 comptime_field_attrs []string // current field attributes
60 comptime_field_idx int // current field index
61 comptime_field_is_embed bool // current field is embedded
62 comptime_continue_label string // label for continue inside unrolled comptime field loops
63 comptime_val_var string // the struct variable being decoded (e.g., 'val')
64 comptime_val_type string // C type of val (e.g., 'Slack')
65 // Comptime $for method iteration state
66 comptime_method_var string // loop variable name (e.g., 'method')
67 comptime_method_name string // current method name (e.g., 'index')
68 comptime_method_attrs []string // current method attributes
69 comptime_method_return_type ast.Expr = ast.empty_expr // current method return type (AST expr)
70 comptime_method_args []ast.Parameter // current method params (excluding receiver)
71 comptime_method_idx int // current method index
72 comptime_method_receiver_type string // receiver C type (e.g., 'main__App')
73 comptime_method_struct_name string // receiver struct V name (e.g., 'App')
74
75 fixed_array_fields map[string]bool
76 fixed_array_field_elem map[string]string
77 fixed_array_globals map[string]bool
78 tuple_aliases map[string][]string
79 struct_field_types map[string]string
80 enum_value_to_enum map[string]string
81 enum_type_fields map[string]map[string]bool
82 array_aliases map[string]bool
83 map_aliases map[string]bool
84 result_aliases map[string]bool
85 option_aliases map[string]bool
86 alias_base_types map[string]string
87 fn_type_aliases map[string]bool
88 emitted_result_structs map[string]bool
89 emitted_option_structs map[string]bool
90 embedded_field_owner map[string]string
91 collected_fixed_array_types map[string]FixedArrayInfo
92 collected_map_types map[string]MapTypeInfo
93 fixed_array_ret_wrappers map[string]string
94 sum_type_variants map[string][]string
95 // Interface method signatures: interface_name -> [(method_name, cast_signature), ...]
96 interface_methods map[string][]InterfaceMethodInfo
97 interface_data_fields map[string][]InterfaceDataFieldInfo
98 interface_decls map[string]ast.InterfaceDecl
99 emitted_interface_bodies map[string]bool
100 interface_wrapper_specs map[string]InterfaceWrapperSpec
101 needed_interface_wrappers map[string]bool
102 ierror_wrapper_bases map[string]bool
103 needed_ierror_wrapper_bases map[string]bool
104 tmp_counter int
105 cur_fn_mut_params map[string]bool // names of mut params in current function
106 global_var_modules map[string]string // global var name → module name
107 global_var_types map[string]string // global var name → C type string
108 module_storage_vars map[string]string // module-prefixed storage symbol -> declaring module
109 c_extern_module_storage map[string]string // module-prefixed C extern symbol -> raw C name
110 primitive_type_aliases map[string]bool // type names that are aliases for primitive types
111 emit_modules map[string]bool // when set, emit consts/globals/fns only for these modules
112 type_modules map[string]bool // when set, alias/type helpers may reference only these modules
113 emit_files map[string]bool // when set, emit consts/globals/fns only for these source files
114 export_const_symbols bool
115 cache_bundle_name string
116 cached_init_calls []string
117 exported_const_seen map[string]bool
118 exported_const_symbols []ExportedConstSymbol
119 cur_file_name string
120 cur_import_modules map[string]string
121 is_module_ident_cache map[string]bool // per-function cache for is_module_ident results
122 not_local_var_cache map[string]bool // per-function negative cache for get_local_var_c_type
123 resolved_module_names map[string]string // per-function cache for resolve_module_name
124 cached_env_scopes map[string]voidptr // cache of env_scope results (avoids repeated locking)
125 selector_field_type_cache map[string]string
126 selector_field_type_miss map[string]bool
127 struct_field_lookup_cache map[string]string
128 struct_field_lookup_miss map[string]bool
129 struct_type_lookup_cache map[string]types.Struct
130 struct_type_lookup_miss map[string]bool
131 struct_decl_info_cache map[string]StructDeclInfo
132 struct_decl_info_miss map[string]bool
133 flat_struct_decl_exact map[string]FlatStructDeclInfo
134 flat_struct_decl_short_by_mod map[string]FlatStructDeclInfo
135 flat_struct_decl_short map[string]FlatStructDeclInfo
136 flat_struct_decl_indexed bool
137 alias_base_lookup_cache map[string]string
138 alias_base_lookup_miss map[string]bool
139 declared_type_names_all map[string]bool
140 declared_type_names_by_mod map[string]bool
141 emit_file_modules map[string]bool
142 declared_type_names_in_emit_files map[string]bool
143 source_module_names map[string]bool
144 imported_symbols_index map[string]string // "file_name\x01symbol_name" -> importing module (built once from g.files)
145 v_method_return_index map[string]string // method short name -> unique V return type (or v_method_ret_ambiguous)
146 ierror_base_index map[string]string // base -> qualified concrete base from the smallest `*__base__msg` fn (built once)
147
148 const_exprs map[string]string // const name → C expression string (for inlining)
149 const_types map[string]string // const name → C type string
150 const_c_names map[string]string // generated const name → collision-free C symbol name
151 runtime_const_targets map[string]bool // module-scoped consts initialized in __v_init_consts_*
152 used_fn_keys map[string]bool
153 force_emit_fn_names map[string]bool // function C names that must be emitted regardless of mark_used
154 export_fn_names map[string]string // V-qualified name → export name (from @[export:] attribute)
155 called_fn_names map[string]bool
156 called_specialized_names map[string]string // base generic C name -> called specialized C name
157 called_specialized_names_indexed bool
158 declared_fn_names map[string]bool // C function names that have a prototype/body head emitted
159 should_emit_fn_decl_cache map[string]bool
160 generic_body_scan_cache map[string]bool
161 collect_generic_scan_calls bool
162 generic_call_spec_scan_only bool
163 generic_scan_called_names map[string]bool
164 generic_spec_index map[string][]string // fn_name → matching keys in env.generic_types
165 generic_fn_decl_index map[string]GenericFnDeclInfo // generic fn C/base name → source location
166 specialized_fn_bases map[string]bool // base C name with at least one _T_ specialization
167 specialized_receiver_methods map[string]string // receiver|method -> single matching specialized method
168 specialized_receiver_method_ambiguous map[string]bool // receiver|method keys with multiple matches
169 specialized_receiver_method_miss map[string]bool // receiver|method keys with no matching specialized method
170 specialized_receiver_method_indexed bool // true after existing signature maps have been indexed
171 late_generic_specs map[string][]map[string]types.Type // additional comptime-discovered specs
172 anon_fn_defs []string // lifted anonymous function definitions
173 late_struct_defs []string // struct definitions discovered during pass 5 codegen
174 pending_late_body_keys map[string]bool // body_keys in late_struct_defs but not yet flushed to g.sb
175 late_generic_str_instances []string // c_names of late generic struct instances needing str macro check
176 pass5_start_pos int // position in sb where pass 5 starts
177 deferred_m_includes []string // Objective-C .m file #include lines deferred until after type definitions
178 spawned_fns map[string]bool // spawn wrapper names already emitted
179 spawn_wrapper_defs []string // spawn wrapper struct + function definitions
180 emitted_trampolines map[string]bool // bound method trampoline names already emitted
181 trampoline_defs []string // bound method trampoline definitions
182 // @[live] hot code reloading
183 live_fns []LiveFnInfo // @[live] functions detected during code generation
184 live_source_file string // source file containing @[live] functions
185 test_fn_names []string // test function names collected in Pass 4
186 has_main bool // whether a main() function was found in Pass 4
187 fn_owner_file map[string]int // fn_key -> first file index (for parallel dedup)
188 global_owner_file map[string]int // global_name -> first file index (for parallel dedup)
189 generic_struct_bindings map[string]map[string]types.Type // struct_name -> {T: concrete_type}
190 // Multi-instantiation support: maps base struct C name (e.g. "json2__Node") to
191 // a list of (suffix, bindings) pairs for each distinct concrete instantiation.
192 // E.g. [("json2__ValueInfo", {T: ValueInfo}), ("json2__StructFieldInfo", {T: StructFieldInfo})]
193 generic_struct_instances map[string][]GenericStructInstance
194 generic_setup_snapshot GenericSetupSnapshot
195 has_generic_setup_snapshot bool
196 c_file_fn_keys map[string]bool // fn_key -> emitted from a .c.v file, so plain .v fallback should be skipped
197 c_struct_types map[string]bool // C struct names declared with `struct C.Name`
198 typedef_c_types map[string]bool // C struct names with @[typedef] attribute (emit without 'struct' prefix)
199 blocked_fn_keys map[string]bool // worker-only fn keys reserved to other pass5 chunks
200 cached_vhash string // cached git short hash for @VHASH/@VCURRENTHASH
201 pass5_worker_id int
202 pass5_file_times []Pass5FileTime
203 // When a large file is split across workers, only the worker that owns its
204 // globals takes file-level dedup ownership; the others block the file's fns.
205 // During its assigned slice a non-owning worker sets these so gen_fn_decl can
206 // bypass blocked_fn_keys for fns owned by exactly explicit_slice_file (the slice
207 // it is explicitly responsible for) without unblocking anything reached from
208 // another file. See gen_file_range / explicit_slice_emit_allows.
209 explicit_slice_active bool
210 explicit_slice_file int
211}
212
213struct Pass5FileTime {
214 file string
215 ms i64
216 cost int
217 worker_id int
218}
219
220struct GenericStructInstance {
221 params_key string // e.g. "json2__ValueInfo" — unique key per instantiation
222 bindings map[string]types.Type // e.g. {T: ValueInfo}
223 c_name string // full C struct name, e.g. "json2__Node_T_json2__StructFieldInfo"
224}
225
226pub struct GenericSetupSnapshot {
227mut:
228 ready bool
229 tuple_aliases map[string][]string
230 array_aliases map[string]bool
231 map_aliases map[string]bool
232 result_aliases map[string]bool
233 option_aliases map[string]bool
234 collected_fixed_array_types map[string]FixedArrayInfo
235 collected_map_types map[string]MapTypeInfo
236 generic_body_scan_cache map[string]bool
237 generic_spec_index map[string][]string
238 late_generic_specs map[string][]map[string]types.Type
239 generic_struct_bindings map[string]map[string]types.Type
240 generic_struct_instances map[string][]GenericStructInstance
241}
242
243struct LiveFnInfo {
244 c_name string // C function name (e.g., 'main__frame', 'Game__update_model')
245 ret_type string // C return type (e.g., 'void', 'string')
246 params string // C parameter list (e.g., 'Game* game')
247 args string // argument names for forwarding (e.g., 'game')
248 is_void bool // true if return type is void
249}
250
251struct ExportedConstSymbol {
252 name string
253 typ string
254 value string
255}
256
257struct StructDeclInfo {
258 decl ast.StructDecl
259 mod string
260 file_name string
261}
262
263struct FlatStructDeclInfo {
264 file_idx int
265 stmt_idx int
266 mod string
267 file_name string
268}
269
270struct InterfaceDeclInfo {
271 decl ast.InterfaceDecl
272 mod string
273 file_name string
274}
275
276fn (mut g Gen) set_struct_info_context(info StructDeclInfo) {
277 g.cur_module = info.mod
278 g.cur_file_name = info.file_name
279}
280
281fn (mut g Gen) set_interface_info_context(info InterfaceDeclInfo) {
282 g.cur_module = info.mod
283 g.cur_file_name = info.file_name
284}
285
286struct FixedArrayInfo {
287 elem_type string
288 size int
289}
290
291struct GenericFnSpecialization {
292 name string
293 generic_types map[string]types.Type
294}
295
296struct GenericFnDeclInfo {
297 file_idx int
298 stmt_idx int
299}
300
301struct MapTypeInfo {
302 key_c_type string
303 value_c_type string
304}
305
306const primitive_types = ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64',
307 'bool', 'rune', 'byte', 'voidptr', 'charptr', 'usize', 'isize', 'void', 'char', 'byteptr',
308 'float_literal', 'int_literal']
309
310fn is_empty_stmt(s ast.Stmt) bool {
311 return s is ast.EmptyStmt
312}
313
314fn generic_signature_struct_type_name(raw_name string) string {
315 mut name := raw_name.trim_space()
316 if name.starts_with('&') {
317 name = name[1..].trim_space()
318 }
319 for name.ends_with('*') {
320 name = name[..name.len - 1].trim_space()
321 }
322 if name == '' || !name.contains('_T_') {
323 return ''
324 }
325 if name.contains('(*)') {
326 return ''
327 }
328 if name in primitive_types || name in ['string', 'array', 'map', 'void*', 'char*', 'u8*'] {
329 return ''
330 }
331 if name.starts_with('Array_') || name.starts_with('Map_') || name.starts_with('Tuple_')
332 || name.starts_with('_option_') || name.starts_with('_result_') {
333 return ''
334 }
335 return name
336}
337
338fn (mut g Gen) emit_forward_typedef_for_signature_type(raw_name string) bool {
339 name := generic_signature_struct_type_name(raw_name)
340 if name == '' || name in g.emitted_types {
341 return false
342 }
343 g.emitted_types[name] = true
344 g.sb.writeln('typedef struct ${name} ${name};')
345 return true
346}
347
348fn (mut g Gen) emit_forward_typedefs_for_signature_types() {
349 mut emitted_any := false
350 mut fn_names := g.fn_return_types.keys()
351 for fn_name in g.fn_param_types.keys() {
352 if fn_name !in fn_names {
353 fn_names << fn_name
354 }
355 }
356 fn_names.sort()
357 for fn_name in fn_names {
358 if ret_type := g.fn_return_types[fn_name] {
359 emitted_any = g.emit_forward_typedef_for_signature_type(ret_type) || emitted_any
360 }
361 if param_types := g.fn_param_types[fn_name] {
362 for param_type in param_types {
363 emitted_any = g.emit_forward_typedef_for_signature_type(param_type) || emitted_any
364 }
365 }
366 }
367 if emitted_any {
368 g.sb.writeln('')
369 }
370}
371
372fn (mut g Gen) get_v_hash() string {
373 if g.cached_vhash.len > 0 {
374 return g.cached_vhash
375 }
376 vroot := if g.pref != unsafe { nil } && g.pref.vroot.len > 0 {
377 g.pref.vroot
378 } else {
379 ''
380 }
381 if vroot.len > 0 {
382 result := os.execute('git -C "${vroot}" rev-parse --short=7 HEAD')
383 if result.exit_code == 0 {
384 g.cached_vhash = result.output.trim_space()
385 return g.cached_vhash
386 }
387 }
388 g.cached_vhash = 'unknown'
389 return g.cached_vhash
390}
391
392fn stmt_has_valid_data(stmt ast.Stmt) bool {
393 if stmt is ast.ReturnStmt {
394 return true
395 }
396 return unsafe { (&u64(&stmt))[1] } != 0
397}
398
399fn expr_has_valid_data(expr ast.Expr) bool {
400 return unsafe { (&u64(&expr))[1] } != 0
401}
402
403fn type_has_valid_data(typ types.Type) bool {
404 return unsafe { (&u64(&typ))[1] } != 0
405}
406
407fn count_substr_in_string(s string, needle string) int {
408 if needle.len == 0 {
409 return 0
410 }
411 mut count := 0
412 mut i := 0
413 for i <= s.len - needle.len {
414 if s[i..i + needle.len] == needle {
415 count++
416 i += needle.len
417 } else {
418 i++
419 }
420 }
421 return count
422}
423
424pub fn Gen.new(files []ast.File) &Gen {
425 mut g := new_gen_with_env_and_pref_impl(unsafe { nil }, unsafe { nil })
426 g.files = files
427 return g
428}
429
430pub fn Gen.new_with_env(files []ast.File, env &types.Environment) &Gen {
431 mut g := new_gen_with_env_and_pref_impl(env, unsafe { nil })
432 g.files = files
433 return g
434}
435
436pub fn Gen.new_with_env_and_pref(files []ast.File, env &types.Environment, p &pref.Preferences) &Gen {
437 mut g := new_gen_with_env_and_pref_impl(env, p)
438 g.files = files
439 return g
440}
441
442pub fn Gen.new_with_env_pref_and_flat(flat &ast.FlatAst, env &types.Environment, p &pref.Preferences) &Gen {
443 mut g := new_gen_with_env_and_pref_impl(env, p)
444 g.flat = unsafe { flat }
445 return g
446}
447
448fn new_gen_with_env_and_pref_impl(env &types.Environment, p &pref.Preferences) &Gen {
449 return &Gen{
450 files: []ast.File{}
451 env: unsafe { env }
452 pref: unsafe { p }
453 sb: strings.new_builder(10_000)
454 fn_param_is_ptr: map[string][]bool{}
455 fn_param_types: map[string][]string{}
456 fn_return_types: map[string]string{}
457 v_fn_return_types: map[string]string{}
458 runtime_local_types: map[string]string{}
459 runtime_decl_types: map[string]string{}
460 runtime_fn_pointer_types: map[string]types.Type{}
461 cur_fn_returned_idents: map[string]bool{}
462 active_generic_types: map[string]types.Type{}
463 cur_fn_generic_params: map[string]string{}
464 cur_fn_scope_miss_key: ''
465 selector_field_type_cache: map[string]string{}
466 selector_field_type_miss: map[string]bool{}
467 struct_field_lookup_cache: map[string]string{}
468 struct_field_lookup_miss: map[string]bool{}
469 struct_type_lookup_cache: map[string]types.Struct{}
470 struct_type_lookup_miss: map[string]bool{}
471 struct_decl_info_cache: map[string]StructDeclInfo{}
472 struct_decl_info_miss: map[string]bool{}
473 flat_struct_decl_exact: map[string]FlatStructDeclInfo{}
474 flat_struct_decl_short_by_mod: map[string]FlatStructDeclInfo{}
475 flat_struct_decl_short: map[string]FlatStructDeclInfo{}
476 flat_struct_decl_indexed: false
477 alias_base_lookup_cache: map[string]string{}
478 alias_base_lookup_miss: map[string]bool{}
479 declared_type_names_all: map[string]bool{}
480 declared_type_names_by_mod: map[string]bool{}
481 emit_file_modules: map[string]bool{}
482 declared_type_names_in_emit_files: map[string]bool{}
483 cur_import_modules: map[string]string{}
484
485 fixed_array_fields: map[string]bool{}
486 fixed_array_field_elem: map[string]string{}
487 fixed_array_globals: map[string]bool{}
488 tuple_aliases: map[string][]string{}
489 struct_field_types: map[string]string{}
490 enum_value_to_enum: map[string]string{}
491 enum_type_fields: map[string]map[string]bool{}
492 array_aliases: map[string]bool{}
493 map_aliases: map[string]bool{}
494 result_aliases: map[string]bool{}
495 option_aliases: map[string]bool{}
496 alias_base_types: map[string]string{}
497 fn_type_aliases: map[string]bool{}
498 emitted_result_structs: map[string]bool{}
499 emitted_option_structs: map[string]bool{}
500 embedded_field_owner: map[string]string{}
501 fixed_array_ret_wrappers: map[string]string{}
502 emit_modules: map[string]bool{}
503 type_modules: map[string]bool{}
504 source_module_names: map[string]bool{}
505 imported_symbols_index: map[string]string{}
506 v_method_return_index: map[string]string{}
507 ierror_base_index: map[string]string{}
508 exported_const_seen: map[string]bool{}
509 exported_const_symbols: []ExportedConstSymbol{}
510 emitted_interface_bodies: map[string]bool{}
511 interface_data_fields: map[string][]InterfaceDataFieldInfo{}
512 interface_wrapper_specs: map[string]InterfaceWrapperSpec{}
513 needed_interface_wrappers: map[string]bool{}
514 ierror_wrapper_bases: map[string]bool{}
515 needed_ierror_wrapper_bases: map[string]bool{}
516 c_file_fn_keys: map[string]bool{}
517 module_storage_vars: map[string]string{}
518 c_extern_module_storage: map[string]string{}
519 runtime_const_targets: map[string]bool{}
520 const_c_names: map[string]string{}
521 used_fn_keys: map[string]bool{}
522 force_emit_fn_names: map[string]bool{}
523 called_fn_names: map[string]bool{}
524 called_specialized_names: map[string]string{}
525 called_specialized_names_indexed: false
526 declared_fn_names: map[string]bool{}
527 should_emit_fn_decl_cache: map[string]bool{}
528 generic_body_scan_cache: map[string]bool{}
529 generic_scan_called_names: map[string]bool{}
530 generic_fn_decl_index: map[string]GenericFnDeclInfo{}
531 specialized_fn_bases: map[string]bool{}
532 specialized_receiver_methods: map[string]string{}
533 specialized_receiver_method_ambiguous: map[string]bool{}
534 specialized_receiver_method_miss: map[string]bool{}
535 specialized_receiver_method_indexed: false
536 c_struct_types: map[string]bool{}
537 typedef_c_types: map[string]bool{}
538 blocked_fn_keys: map[string]bool{}
539 }
540}
541
542fn (g &Gen) has_flat() bool {
543 return g.flat != unsafe { nil } && g.flat.files.len > 0
544}
545
546fn (mut g Gen) gen_file(file ast.File) {
547 g.set_file_module(file)
548 file_name := g.cur_file_name
549 file_module := g.cur_module
550 file_import_modules := g.cur_import_modules.clone()
551 mut global_indices := []int{}
552 mut fn_indices := []int{}
553 for i in 0 .. file.stmts.len {
554 stmt_ptr := &file.stmts[i]
555 // Collect top-level items first. Self-hosted cleanc can disturb the active
556 // stmt iteration state after the first emitted body in a file, so discovery
557 // and emission need to be split.
558 if (*stmt_ptr) is ast.GlobalDecl {
559 global_indices << i
560 continue
561 }
562 if (*stmt_ptr) is ast.FnDecl {
563 fn_indices << i
564 }
565 }
566 for gi in global_indices {
567 stmt_ptr := &file.stmts[gi]
568 g.gen_global_decl((*stmt_ptr) as ast.GlobalDecl)
569 }
570 for fi in fn_indices {
571 // Re-set file/module context before each function body emission,
572 // because body generation can modify g.cur_file_name and g.cur_module
573 // (e.g. via find_generic_fn_decl_by_base_name, resolve_method_on_embedded_decl).
574 g.restore_file_module_context(file_name, file_module, file_import_modules)
575 stmt_ptr := &file.stmts[fi]
576 fn_decl := (*stmt_ptr) as ast.FnDecl
577 g.gen_fn_decl_ptr(&fn_decl)
578 }
579}
580
581// gen_file_range emits only the FnDecls at the given statement indices of `file`
582// (and, when emit_globals is set, the file's GlobalDecls). Parallel Pass 5 uses
583// this to split a single large file's functions across several workers so one
584// huge file (e.g. ssa/builder.v) cannot pin the whole parallel phase. Each
585// function is still emitted by exactly one worker (the index ranges partition
586// the file's FnDecls), and only the first range emits globals. Globals are
587// forward-declared for every file in gen_pass5_pre (gen_file_extern_globals),
588// so the single definition's position within the merged output is irrelevant.
589// explicit_slice_emit_allows reports whether a fn that is in blocked_fn_keys may
590// still be emitted because the worker is currently emitting its explicit FnDecl
591// slice of the file that owns this fn. The bypass is scoped to the slice's own
592// file (explicit_slice_file) so it restores exactly the fns the slice would have
593// emitted under file-level ownership and nothing transitively reached from another
594// file (which remains blocked and is emitted by its own owning worker).
595fn (g &Gen) explicit_slice_emit_allows(fn_key string) bool {
596 if !g.explicit_slice_active {
597 return false
598 }
599 if owner := g.fn_owner_file[fn_key] {
600 return owner == g.explicit_slice_file
601 }
602 return false
603}
604
605fn (mut g Gen) gen_file_range(file ast.File, fn_stmt_indices []int, emit_globals bool) {
606 g.set_file_module(file)
607 file_name := g.cur_file_name
608 file_module := g.cur_module
609 file_import_modules := g.cur_import_modules.clone()
610 if emit_globals {
611 for i in 0 .. file.stmts.len {
612 stmt_ptr := &file.stmts[i]
613 if (*stmt_ptr) is ast.GlobalDecl {
614 g.gen_global_decl((*stmt_ptr) as ast.GlobalDecl)
615 }
616 }
617 }
618 for fi in fn_stmt_indices {
619 g.restore_file_module_context(file_name, file_module, file_import_modules)
620 stmt_ptr := &file.stmts[fi]
621 fn_decl := (*stmt_ptr) as ast.FnDecl
622 g.gen_fn_decl_ptr(&fn_decl)
623 }
624}
625
626fn (mut g Gen) gen_file_cursor(fc ast.FileCursor) {
627 g.set_file_cursor_module(fc)
628 file_name := g.cur_file_name
629 file_module := g.cur_module
630 file_import_modules := g.cur_import_modules.clone()
631 stmts := fc.stmts()
632 mut global_indices := []int{}
633 mut fn_indices := []int{}
634 for i in 0 .. stmts.len() {
635 stmt := stmts.at(i)
636 match stmt.kind() {
637 .stmt_global_decl {
638 global_indices << i
639 }
640 .stmt_fn_decl {
641 fn_indices << i
642 }
643 else {}
644 }
645 }
646 for gi in global_indices {
647 g.gen_global_decl(stmts.at(gi).global_decl(true))
648 }
649 for fi in fn_indices {
650 g.restore_file_module_context(file_name, file_module, file_import_modules)
651 fn_decl := stmts.at(fi).fn_decl()
652 g.gen_fn_decl_ptr(&fn_decl)
653 }
654}
655
656fn (mut g Gen) gen_file_cursor_range(fc ast.FileCursor, fn_stmt_indices []int, emit_globals bool) {
657 g.set_file_cursor_module(fc)
658 file_name := g.cur_file_name
659 file_module := g.cur_module
660 file_import_modules := g.cur_import_modules.clone()
661 stmts := fc.stmts()
662 if emit_globals {
663 for i in 0 .. stmts.len() {
664 stmt := stmts.at(i)
665 if stmt.kind() == .stmt_global_decl {
666 g.gen_global_decl(stmt.global_decl(true))
667 }
668 }
669 }
670 for fi in fn_stmt_indices {
671 g.restore_file_module_context(file_name, file_module, file_import_modules)
672 fn_decl := stmts.at(fi).fn_decl()
673 g.gen_fn_decl_ptr(&fn_decl)
674 }
675}
676
677// set_emit_modules limits code emission to the provided module names.
678// Type declarations and forward declarations are still emitted for all modules.
679pub fn (mut g Gen) set_emit_modules(modules []string) {
680 g.emit_modules = map[string]bool{}
681 for module_name in modules {
682 if module_name != '' {
683 g.emit_modules[module_name] = true
684 }
685 }
686}
687
688pub fn (mut g Gen) set_type_modules(modules []string) {
689 g.type_modules = map[string]bool{}
690 for module_name in modules {
691 if module_name != '' {
692 g.type_modules[module_name] = true
693 }
694 }
695}
696
697// set_emit_files limits body/const/global emission to the provided source files.
698// Type declarations and forward declarations are still emitted for all files.
699pub fn (mut g Gen) set_emit_files(files []string) {
700 g.emit_files = map[string]bool{}
701 for file in files {
702 if file != '' {
703 g.emit_files[os.norm_path(file)] = true
704 g.emit_files[os.norm_path(os.abs_path(file))] = true
705 }
706 }
707 g.collect_emit_file_indexes()
708}
709
710fn (mut g Gen) collect_source_module_names() {
711 g.source_module_names = map[string]bool{}
712 // Index `import mod { sym }` selective imports per file so imported_symbol_c_type is an O(1)
713 // lookup instead of rescanning all files' imports/symbols (it was the hottest codegen fn).
714 mut imp_idx := map[string]string{}
715 if g.has_flat() {
716 for i in 0 .. g.flat.files.len {
717 fc := g.flat.file_cursor(i)
718 g.source_module_names[flat_file_module_name(fc)] = true
719 imports := fc.imports()
720 for j in 0 .. imports.len() {
721 import_stmt := imports.at(j).import_stmt()
722 if import_stmt.symbols.len == 0 {
723 continue
724 }
725 mod_name := import_stmt.name.all_after_last('.').replace('.', '_')
726 for symbol in import_stmt.symbols {
727 sn := symbol.name()
728 if sn == '' {
729 continue
730 }
731 key := '${fc.name()}\x01${sn}'
732 if key !in imp_idx { // first occurrence wins, matching the original scan order
733 imp_idx[key] = mod_name
734 }
735 }
736 }
737 }
738 g.imported_symbols_index = imp_idx.move()
739 return
740 }
741 for file in g.files {
742 g.source_module_names[file_module_name(file)] = true
743 for import_stmt in file.imports {
744 if import_stmt.symbols.len == 0 {
745 continue
746 }
747 mod_name := import_stmt.name.all_after_last('.').replace('.', '_')
748 for symbol in import_stmt.symbols {
749 sn := symbol.name()
750 if sn == '' {
751 continue
752 }
753 key := '${file.name}\x01${sn}'
754 if key !in imp_idx { // first occurrence wins, matching the original scan order
755 imp_idx[key] = mod_name
756 }
757 }
758 }
759 }
760 g.imported_symbols_index = imp_idx.move()
761}
762
763fn (mut g Gen) collect_emit_file_indexes() {
764 g.emit_file_modules = map[string]bool{}
765 g.declared_type_names_in_emit_files = map[string]bool{}
766 if g.emit_files.len == 0 {
767 return
768 }
769 if g.has_flat() {
770 for i in 0 .. g.flat.files.len {
771 fc := g.flat.file_cursor(i)
772 file_name := fc.name()
773 if !(os.norm_path(file_name) in g.emit_files
774 || os.norm_path(os.abs_path(file_name)) in g.emit_files) {
775 continue
776 }
777 module_name := flat_file_module_name(fc)
778 g.emit_file_modules[module_name] = true
779 stmts := fc.stmts()
780 for j in 0 .. stmts.len() {
781 stmt := stmts.at(j)
782 match stmt.kind() {
783 .stmt_struct_decl, .stmt_enum_decl, .stmt_interface_decl, .stmt_type_decl {
784 g.record_emit_file_type_name(stmt.name(), module_name)
785 }
786 else {}
787 }
788 }
789 }
790 return
791 }
792 for file in g.files {
793 if !(os.norm_path(file.name) in g.emit_files
794 || os.norm_path(os.abs_path(file.name)) in g.emit_files) {
795 continue
796 }
797 module_name := file_module_name(file)
798 g.emit_file_modules[module_name] = true
799 for stmt in file.stmts {
800 match stmt {
801 ast.StructDecl {
802 g.record_emit_file_type_name(stmt.name, module_name)
803 }
804 ast.EnumDecl {
805 g.record_emit_file_type_name(stmt.name, module_name)
806 }
807 ast.InterfaceDecl {
808 g.record_emit_file_type_name(stmt.name, module_name)
809 }
810 ast.TypeDecl {
811 g.record_emit_file_type_name(stmt.name, module_name)
812 }
813 else {}
814 }
815 }
816 }
817}
818
819fn flat_file_module_name(fc ast.FileCursor) string {
820 mod_name := fc.mod()
821 if mod_name != '' {
822 return mod_name.replace('.', '_')
823 }
824 return 'main'
825}
826
827fn (mut g Gen) record_emit_file_type_name(decl_name string, module_name string) {
828 if decl_name == '' {
829 return
830 }
831 alias_name := type_decl_name_in_module(decl_name, module_name)
832 g.declared_type_names_in_emit_files[alias_name] = true
833 if (module_name == '' || module_name == 'main' || module_name == 'builtin')
834 && decl_name != alias_name {
835 g.declared_type_names_in_emit_files[decl_name] = true
836 }
837}
838
839fn (mut g Gen) collect_force_emit_sort_fns() {
840 if g.emit_files.len == 0 || g.cache_bundle_name.len > 0 {
841 return
842 }
843 old_file := g.cur_file_name
844 old_module := g.cur_module
845 old_import_modules := g.cur_import_modules.clone()
846 mut changed := false
847 if g.has_flat() {
848 for i in 0 .. g.flat.files.len {
849 fc := g.flat.file_cursor(i)
850 g.set_file_cursor_module(fc)
851 if g.cur_module != 'main' {
852 continue
853 }
854 stmts := fc.stmts()
855 for j in 0 .. stmts.len() {
856 stmt := stmts.at(j)
857 if stmt.kind() != .stmt_fn_decl || !stmt.name().starts_with('__sort_cmp_') {
858 continue
859 }
860 decl := stmt.fn_decl_signature()
861 fn_name := g.get_fn_name(decl)
862 if fn_name != '' {
863 if fn_name !in g.force_emit_fn_names {
864 changed = true
865 }
866 g.force_emit_fn_names[fn_name] = true
867 }
868 }
869 }
870 } else {
871 for file in g.files {
872 g.set_file_module(file)
873 if g.cur_module != 'main' {
874 continue
875 }
876 for stmt in file.stmts {
877 if stmt is ast.FnDecl && stmt.name.starts_with('__sort_cmp_') {
878 fn_name := g.get_fn_name(stmt)
879 if fn_name != '' {
880 if fn_name !in g.force_emit_fn_names {
881 changed = true
882 }
883 g.force_emit_fn_names[fn_name] = true
884 }
885 }
886 }
887 }
888 }
889 if changed {
890 g.should_emit_fn_decl_cache = map[string]bool{}
891 }
892 g.cur_file_name = old_file
893 g.cur_module = old_module
894 g.cur_import_modules = old_import_modules.clone()
895}
896
897// set_cached_init_calls sets cache-init functions to invoke from generated main().
898
899// set_used_fn_keys limits function emission to declaration keys marked as used.
900
901fn is_builtin_map_file(path string) bool {
902 normalized := path.replace('\\', '/')
903 return normalized.ends_with('vlib/builtin/map.v')
904}
905
906fn is_builtin_string_file(path string) bool {
907 normalized := path.replace('\\', '/')
908 return normalized.ends_with('vlib/builtin/string.v')
909}
910
911fn (mut g Gen) emit_live_reload_infrastructure() {
912 // Don't emit live infrastructure in shared library mode (only impl_live_ bodies needed)
913 if g.pref != unsafe { nil } && g.pref.is_shared_lib {
914 return
915 }
916 // Skip in cached module sources — __v_live_init belongs only in the main module.
917 if g.cache_bundle_name.len > 0 {
918 return
919 }
920 if g.live_fns.len == 0 {
921 if g.has_live_reload_functions() {
922 g.sb.writeln('')
923 g.sb.writeln('void __v_live_init(void) {}')
924 }
925 return
926 }
927
928 g.sb.writeln('')
929 g.sb.writeln('// ===== @[live] hot code reloading infrastructure =====')
930 g.sb.writeln('#include <dlfcn.h>')
931 g.sb.writeln('')
932
933 // Mutex for thread-safe function pointer updates
934 g.sb.writeln('static pthread_mutex_t __live_fn_mutex = PTHREAD_MUTEX_INITIALIZER;')
935 g.sb.writeln('')
936
937 // Function pointer globals (initialized to impl_live_ versions)
938 for lf in g.live_fns {
939 if lf.params.len > 0 {
940 g.sb.writeln('static ${lf.ret_type} (*__live_ptr_${lf.c_name})(${lf.params}) = &impl_live_${lf.c_name};')
941 } else {
942 g.sb.writeln('static ${lf.ret_type} (*__live_ptr_${lf.c_name})(void) = &impl_live_${lf.c_name};')
943 }
944 }
945 g.sb.writeln('')
946
947 // Wrapper functions that call through pointers with mutex protection
948 for lf in g.live_fns {
949 params := if lf.params.len > 0 { lf.params } else { 'void' }
950 g.sb.writeln('${lf.ret_type} ${lf.c_name}(${params}) {')
951 g.sb.writeln('\tpthread_mutex_lock(&__live_fn_mutex);')
952 if lf.is_void {
953 if lf.args.len > 0 {
954 g.sb.writeln('\t__live_ptr_${lf.c_name}(${lf.args});')
955 } else {
956 g.sb.writeln('\t__live_ptr_${lf.c_name}();')
957 }
958 } else {
959 if lf.args.len > 0 {
960 g.sb.writeln('\t${lf.ret_type} __live_ret = __live_ptr_${lf.c_name}(${lf.args});')
961 } else {
962 g.sb.writeln('\t${lf.ret_type} __live_ret = __live_ptr_${lf.c_name}();')
963 }
964 }
965 g.sb.writeln('\tpthread_mutex_unlock(&__live_fn_mutex);')
966 if !lf.is_void {
967 g.sb.writeln('\treturn __live_ret;')
968 }
969 g.sb.writeln('}')
970 g.sb.writeln('')
971 }
972
973 // v_bind_live_symbols: called after dlopen to update function pointers
974 g.sb.writeln('static void __v_bind_live_symbols(void* __live_lib) {')
975 for lf in g.live_fns {
976 g.sb.writeln('\tvoid* __sym_${lf.c_name} = dlsym(__live_lib, "impl_live_${lf.c_name}");')
977 g.sb.writeln('\tif (__sym_${lf.c_name}) {')
978 g.sb.writeln('\t\t__live_ptr_${lf.c_name} = __sym_${lf.c_name};')
979 g.sb.writeln('\t}')
980 }
981 g.sb.writeln('}')
982 g.sb.writeln('')
983
984 // Background reloader thread
985 source_file := g.live_source_file
986 dylib_path := '/tmp/_v2_live_reload.dylib'
987 // c_path := '/tmp/_v2_live_reload.c'
988 // Resolve absolute path to v2 binary at compile time
989 v2_path := if g.pref != unsafe { nil } && g.pref.vroot.len > 0 {
990 g.pref.vroot + '/cmd/v2/v2'
991 } else {
992 './v2'
993 }
994 g.sb.writeln('static void* __v_live_reloader_thread(void* __arg) {')
995 g.sb.writeln('\t(void)__arg;')
996 g.sb.writeln('\tchar* __live_src = "${source_file}";')
997 g.sb.writeln('\tlong long __live_last_mtime = 0;')
998 g.sb.writeln('\tstruct stat __live_st;')
999 g.sb.writeln('\t// Get initial mtime')
1000 g.sb.writeln('\tif (stat(__live_src, &__live_st) == 0) {')
1001 g.sb.writeln('\t\t__live_last_mtime = __live_st.st_mtimespec.tv_sec;')
1002 g.sb.writeln('\t}')
1003 g.sb.writeln('\tint __live_reload_count = 0;')
1004 g.sb.writeln('\twhile (1) {')
1005 g.sb.writeln('\t\tusleep(250000); // 250ms')
1006 g.sb.writeln('\t\tif (stat(__live_src, &__live_st) != 0) continue;')
1007 g.sb.writeln('\t\tlong long __live_new_mtime = __live_st.st_mtimespec.tv_sec;')
1008 g.sb.writeln('\t\tif (__live_new_mtime <= __live_last_mtime) continue;')
1009 g.sb.writeln('\t\t__live_last_mtime = __live_new_mtime;')
1010 g.sb.writeln('\t\tprintf("[live] source changed, recompiling...\\n");')
1011 g.sb.writeln('\t\tfflush(stdout);')
1012
1013 // Recompile V source directly to shared library
1014 g.sb.writeln('\t\tint __live_rc = system("${v2_path} -backend cleanc -gc none -nocache -shared -o ${dylib_path} ${source_file} 2>/dev/null");')
1015 g.sb.writeln('\t\tif (__live_rc != 0) {')
1016 g.sb.writeln('\t\t\tprintf("[live] recompilation failed (exit %d)\\n", __live_rc);')
1017 g.sb.writeln('\t\t\tfflush(stdout);')
1018 g.sb.writeln('\t\t\tcontinue;')
1019 g.sb.writeln('\t\t}')
1020
1021 // Step 3: dlopen and rebind
1022 g.sb.writeln('\t\tvoid* __live_lib = dlopen("${dylib_path}", RTLD_NOW | RTLD_LOCAL);')
1023 g.sb.writeln('\t\tif (!__live_lib) {')
1024 g.sb.writeln('\t\t\tprintf("[live] dlopen failed: %s\\n", dlerror());')
1025 g.sb.writeln('\t\t\tfflush(stdout);')
1026 g.sb.writeln('\t\t\tcontinue;')
1027 g.sb.writeln('\t\t}')
1028 g.sb.writeln('\t\tpthread_mutex_lock(&__live_fn_mutex);')
1029 g.sb.writeln('\t\t__v_bind_live_symbols(__live_lib);')
1030 g.sb.writeln('\t\tpthread_mutex_unlock(&__live_fn_mutex);')
1031 g.sb.writeln('\t\t__live_reload_count++;')
1032 g.sb.writeln('\t\tprintf("[live] reload #%d complete\\n", __live_reload_count);')
1033 g.sb.writeln('\t\tfflush(stdout);')
1034 g.sb.writeln('\t}')
1035 g.sb.writeln('\treturn 0;')
1036 g.sb.writeln('}')
1037 g.sb.writeln('')
1038
1039 // __v_live_init: called from main() to start the reloader thread
1040 g.sb.writeln('void __v_live_init(void) {')
1041 g.sb.writeln('\tpthread_t __live_thread;')
1042 g.sb.writeln('\tpthread_create(&__live_thread, 0, __v_live_reloader_thread, 0);')
1043 g.sb.writeln('\tpthread_detach(__live_thread);')
1044 g.sb.writeln('}')
1045 g.sb.writeln('')
1046 g.sb.writeln('// ===== end @[live] infrastructure =====')
1047 g.sb.writeln('')
1048}
1049
1050fn should_keep_builtin_map_decl(decl ast.FnDecl) bool {
1051 base_keep := decl.name in ['new_map', 'move', 'clear', 'key_to_index', 'meta_less',
1052 'meta_greater', 'ensure_extra_metas', 'ensure_extra_metas_grow', 'set', 'expand', 'rehash',
1053 'reserve', 'cached_rehash', 'get_and_set', 'get', 'get_check', 'exists', 'delete', 'keys',
1054 'values', 'clone', 'free', 'key', 'value', 'has_index', 'zeros_to_end', 'new_dense_array']
1055 return base_keep || decl.name.starts_with('map_eq_') || decl.name.starts_with('map_clone_')
1056 || decl.name.starts_with('map_free_')
1057}
1058
1059fn should_keep_builtin_string_decl(decl ast.FnDecl) bool {
1060 return decl.name in ['eq', 'plus', 'plus_two', 'substr', 'substr_unsafe', 'repeat', 'free',
1061 'vstring', 'vstring_with_len', 'vstring_literal', 'vstring_literal_with_len', 'runes',
1062 'join', 'compare_strings', 'compare_strings_by_len', 'compare_lower_strings']
1063}
1064
1065fn should_always_emit_for_markused(path string) bool {
1066 if path.ends_with('.vh') {
1067 return true
1068 }
1069 return is_builtin_runtime_keep_file(path)
1070}
1071
1072fn is_builtin_runtime_keep_file(path string) bool {
1073 normalized := path.replace('\\', '/')
1074 return normalized.ends_with('vlib/builtin/map.c.v')
1075 || normalized.ends_with('vlib/builtin/builtin.c.v')
1076 || normalized.ends_with('vlib/builtin/builtin.v')
1077 || normalized.ends_with('vlib/builtin/cfns_wrapper.c.v')
1078 || normalized.ends_with('vlib/builtin/allocation.c.v')
1079 || normalized.ends_with('vlib/builtin/prealloc.c.v')
1080 || normalized.ends_with('vlib/builtin/panicing.c.v')
1081 || normalized.ends_with('vlib/builtin/chan_option_result.v')
1082 || normalized.ends_with('vlib/builtin/int.v') || normalized.ends_with('vlib/builtin/rune.v')
1083 || normalized.ends_with('vlib/builtin/float.c.v')
1084 || normalized.ends_with('vlib/builtin/utf8.v')
1085 || normalized.ends_with('vlib/builtin/utf8.c.v')
1086 || normalized.ends_with('vlib/strings/builder.c.v')
1087 || normalized.ends_with('vlib/strconv/utilities.v')
1088 || normalized.ends_with('vlib/strconv/utilities.c.v')
1089 || normalized.ends_with('vlib/strconv/ftoa.c.v')
1090 || normalized.ends_with('vlib/strconv/f32_str.c.v')
1091 || normalized.ends_with('vlib/strconv/f64_str.c.v')
1092 || normalized.ends_with('vlib/math/bits/bits.v')
1093 || normalized.ends_with('vlib/math/bits/bits.c.v')
1094 || normalized.ends_with('vlib/sokol/memory/memory.c.v')
1095}
1096
1097fn (g &Gen) should_emit_module(module_name string) bool {
1098 if g.emit_modules.len == 0 {
1099 return true
1100 }
1101 return module_name in g.emit_modules
1102}
1103
1104fn (g &Gen) should_emit_current_file() bool {
1105 if !g.should_emit_module(g.cur_module) {
1106 return false
1107 }
1108 if g.emit_files.len == 0 {
1109 return true
1110 }
1111 return os.norm_path(g.cur_file_name) in g.emit_files
1112 || os.norm_path(os.abs_path(g.cur_file_name)) in g.emit_files
1113}
1114
1115fn (g &Gen) cgen_stats_enabled() bool {
1116 return g.pref != unsafe { nil } && g.pref.stats
1117}
1118
1119fn (g &Gen) cgen_stats_scope_label() string {
1120 if g.cache_bundle_name.len > 0 {
1121 return 'cache:${g.cache_bundle_name}'
1122 }
1123 if g.emit_modules.len == 0 {
1124 return 'full'
1125 }
1126 if g.emit_files.len > 0 {
1127 return 'files:${g.emit_files.len}'
1128 }
1129 if g.emit_modules.len == 1 && 'main' in g.emit_modules {
1130 return 'main'
1131 }
1132 return 'modules:${g.emit_modules.len}'
1133}
1134
1135fn (g &Gen) print_cgen_step_time(stats_enabled bool, scope string, step string, elapsed time.Duration) {
1136 if !stats_enabled {
1137 return
1138 }
1139 println(' - C Gen/${scope} ${step}: ${elapsed.milliseconds()}ms')
1140}
1141
1142fn (g &Gen) mark_cgen_step(stats_enabled bool, scope string, mut sw time.StopWatch, stage_start time.Duration, step string) time.Duration {
1143 if !stats_enabled {
1144 g.print_cgen_mem(step)
1145 return stage_start
1146 }
1147 now := sw.elapsed()
1148 g.print_cgen_step_time(true, scope, step, time.Duration(now - stage_start))
1149 g.print_cgen_mem(step)
1150 return now
1151}
1152
1153// gen generates C source from the transformed AST files (sequential).
1154pub fn (mut g Gen) gen() string {
1155 g.gen_passes_1_to_4()
1156 g.gen_pass5()
1157 return g.gen_finalize()
1158}
1159
1160// gen_passes_1_to_4 runs setup and passes 1-4 (types, structs, constants, forward declarations).
1161pub fn (mut g Gen) gen_passes_1_to_4() {
1162 stats_enabled := g.cgen_stats_enabled()
1163 stats_scope := g.cgen_stats_scope_label()
1164 mut stats_sw := time.new_stopwatch()
1165 mut stage_start := stats_sw.elapsed()
1166
1167 g.write_preamble()
1168 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1169 'setup.preamble')
1170 g.collect_source_module_names()
1171 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1172 'setup.source_module_names')
1173 g.collect_typedef_c_types()
1174 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1175 'setup.typedef_c_types')
1176 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1177 'setup.generic_fn_decl_index')
1178 if g.has_generic_setup_snapshot {
1179 g.apply_generic_setup_snapshot()
1180 }
1181 g.collect_generic_struct_bindings()
1182 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1183 'setup.generic_struct_bindings')
1184 g.collect_module_type_names()
1185 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1186 'setup.module_type_names')
1187 g.collect_emit_file_indexes()
1188 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1189 'setup.emit_file_indexes')
1190 g.collect_runtime_aliases()
1191 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1192 'setup.runtime_aliases')
1193 // V2 does not fully monomorphize generic structs yet, so the unsuffixed
1194 // stdatomic.AtomicVal body has to use one concrete storage type. Keep it on
1195 // `int`: the generated stdatomic receiver methods are also pinned to `int`,
1196 // which keeps atomic counters on a supported add/sub path while bool flags
1197 // still store/load through C's scalar conversions.
1198 g.generic_struct_bindings['stdatomic__AtomicVal'] = {
1199 'T': types.Type(types.int_)
1200 }
1201 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1202 'setup.discover_generic_specs')
1203 g.collect_force_emit_sort_fns()
1204 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1205 'setup.force_emit_sort_fns')
1206 g.collect_fn_signatures_to_fixed_point()
1207 g.build_v_method_return_index()
1208 g.build_ierror_base_index()
1209 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1210 'setup.fn_signatures')
1211 g.collect_c_file_fn_keys()
1212 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1213 'setup.c_file_fn_keys')
1214 g.collect_runtime_const_targets()
1215 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1216 'setup.runtime_const_targets')
1217 g.register_builder_methods()
1218 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1219 'setup.register_builder_methods')
1220 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'setup')
1221
1222 g.collect_global_storage_names()
1223 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1224 'collect globals')
1225
1226 // Pass 1: Forward declarations for all structs/unions/sumtypes/interfaces (needed for mutual references)
1227 g.emit_pass1_forward_decls()
1228 g.sb.writeln('')
1229 g.emit_runtime_aliases()
1230 g.sb.writeln('')
1231 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1232 'pass 1 forward decls')
1233
1234 // Pass 2: Emit type declarations in dependency-safe buckets.
1235 // Emit enums first, then type aliases/sum types.
1236 // Interface bodies are emitted later, after structs/tuple aliases are available.
1237 g.emit_pass2_type_decls()
1238 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1239 'pass 2 type declarations')
1240
1241 // Emit pointer element typedefs (e.g. 'typedef Color* Colorptr;') now that
1242 // enums and type aliases have been defined.
1243 g.emit_pointer_typedefs()
1244 // Fixed arrays over aliases can be emitted once the aliases from pass 2 exist.
1245 g.emit_deferred_fixed_array_aliases()
1246
1247 // Pass 3: Full struct definitions (use named struct/union to match forward decls)
1248 // Collect all struct decls, then emit in dependency order
1249 all_structs, all_interfaces := g.collect_struct_and_interface_infos()
1250 // Emit structs with only primitive/resolved fields first, then the rest.
1251 // Interleave option/result wrapper emission as soon as their payload types are complete.
1252 // Also emit fixed array typedefs as soon as their element types are defined.
1253 // Repeat until no more progress (simple topo sort with wrapper side-effects).
1254 for info in all_structs {
1255 g.set_struct_info_context(info)
1256 if g.struct_is_leaf(info.decl) {
1257 g.gen_struct_decl(info.decl)
1258 }
1259 }
1260 // Emit fixed array typedefs whose element types are now defined (leaf structs).
1261 g.emit_deferred_fixed_array_aliases()
1262 for _ in 0 .. (all_structs.len * 2) {
1263 mut progressed := false
1264 for info in all_structs {
1265 g.set_struct_info_context(info)
1266 name := g.get_struct_name(info.decl)
1267 body_key := 'body_${name}'
1268 if body_key in g.emitted_types {
1269 continue
1270 }
1271 // Check if all field types are already defined
1272 if g.struct_fields_resolved(info.decl) {
1273 g.gen_struct_decl(info.decl)
1274 progressed = true
1275 }
1276 }
1277 if g.emit_ready_option_result_structs() {
1278 progressed = true
1279 }
1280 // Emit fixed array typedefs whose element types are now resolved.
1281 g.emit_deferred_fixed_array_aliases()
1282 if !progressed {
1283 break
1284 }
1285 }
1286 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1287 'pass 3 struct definitions')
1288
1289 // Pass 3.1: Emit deferred (non-primitive) fixed array typedefs now that struct defs exist
1290 g.emit_deferred_fixed_array_aliases()
1291 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1292 'pass 3.1 fixed arrays')
1293
1294 // Pass 3.25: Tuple aliases (multiple-return lowering support)
1295 g.emit_tuple_aliases()
1296 if g.tuple_aliases.len > 0 {
1297 g.sb.writeln('')
1298 }
1299 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1300 'pass 3.25 tuple aliases')
1301
1302 // Pass 3.3: Emit interface bodies after structs and tuple aliases.
1303 for _ in 0 .. (all_interfaces.len * 2) {
1304 mut progressed := false
1305 for info in all_interfaces {
1306 g.set_interface_info_context(info)
1307 name := g.get_interface_name(info.decl)
1308 body_key := 'body_${name}'
1309 if body_key in g.emitted_types {
1310 continue
1311 }
1312 if g.interface_fields_resolved(info.decl) {
1313 g.gen_interface_decl(info.decl)
1314 progressed = true
1315 }
1316 }
1317 if !progressed {
1318 break
1319 }
1320 }
1321 // Emit any remaining interfaces before retrying structs/wrappers. This keeps
1322 // option/result wrappers for interface payloads available before by-value users.
1323 for info in all_interfaces {
1324 g.set_interface_info_context(info)
1325 g.gen_interface_decl(info.decl)
1326 }
1327 // Retry any structs that were waiting on interface bodies.
1328 for _ in 0 .. all_structs.len {
1329 mut progressed := false
1330 for info in all_structs {
1331 g.set_struct_info_context(info)
1332 name := g.get_struct_name(info.decl)
1333 body_key := 'body_${name}'
1334 if body_key in g.emitted_types {
1335 continue
1336 }
1337 if g.struct_fields_resolved(info.decl) {
1338 g.gen_struct_decl(info.decl)
1339 progressed = true
1340 }
1341 }
1342 if g.emit_ready_option_result_structs() {
1343 progressed = true
1344 }
1345 g.emit_deferred_fixed_array_aliases()
1346 if !progressed {
1347 break
1348 }
1349 }
1350 // Emit any remaining structs as a last resort for cyclic declarations.
1351 for info in all_structs {
1352 g.set_struct_info_context(info)
1353 g.gen_struct_decl(info.decl)
1354 }
1355 _ = g.emit_ready_option_result_structs()
1356 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1357 'pass 3.3 interfaces')
1358
1359 // Pass 3.4: Emit option/result struct definitions (needs IError + tuple types defined)
1360 g.emit_option_result_structs()
1361 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1362 'pass 3.4 option/result structs')
1363
1364 // Pass 3.45: Retry tuple aliases now that interface bodies and wrapper
1365 // payload types have been emitted.
1366 g.emit_tuple_aliases()
1367 if g.tuple_aliases.len > 0 {
1368 g.sb.writeln('')
1369 }
1370 g.emit_option_result_structs()
1371 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1372 'pass 3.45 late tuple aliases')
1373
1374 // Pass 3.5: Wrapper structs for functions returning fixed arrays.
1375 g.emit_fixed_array_return_wrappers()
1376 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1377 'pass 3.5 fixed-array return wrappers')
1378
1379 // Recursive array equality helper for nested arrays and string arrays.
1380 // In cached-core builds, the body lives in the builtin cache unit. The
1381 // prototype is emitted here from structured generator state; single-TU
1382 // builds also emit the body directly.
1383 g.sb.writeln('bool string__eq(string a, string b);')
1384 g.sb.writeln('bool __v2_array_eq(array a, array b);')
1385 if g.should_emit_module('builtin') {
1386 g.sb.writeln('bool __v2_array_eq(array a, array b) {')
1387 g.sb.writeln(' if (a.len != b.len) return false;')
1388 g.sb.writeln(' if (a.len == 0) return true;')
1389 g.sb.writeln(' if (a.element_size != b.element_size) return false;')
1390 g.sb.writeln(' if (a.element_size == sizeof(array)) {')
1391 g.sb.writeln(' for (int i = 0; i < a.len; i++) {')
1392 g.sb.writeln(' if (!__v2_array_eq(((array*)a.data)[i], ((array*)b.data)[i])) return false;')
1393 g.sb.writeln(' }')
1394 g.sb.writeln(' return true;')
1395 g.sb.writeln(' }')
1396 g.sb.writeln(' if (a.element_size == sizeof(string)) {')
1397 g.sb.writeln(' for (int i = 0; i < a.len; i++) {')
1398 g.sb.writeln(' if (!string__eq(((string*)a.data)[i], ((string*)b.data)[i])) return false;')
1399 g.sb.writeln(' }')
1400 g.sb.writeln(' return true;')
1401 g.sb.writeln(' }')
1402 g.sb.writeln(' return memcmp(a.data, b.data, a.len * a.element_size) == 0;')
1403 g.sb.writeln('}')
1404 }
1405 g.sb.writeln('')
1406 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1407 'pass 3.6 array helpers')
1408
1409 g.emit_forward_typedefs_for_signature_types()
1410
1411 // Pass 4: Function forward declarations
1412 g.emit_pass4_fn_forward_decls()
1413 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1414 'pass 4 fn forward declarations')
1415
1416 g.sb.writeln('')
1417 if g.has_live_reload_functions() {
1418 g.sb.writeln('void __v_live_init(void);')
1419 }
1420 g.emit_cached_init_call_decls()
1421 g.emit_ierror_wrapper_decls()
1422 g.collect_interface_wrapper_specs()
1423 g.emit_interface_method_wrapper_decls()
1424 g.emit_interface_clone_decls()
1425 g.emit_array_interface_repeat_decls()
1426 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1427 'pass 4 helper declarations')
1428
1429 g.emit_enum_from_string_helpers()
1430 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1431 'pass 4.1 enum from_string')
1432
1433 // Pass 4.5: Emit constants after function forward declarations so const
1434 // initializers can reference functions by name (for example function-pointer
1435 // tables).
1436 g.emit_pass45_const_decls()
1437 g.sb.writeln('')
1438 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1439 'pass 4.5 const declarations')
1440
1441 // Emit deferred Objective-C .m includes now that all types are defined.
1442 g.emit_deferred_m_includes()
1443}
1444
1445fn (mut g Gen) collect_global_storage_names() {
1446 if g.has_flat() {
1447 for i in 0 .. g.flat.files.len {
1448 fc := g.flat.file_cursor(i)
1449 g.set_file_cursor_module(fc)
1450 stmts := fc.stmts()
1451 for j in 0 .. stmts.len() {
1452 stmt := stmts.at(j)
1453 if stmt.kind() != .stmt_global_decl {
1454 continue
1455 }
1456 g.collect_global_storage_names_from_decl(stmt.global_decl(false))
1457 }
1458 }
1459 return
1460 }
1461 for file in g.files {
1462 g.set_file_module(file)
1463 for stmt in file.stmts {
1464 if !stmt_has_valid_data(stmt) {
1465 continue
1466 }
1467 if stmt is ast.GlobalDecl {
1468 g.collect_global_storage_names_from_decl(stmt)
1469 }
1470 }
1471 }
1472}
1473
1474fn (mut g Gen) collect_global_storage_names_from_decl(decl ast.GlobalDecl) {
1475 for field in decl.fields {
1476 storage_name := module_storage_field_c_name(g.cur_module, decl, field)
1477 qualified_name := module_storage_c_name(g.cur_module, if field.name.starts_with('C.') {
1478 field.name.all_after('C.')
1479 } else {
1480 field.name
1481 })
1482 if module_storage_field_is_c_extern(decl, field) {
1483 g.c_extern_module_storage[qualified_name] = storage_name
1484 } else {
1485 g.module_storage_vars[storage_name] = g.cur_module
1486 }
1487 if g.cur_module != '' && g.cur_module != 'main' && g.cur_module != 'builtin' {
1488 g.global_var_modules[field.name] = g.cur_module
1489 }
1490 }
1491}
1492
1493fn (mut g Gen) emit_pass1_forward_decls() {
1494 if g.has_flat() {
1495 for i in 0 .. g.flat.files.len {
1496 fc := g.flat.file_cursor(i)
1497 g.set_file_cursor_module(fc)
1498 stmts := fc.stmts()
1499 for j in 0 .. stmts.len() {
1500 stmt := stmts.at(j)
1501 match stmt.kind() {
1502 .stmt_struct_decl {
1503 decl := stmt.struct_decl()
1504 if decl.language == .c {
1505 continue
1506 }
1507 g.emit_struct_forward_decl(decl)
1508 }
1509 .stmt_type_decl {
1510 decl := stmt.type_decl()
1511 if decl.variants.len > 0 {
1512 g.emit_sum_type_forward_decl(decl)
1513 }
1514 }
1515 .stmt_interface_decl {
1516 g.emit_interface_forward_decl(stmt.interface_decl())
1517 }
1518 else {}
1519 }
1520 }
1521 }
1522 return
1523 }
1524 for file in g.files {
1525 g.set_file_module(file)
1526 for stmt in file.stmts {
1527 if !stmt_has_valid_data(stmt) {
1528 continue
1529 }
1530 if stmt is ast.StructDecl {
1531 if stmt.language == .c {
1532 continue
1533 }
1534 g.emit_struct_forward_decl(stmt)
1535 } else if stmt is ast.TypeDecl {
1536 if stmt.variants.len > 0 {
1537 g.emit_sum_type_forward_decl(stmt)
1538 }
1539 } else if stmt is ast.InterfaceDecl {
1540 g.emit_interface_forward_decl(stmt)
1541 }
1542 }
1543 }
1544}
1545
1546fn (mut g Gen) emit_struct_forward_decl(decl ast.StructDecl) {
1547 name := g.get_struct_name(decl)
1548 runtime_generic_params := if decl.generic_params.len > 0 {
1549 g.generic_struct_runtime_param_names(name, name)
1550 } else {
1551 []string{}
1552 }
1553 if runtime_generic_params.len > 0 && name !in g.generic_struct_bindings
1554 && name !in g.generic_struct_instances {
1555 return
1556 }
1557 if name in g.emitted_types {
1558 return
1559 }
1560 g.emitted_types[name] = true
1561 keyword := if decl.is_union { 'union' } else { 'struct' }
1562 g.sb.writeln('typedef ${keyword} ${name} ${name};')
1563 if decl.generic_params.len > 0 {
1564 instances := g.generic_struct_instances[name]
1565 for inst in instances {
1566 if inst.c_name == name {
1567 continue
1568 }
1569 if inst.c_name !in g.emitted_types {
1570 g.emitted_types[inst.c_name] = true
1571 g.sb.writeln('typedef ${keyword} ${inst.c_name} ${inst.c_name};')
1572 }
1573 }
1574 }
1575}
1576
1577fn (mut g Gen) emit_sum_type_forward_decl(decl ast.TypeDecl) {
1578 name := g.get_type_decl_name(decl)
1579 if name !in g.emitted_types {
1580 g.emitted_types[name] = true
1581 g.sb.writeln('typedef struct ${name} ${name};')
1582 }
1583}
1584
1585fn (mut g Gen) emit_interface_forward_decl(decl ast.InterfaceDecl) {
1586 name := g.get_interface_name(decl)
1587 if name !in g.emitted_types {
1588 g.emitted_types[name] = true
1589 g.sb.writeln('typedef struct ${name} ${name};')
1590 }
1591}
1592
1593fn (mut g Gen) emit_pass2_type_decls() {
1594 if g.has_flat() {
1595 g.emit_pass2_type_decls_flat()
1596 return
1597 }
1598 for file in g.files {
1599 g.set_file_module(file)
1600 for stmt in file.stmts {
1601 if !stmt_has_valid_data(stmt) {
1602 continue
1603 }
1604 if stmt is ast.EnumDecl {
1605 g.gen_enum_decl(stmt)
1606 }
1607 }
1608 }
1609 g.prescan_type_aliases_and_interfaces()
1610 g.emit_tuple_aliases()
1611 if g.tuple_aliases.len > 0 {
1612 g.sb.writeln('')
1613 }
1614 for file in g.files {
1615 g.set_file_module(file)
1616 for stmt in file.stmts {
1617 if !stmt_has_valid_data(stmt) {
1618 continue
1619 }
1620 if stmt is ast.TypeDecl && stmt.variants.len == 0 && stmt.base_type !is ast.EmptyExpr
1621 && !type_decl_base_is_fn_type(stmt) {
1622 g.gen_type_alias(stmt)
1623 }
1624 }
1625 }
1626 for file in g.files {
1627 g.set_file_module(file)
1628 for stmt in file.stmts {
1629 if !stmt_has_valid_data(stmt) {
1630 continue
1631 }
1632 if stmt is ast.TypeDecl {
1633 if stmt.variants.len == 0 && stmt.base_type !is ast.EmptyExpr
1634 && type_decl_base_is_fn_type(stmt) {
1635 g.gen_type_alias(stmt)
1636 } else if stmt.variants.len > 0 {
1637 g.gen_sum_type_decl(stmt)
1638 }
1639 }
1640 }
1641 }
1642}
1643
1644fn (mut g Gen) emit_pass2_type_decls_flat() {
1645 for i in 0 .. g.flat.files.len {
1646 fc := g.flat.file_cursor(i)
1647 g.set_file_cursor_module(fc)
1648 stmts := fc.stmts()
1649 for j in 0 .. stmts.len() {
1650 stmt := stmts.at(j)
1651 if stmt.kind() == .stmt_enum_decl {
1652 g.gen_enum_decl(stmt.enum_decl(true))
1653 }
1654 }
1655 }
1656 g.prescan_type_aliases_and_interfaces()
1657 g.emit_tuple_aliases()
1658 if g.tuple_aliases.len > 0 {
1659 g.sb.writeln('')
1660 }
1661 for i in 0 .. g.flat.files.len {
1662 fc := g.flat.file_cursor(i)
1663 g.set_file_cursor_module(fc)
1664 stmts := fc.stmts()
1665 for j in 0 .. stmts.len() {
1666 stmt := stmts.at(j)
1667 if stmt.kind() != .stmt_type_decl {
1668 continue
1669 }
1670 decl := stmt.type_decl()
1671 if decl.variants.len == 0 && decl.base_type !is ast.EmptyExpr
1672 && !type_decl_base_is_fn_type(decl) {
1673 g.gen_type_alias(decl)
1674 }
1675 }
1676 }
1677 for i in 0 .. g.flat.files.len {
1678 fc := g.flat.file_cursor(i)
1679 g.set_file_cursor_module(fc)
1680 stmts := fc.stmts()
1681 for j in 0 .. stmts.len() {
1682 stmt := stmts.at(j)
1683 if stmt.kind() != .stmt_type_decl {
1684 continue
1685 }
1686 decl := stmt.type_decl()
1687 if decl.variants.len == 0 && decl.base_type !is ast.EmptyExpr
1688 && type_decl_base_is_fn_type(decl) {
1689 g.gen_type_alias(decl)
1690 } else if decl.variants.len > 0 {
1691 g.gen_sum_type_decl(decl)
1692 }
1693 }
1694 }
1695}
1696
1697fn (mut g Gen) prescan_type_aliases_and_interfaces() {
1698 if g.has_flat() {
1699 for i in 0 .. g.flat.files.len {
1700 fc := g.flat.file_cursor(i)
1701 g.set_file_cursor_module(fc)
1702 stmts := fc.stmts()
1703 for j in 0 .. stmts.len() {
1704 stmt := stmts.at(j)
1705 match stmt.kind() {
1706 .stmt_type_decl {
1707 decl := stmt.type_decl()
1708 if decl.base_type !is ast.EmptyExpr {
1709 _ = g.expr_type_to_c(decl.base_type)
1710 }
1711 }
1712 .stmt_interface_decl {
1713 decl := stmt.interface_decl()
1714 for field in decl.fields {
1715 _ = g.interface_method_info(field)
1716 }
1717 }
1718 else {}
1719 }
1720 }
1721 }
1722 return
1723 }
1724 for file in g.files {
1725 g.set_file_module(file)
1726 for stmt in file.stmts {
1727 if !stmt_has_valid_data(stmt) {
1728 continue
1729 }
1730 if stmt is ast.TypeDecl {
1731 if stmt.base_type !is ast.EmptyExpr {
1732 _ = g.expr_type_to_c(stmt.base_type)
1733 }
1734 } else if stmt is ast.InterfaceDecl {
1735 for field in stmt.fields {
1736 _ = g.interface_method_info(field)
1737 }
1738 }
1739 }
1740 }
1741}
1742
1743fn (mut g Gen) collect_struct_and_interface_infos() ([]StructDeclInfo, []InterfaceDeclInfo) {
1744 mut all_structs := []StructDeclInfo{}
1745 mut all_interfaces := []InterfaceDeclInfo{}
1746 if g.has_flat() {
1747 for i in 0 .. g.flat.files.len {
1748 fc := g.flat.file_cursor(i)
1749 g.set_file_cursor_module(fc)
1750 stmts := fc.stmts()
1751 for j in 0 .. stmts.len() {
1752 stmt := stmts.at(j)
1753 match stmt.kind() {
1754 .stmt_struct_decl {
1755 decl := stmt.struct_decl()
1756 if decl.language == .c {
1757 continue
1758 }
1759 all_structs << StructDeclInfo{
1760 decl: decl
1761 mod: g.cur_module
1762 file_name: fc.name()
1763 }
1764 }
1765 .stmt_interface_decl {
1766 decl := stmt.interface_decl()
1767 g.collect_interface_decl_info(decl, fc.name(), mut all_interfaces)
1768 }
1769 else {}
1770 }
1771 }
1772 }
1773 return all_structs, all_interfaces
1774 }
1775 for file in g.files {
1776 g.set_file_module(file)
1777 for stmt in file.stmts {
1778 if !stmt_has_valid_data(stmt) {
1779 continue
1780 }
1781 if stmt is ast.StructDecl {
1782 if stmt.language == .c {
1783 continue
1784 }
1785 all_structs << StructDeclInfo{
1786 decl: stmt
1787 mod: g.cur_module
1788 file_name: file.name
1789 }
1790 } else if stmt is ast.InterfaceDecl {
1791 g.collect_interface_decl_info(stmt, file.name, mut all_interfaces)
1792 }
1793 }
1794 }
1795 return all_structs, all_interfaces
1796}
1797
1798fn (mut g Gen) collect_interface_decl_info(decl ast.InterfaceDecl, file_name string, mut all_interfaces []InterfaceDeclInfo) {
1799 for field in decl.fields {
1800 _ = g.interface_method_info(field)
1801 }
1802 iface_c_name := if g.cur_module != '' && g.cur_module != 'builtin' {
1803 '${g.cur_module}__${decl.name}'
1804 } else {
1805 decl.name
1806 }
1807 g.interface_decls[iface_c_name] = decl
1808 all_interfaces << InterfaceDeclInfo{
1809 decl: decl
1810 mod: g.cur_module
1811 file_name: file_name
1812 }
1813}
1814
1815fn (mut g Gen) emit_pass4_fn_forward_decls() {
1816 if g.has_flat() {
1817 for fi in 0 .. g.flat.files.len {
1818 fc := g.flat.file_cursor(fi)
1819 g.set_file_cursor_module(fc)
1820 stmts := fc.stmts()
1821 for j in 0 .. stmts.len() {
1822 stmt := stmts.at(j)
1823 if stmt.kind() != .stmt_fn_decl {
1824 continue
1825 }
1826 g.emit_fn_forward_decl_for_decl(stmt.fn_decl_signature(), stmt.list_at(3).len(), fi)
1827 }
1828 }
1829 return
1830 }
1831 for fi, file in g.files {
1832 g.set_file_module(file)
1833 for stmt in file.stmts {
1834 if !stmt_has_valid_data(stmt) {
1835 continue
1836 }
1837 if stmt is ast.FnDecl {
1838 decl := stmt as ast.FnDecl
1839 g.emit_fn_forward_decl_for_decl(decl, decl.stmts.len, fi)
1840 }
1841 }
1842 }
1843}
1844
1845fn (mut g Gen) emit_fn_forward_decl_for_decl(decl ast.FnDecl, body_len int, file_idx int) {
1846 if !g.should_emit_fn_decl_cached(g.cur_module, decl) {
1847 return
1848 }
1849 if decl.language == .js {
1850 return
1851 }
1852 if decl.language == .c && body_len == 0 {
1853 // C extern declarations — their prototypes come from #include/#insert headers.
1854 return
1855 }
1856 if g.should_skip_backend_generic_fn(decl) {
1857 return
1858 }
1859 if g.generic_fn_param_names(decl).len > 0 {
1860 gfn_name := g.get_fn_name(decl)
1861 specs := g.generic_fn_specializations_for_emit_scope_with_receiver_bindings(decl)
1862 if specs.len > 0 {
1863 prev_generic_types := g.active_generic_types.clone()
1864 for spec in specs {
1865 g.active_generic_types = spec.generic_types.clone()
1866 g.record_fn_owner_for_current_file(spec.name, file_idx)
1867 g.gen_fn_head_with_name(decl, spec.name)
1868 g.sb.writeln(';')
1869 }
1870 g.active_generic_types = prev_generic_types.clone()
1871 } else {
1872 if gfn_name != '' && generic_fn_has_macro_fallback(decl) {
1873 g.emit_generic_fn_macro(gfn_name, decl)
1874 }
1875 }
1876 return
1877 }
1878 recv_gp := receiver_generic_param_names(decl)
1879 if recv_gp.len > 0 {
1880 all_bindings := g.get_all_receiver_generic_bindings(decl)
1881 if all_bindings.len > 0 {
1882 prev_generic_types := g.active_generic_types.clone()
1883 for bindings in all_bindings {
1884 g.active_generic_types = bindings.clone()
1885 gfn_name := g.get_fn_name(decl)
1886 if gfn_name != '' {
1887 g.record_fn_owner_for_current_file(gfn_name, file_idx)
1888 g.gen_fn_head_with_name(decl, gfn_name)
1889 g.sb.writeln(';')
1890 }
1891 }
1892 g.active_generic_types = prev_generic_types.clone()
1893 return
1894 } else if bindings := g.get_receiver_generic_bindings(decl) {
1895 prev_generic_types := g.active_generic_types.clone()
1896 g.active_generic_types = bindings.clone()
1897 gfn_name := g.get_fn_name(decl)
1898 if gfn_name != '' {
1899 g.record_fn_owner_for_current_file(gfn_name, file_idx)
1900 g.gen_fn_head_with_name(decl, gfn_name)
1901 g.sb.writeln(';')
1902 }
1903 g.active_generic_types = prev_generic_types.clone()
1904 return
1905 }
1906 return
1907 }
1908 fn_name := g.get_fn_name(decl)
1909 if fn_name == '' {
1910 return
1911 }
1912 fn_key := 'fn_${fn_name}'
1913 if g.should_skip_plain_v_fallback_fn(fn_key) {
1914 return
1915 }
1916 if fn_name == 'main' {
1917 g.has_main = true
1918 }
1919 if decl.name.starts_with('test_') && !decl.is_method && decl.typ.params.len == 0 {
1920 g.test_fn_names << fn_name
1921 }
1922 g.record_fn_owner_for_current_file(fn_name, file_idx)
1923 if g.env != unsafe { nil } {
1924 if fn_scope := g.env.get_fn_scope(g.cur_module, fn_name) {
1925 g.cur_fn_scope = fn_scope
1926 }
1927 }
1928 g.gen_fn_head_with_name(decl, fn_name)
1929 g.sb.writeln(';')
1930}
1931
1932fn (mut g Gen) emit_pass45_const_decls() {
1933 if g.has_flat() {
1934 for i in 0 .. g.flat.files.len {
1935 fc := g.flat.file_cursor(i)
1936 g.set_file_cursor_module(fc)
1937 if !g.should_emit_current_file() {
1938 continue
1939 }
1940 stmts := fc.stmts()
1941 for j in 0 .. stmts.len() {
1942 stmt := stmts.at(j)
1943 if stmt.kind() == .stmt_const_decl {
1944 g.gen_const_decl(stmt.const_decl())
1945 }
1946 }
1947 }
1948 return
1949 }
1950 for file in g.files {
1951 g.set_file_module(file)
1952 if !g.should_emit_current_file() {
1953 continue
1954 }
1955 for stmt in file.stmts {
1956 if !stmt_has_valid_data(stmt) {
1957 continue
1958 }
1959 if stmt is ast.ConstDecl {
1960 g.gen_const_decl(stmt)
1961 }
1962 }
1963 }
1964}
1965
1966fn (mut g Gen) record_fn_owner_for_current_file(fn_name string, file_idx int) {
1967 if fn_name == '' || !g.should_emit_current_file() {
1968 return
1969 }
1970 fn_key := 'fn_${fn_name}'
1971 if fn_key !in g.fn_owner_file {
1972 g.fn_owner_file[fn_key] = file_idx
1973 }
1974}
1975
1976fn (mut g Gen) collect_c_file_fn_keys() {
1977 g.c_file_fn_keys = map[string]bool{}
1978 if g.has_flat() {
1979 for i in 0 .. g.flat.files.len {
1980 fc := g.flat.file_cursor(i)
1981 if !fc.name().ends_with('.c.v') {
1982 continue
1983 }
1984 g.set_file_cursor_module(fc)
1985 stmts := fc.stmts()
1986 for j in 0 .. stmts.len() {
1987 stmt := stmts.at(j)
1988 if stmt.kind() != .stmt_fn_decl {
1989 continue
1990 }
1991 decl := stmt.fn_decl_signature()
1992 if decl.language == .js {
1993 continue
1994 }
1995 if decl.language == .c && stmt.list_at(3).len() == 0 {
1996 continue
1997 }
1998 if decl.typ.generic_params.len > 0 {
1999 continue
2000 }
2001 fn_name := g.get_fn_name(decl)
2002 if fn_name == '' {
2003 continue
2004 }
2005 g.c_file_fn_keys['fn_${fn_name}'] = true
2006 }
2007 }
2008 return
2009 }
2010 for file in g.files {
2011 if !file.name.ends_with('.c.v') {
2012 continue
2013 }
2014 g.set_file_module(file)
2015 for stmt in file.stmts {
2016 if stmt is ast.FnDecl {
2017 decl := stmt as ast.FnDecl
2018 if decl.language == .js {
2019 continue
2020 }
2021 if decl.language == .c && decl.stmts.len == 0 {
2022 continue
2023 }
2024 if decl.typ.generic_params.len > 0 {
2025 continue
2026 }
2027 fn_name := g.get_fn_name(decl)
2028 if fn_name == '' {
2029 continue
2030 }
2031 g.c_file_fn_keys['fn_${fn_name}'] = true
2032 }
2033 }
2034 }
2035}
2036
2037fn (g &Gen) should_skip_plain_v_fallback_fn(fn_key string) bool {
2038 return g.cur_file_name.ends_with('.v') && !g.cur_file_name.ends_with('.c.v')
2039 && fn_key in g.c_file_fn_keys
2040}
2041
2042// gen_finalize runs post-pass-5 finalization and returns the complete C source string.
2043pub fn (mut g Gen) gen_finalize() string {
2044 stats_enabled := g.cgen_stats_enabled()
2045 stats_scope := g.cgen_stats_scope_label()
2046 mut stats_sw := time.new_stopwatch()
2047 mut stage_start := stats_sw.elapsed()
2048
2049 // Generate test runner main if this is a test file (has test_ functions but no main)
2050 // Skip when generating cached module sources (cache_bundle_name is set) - main belongs only in the main module source
2051 if !g.has_main && g.test_fn_names.len > 0 && g.cache_bundle_name.len == 0 {
2052 g.sb.writeln('')
2053 g.sb.writeln('int main(int ___argc, char** ___argv) {')
2054 g.sb.writeln('\tg_main_argc = ___argc;')
2055 g.sb.writeln('\tg_main_argv = (void*)___argv;')
2056 for init_call in g.cached_init_calls {
2057 g.sb.writeln('\t${init_call}();')
2058 }
2059 // Runtime constants like builtin.max_int must be initialized before module
2060 // init() functions, since init paths can allocate and use array bounds.
2061 for fn_name, _ in g.fn_return_types {
2062 if fn_name.contains('__v_init_consts_') && fn_name != '__v_init_consts_main' {
2063 g.sb.writeln('\t${fn_name}();')
2064 }
2065 }
2066 // Call module init() functions and __v_init_consts_main — test files have
2067 // no main() function, so the transformer's injected init calls are not present.
2068 for fn_name, _ in g.fn_return_types {
2069 // Module init functions: MODULE__init (e.g., rand__init)
2070 // Skip methods (Type__method patterns where Type is capitalized)
2071 if fn_name.ends_with('__init') && count_substr_in_string(fn_name, '__') == 1 {
2072 first_char := fn_name[0]
2073 if first_char >= `a` && first_char <= `z` {
2074 if params := g.fn_param_is_ptr[fn_name] {
2075 if params.len == 0 {
2076 g.sb.writeln('\t${fn_name}();')
2077 }
2078 } else {
2079 g.sb.writeln('\t${fn_name}();')
2080 }
2081 }
2082 }
2083 }
2084 if '__v_init_consts_main' in g.fn_return_types {
2085 g.sb.writeln('\t__v_init_consts_main();')
2086 }
2087 g.sb.writeln('\tg_main_argc = ___argc;')
2088 g.sb.writeln('\tg_main_argv = (void*)___argv;')
2089 for test_fn in g.test_fn_names {
2090 msg_run := 'Running test: ${test_fn}...'
2091 msg_ok := ' OK'
2092 g.sb.writeln('\tprintln(${c_static_v_string_expr(msg_run)});')
2093 g.sb.writeln('\t${test_fn}();')
2094 g.sb.writeln('\tprintln(${c_static_v_string_expr(msg_ok)});')
2095 }
2096 msg_all := 'All ${g.test_fn_names.len} tests passed.'
2097 g.sb.writeln('\tprintln(${c_static_v_string_expr(msg_all)});')
2098 g.sb.writeln('\treturn 0;')
2099 g.sb.writeln('}')
2100 }
2101 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
2102 'finalize test main')
2103
2104 array_contains_specs := g.missing_array_contains_fallback_specs()
2105 late_array_contains_decls := array_contains_fallback_decls(array_contains_specs)
2106 g.emit_array_contains_fallbacks(array_contains_specs)
2107 g.emit_missing_runtime_fallbacks()
2108 g.emit_cached_module_init_function()
2109 g.emit_exported_const_symbols()
2110 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
2111 'finalize fallbacks')
2112
2113 mut out := ''
2114 // Emit deferred str macros for late-discovered generic struct instances.
2115 // At this point fn_return_types is fully populated (pass 4 complete).
2116 for inst_name in g.late_generic_str_instances {
2117 str_fn := '${inst_name}__str'
2118 if str_fn !in g.fn_return_types {
2119 label := '${inst_name}{}'
2120 g.late_struct_defs << '#define ${inst_name}__str(v) ((string){.str = "${label}", .len = ${label.len}, .is_lit = 1})\n#define ${inst_name}_str(v) ${inst_name}__str(v)\n'
2121 }
2122 }
2123 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
2124 'finalize late str macros')
2125 has_late_defs := g.anon_fn_defs.len > 0 || g.spawn_wrapper_defs.len > 0
2126 || g.trampoline_defs.len > 0 || g.late_struct_defs.len > 0
2127 || g.pending_late_body_keys.len > 0
2128 if late_array_contains_decls.len > 0 || has_late_defs {
2129 full := g.sb.str()
2130 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
2131 'finalize snapshot')
2132 mut out_sb := strings.new_builder(full.len + late_array_contains_decls.len + 4096)
2133 unsafe { out_sb.write_ptr(full.str, g.pass5_start_pos) }
2134 if has_late_defs {
2135 // Late-discovered generic struct definitions (discovered during setup/pass 4 codegen)
2136 for def in g.late_struct_defs {
2137 out_sb.write_string(def)
2138 }
2139 // Mark pending late body keys as emitted now that they're in the output.
2140 for key, _ in g.pending_late_body_keys {
2141 g.emitted_types[key] = true
2142 }
2143 g.pending_late_body_keys = map[string]bool{}
2144 // Emit any option/result wrappers that were deferred because their payload
2145 // types were only in late_struct_defs. Temporarily swap sb with out_sb.
2146 g.sb = out_sb
2147 g.emit_option_result_structs()
2148 out_sb = g.sb
2149 g.sb = strings.new_builder(0)
2150 }
2151 if late_array_contains_decls.len > 0 {
2152 out_sb.write_string(late_array_contains_decls)
2153 }
2154 if has_late_defs {
2155 mut seen_spawn_defs := map[string]bool{}
2156 for def in g.spawn_wrapper_defs {
2157 if def in seen_spawn_defs {
2158 continue
2159 }
2160 seen_spawn_defs[def] = true
2161 out_sb.write_string(def)
2162 }
2163 for def in g.anon_fn_defs {
2164 out_sb.write_string(def)
2165 }
2166 for def in g.trampoline_defs {
2167 out_sb.write_string(def)
2168 }
2169 }
2170 if g.pass5_start_pos < full.len {
2171 unsafe { out_sb.write_ptr(full.str + g.pass5_start_pos, full.len - g.pass5_start_pos) }
2172 }
2173 out = out_sb.str()
2174 } else {
2175 out = g.sb.str()
2176 }
2177 _ = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'finalize output')
2178 return out
2179}
2180
2181// gen_pass5 generates Pass 5 (function bodies, globals, etc.) sequentially.
2182fn (mut g Gen) gen_pass5() {
2183 stats_enabled := g.cgen_stats_enabled()
2184 stats_scope := g.cgen_stats_scope_label()
2185 mut stats_sw := time.new_stopwatch()
2186 mut stage_start := stats_sw.elapsed()
2187
2188 g.pass5_start_pos = g.sb.len
2189 g.selector_field_type_cache = map[string]string{}
2190 g.selector_field_type_miss = map[string]bool{}
2191 g.struct_field_lookup_cache = map[string]string{}
2192 g.struct_field_lookup_miss = map[string]bool{}
2193 g.ensure_flat_struct_decl_index()
2194 g.ensure_called_specialized_name_index()
2195 g.collect_force_emit_str_fns()
2196 g.emit_pass5_extern_consts_for_non_emit_files()
2197 g.emit_pass5_extern_globals()
2198 if g.has_flat() {
2199 for i in 0 .. g.flat.files.len {
2200 fc := g.flat.file_cursor(i)
2201 g.set_file_cursor_module(fc)
2202 if g.should_emit_current_file() {
2203 g.gen_file_cursor(fc)
2204 }
2205 }
2206 } else {
2207 for file in g.files {
2208 g.set_file_module(file)
2209 if g.should_emit_current_file() {
2210 g.gen_file(file)
2211 }
2212 }
2213 }
2214 stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
2215 'pass 5 files')
2216 g.gen_pass5_post()
2217 _ = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'pass 5 post')
2218}
2219
2220// gen_pass5_pre runs Pass 5 sequential pre-work: extern globals and extern consts
2221// for non-emitted modules. Returns the list of file indices that need gen_file().
2222pub fn (mut g Gen) gen_pass5_pre() []int {
2223 g.pass5_start_pos = g.sb.len
2224 g.selector_field_type_cache = map[string]string{}
2225 g.selector_field_type_miss = map[string]bool{}
2226 g.struct_field_lookup_cache = map[string]string{}
2227 g.struct_field_lookup_miss = map[string]bool{}
2228 g.ensure_flat_struct_decl_index()
2229 g.ensure_called_specialized_name_index()
2230 g.collect_force_emit_str_fns()
2231 g.emit_pass5_extern_consts_for_non_emit_files()
2232 g.emit_pass5_extern_globals()
2233 // Collect emittable file indices. Also build global_owner_file: assign each
2234 // global to the first file that declares it so parallel workers can avoid
2235 // emitting duplicate definitions.
2236 if g.has_flat() {
2237 return g.collect_pass5_emit_indices_flat()
2238 }
2239 mut emit_indices := []int{cap: g.files.len}
2240 for fi, file in g.files {
2241 g.set_file_module(file)
2242 if g.should_emit_current_file() {
2243 emit_indices << fi
2244 for stmt in file.stmts {
2245 if stmt is ast.GlobalDecl {
2246 for field in stmt.fields {
2247 gname := if g.cur_module != '' && g.cur_module != 'main'
2248 && g.cur_module != 'builtin' {
2249 '${g.cur_module}__${field.name}'
2250 } else {
2251 field.name
2252 }
2253 if gname !in g.global_owner_file {
2254 g.global_owner_file[gname] = fi
2255 }
2256 }
2257 }
2258 }
2259 }
2260 }
2261 return emit_indices
2262}
2263
2264fn (mut g Gen) emit_pass5_extern_consts_for_non_emit_files() {
2265 if g.has_flat() {
2266 for i in 0 .. g.flat.files.len {
2267 fc := g.flat.file_cursor(i)
2268 g.set_file_cursor_module(fc)
2269 if g.should_emit_current_file() {
2270 continue
2271 }
2272 stmts := fc.stmts()
2273 for j in 0 .. stmts.len() {
2274 stmt := stmts.at(j)
2275 if stmt.kind() == .stmt_const_decl {
2276 g.gen_const_decl_extern(stmt.const_decl())
2277 }
2278 }
2279 }
2280 return
2281 }
2282 for file in g.files {
2283 g.set_file_module(file)
2284 if !g.should_emit_current_file() {
2285 g.gen_file_extern_consts(file)
2286 }
2287 }
2288}
2289
2290fn (mut g Gen) emit_pass5_extern_globals() {
2291 if g.has_flat() {
2292 for i in 0 .. g.flat.files.len {
2293 fc := g.flat.file_cursor(i)
2294 g.set_file_cursor_module(fc)
2295 stmts := fc.stmts()
2296 for j in 0 .. stmts.len() {
2297 stmt := stmts.at(j)
2298 if stmt.kind() == .stmt_global_decl {
2299 g.gen_global_decl_extern(stmt.global_decl(true))
2300 }
2301 }
2302 }
2303 return
2304 }
2305 for file in g.files {
2306 g.set_file_module(file)
2307 g.gen_file_extern_globals(file)
2308 }
2309}
2310
2311fn (mut g Gen) collect_pass5_emit_indices_flat() []int {
2312 mut emit_indices := []int{cap: g.flat.files.len}
2313 for fi in 0 .. g.flat.files.len {
2314 fc := g.flat.file_cursor(fi)
2315 g.set_file_cursor_module(fc)
2316 if !g.should_emit_current_file() {
2317 continue
2318 }
2319 emit_indices << fi
2320 stmts := fc.stmts()
2321 for j in 0 .. stmts.len() {
2322 stmt := stmts.at(j)
2323 if stmt.kind() != .stmt_global_decl {
2324 continue
2325 }
2326 decl := stmt.global_decl(false)
2327 for field in decl.fields {
2328 gname := if g.cur_module != '' && g.cur_module != 'main'
2329 && g.cur_module != 'builtin' {
2330 '${g.cur_module}__${field.name}'
2331 } else {
2332 field.name
2333 }
2334 if gname !in g.global_owner_file {
2335 g.global_owner_file[gname] = fi
2336 }
2337 }
2338 }
2339 }
2340 return emit_indices
2341}
2342
2343// gen_pass5_post runs post-Pass-5 finalization (interface wrappers, live reload, map helpers).
2344pub fn (mut g Gen) gen_pass5_post() {
2345 g.emit_forced_helpers_from_non_emit_files()
2346 g.emit_needed_ierror_wrappers()
2347 g.emit_needed_interface_method_wrappers()
2348 g.emit_interface_clone_helpers()
2349 g.emit_option_string_clone_helper()
2350 g.emit_array_interface_repeat_helpers()
2351 g.emit_live_reload_infrastructure()
2352 if g.cache_bundle_name.len == 0 {
2353 g.emit_map_str_functions()
2354 g.emit_map_eq_functions()
2355 }
2356}
2357
2358fn (mut g Gen) emit_forced_helpers_from_non_emit_files() {
2359 if g.emit_files.len == 0 || g.force_emit_fn_names.len == 0 {
2360 return
2361 }
2362 old_file := g.cur_file_name
2363 old_module := g.cur_module
2364 old_import_modules := g.cur_import_modules.clone()
2365 mut emitted_file_fns := map[string]bool{}
2366 if g.has_flat() {
2367 for i in 0 .. g.flat.files.len {
2368 fc := g.flat.file_cursor(i)
2369 g.set_file_cursor_module(fc)
2370 if !g.should_emit_current_file() {
2371 continue
2372 }
2373 stmts := fc.stmts()
2374 for j in 0 .. stmts.len() {
2375 stmt := stmts.at(j)
2376 if stmt.kind() == .stmt_fn_decl && stmt.name().starts_with('__sort_cmp_') {
2377 decl := stmt.fn_decl_signature()
2378 fn_name := g.get_fn_name(decl)
2379 if fn_name != '' {
2380 emitted_file_fns[fn_name] = true
2381 }
2382 }
2383 }
2384 }
2385 mut emitted := map[string]bool{}
2386 for i in 0 .. g.flat.files.len {
2387 fc := g.flat.file_cursor(i)
2388 g.set_file_cursor_module(fc)
2389 if fc.name().ends_with('.vh') {
2390 continue
2391 }
2392 stmts := fc.stmts()
2393 for j in 0 .. stmts.len() {
2394 stmt := stmts.at(j)
2395 if stmt.kind() != .stmt_fn_decl || !stmt.name().starts_with('__sort_cmp_') {
2396 continue
2397 }
2398 decl_sig := stmt.fn_decl_signature()
2399 fn_name := g.get_fn_name(decl_sig)
2400 if fn_name == '' || fn_name !in g.force_emit_fn_names || fn_name in emitted {
2401 continue
2402 }
2403 if fn_name in emitted_file_fns {
2404 continue
2405 }
2406 if 'fn_${fn_name}' in g.fn_owner_file {
2407 continue
2408 }
2409 decl := stmt.fn_decl()
2410 g.gen_fn_decl(decl)
2411 emitted[fn_name] = true
2412 }
2413 }
2414 g.cur_file_name = old_file
2415 g.cur_module = old_module
2416 g.cur_import_modules = old_import_modules.clone()
2417 return
2418 }
2419 for file in g.files {
2420 g.set_file_module(file)
2421 if !g.should_emit_current_file() {
2422 continue
2423 }
2424 for stmt in file.stmts {
2425 if stmt is ast.FnDecl && stmt.name.starts_with('__sort_cmp_') {
2426 fn_name := g.get_fn_name(stmt)
2427 if fn_name != '' {
2428 emitted_file_fns[fn_name] = true
2429 }
2430 }
2431 }
2432 }
2433 mut emitted := map[string]bool{}
2434 for file in g.files {
2435 g.set_file_module(file)
2436 if file.name.ends_with('.vh') {
2437 continue
2438 }
2439 for stmt in file.stmts {
2440 if !stmt_has_valid_data(stmt) {
2441 continue
2442 }
2443 if stmt is ast.FnDecl {
2444 decl := stmt as ast.FnDecl
2445 if !decl.name.starts_with('__sort_cmp_') {
2446 continue
2447 }
2448 fn_name := g.get_fn_name(decl)
2449 if fn_name == '' || fn_name !in g.force_emit_fn_names || fn_name in emitted {
2450 continue
2451 }
2452 if fn_name in emitted_file_fns {
2453 continue
2454 }
2455 if 'fn_${fn_name}' in g.fn_owner_file {
2456 continue
2457 }
2458 g.gen_fn_decl(decl)
2459 emitted[fn_name] = true
2460 }
2461 }
2462 }
2463 g.cur_file_name = old_file
2464 g.cur_module = old_module
2465 g.cur_import_modules = old_import_modules.clone()
2466}
2467
2468fn (mut g Gen) late_generic_fn_specializations(node ast.FnDecl) []GenericFnSpecialization {
2469 generic_params := g.generic_fn_param_names(node)
2470 if generic_params.len == 0 || g.env == unsafe { nil } {
2471 return []GenericFnSpecialization{}
2472 }
2473 mut specs := []GenericFnSpecialization{}
2474 mut seen := map[string]bool{}
2475 for key in g.generic_spec_index[node.name] {
2476 if key !in g.late_generic_specs || !g.generic_key_matches_decl(node, key) {
2477 continue
2478 }
2479 for generic_types in g.late_generic_specs[key] {
2480 mut skip_spec := false
2481 mut runtime_specializable := true
2482 mut normalized_generic_types := generic_types.clone()
2483 for param_name in generic_params {
2484 concrete0 := generic_types[param_name] or {
2485 skip_spec = true
2486 break
2487 }
2488 concrete := normalize_generic_concrete_type(concrete0)
2489 normalized_generic_types[param_name] = concrete
2490 if concrete.name() == 'void' || concrete.name() == param_name
2491 || type_contains_generic_placeholder(concrete)
2492 || !generic_concrete_type_is_runtime_specializable(concrete) {
2493 skip_spec = true
2494 break
2495 }
2496 if !g.generic_concrete_type_is_runtime_specializable(concrete) {
2497 runtime_specializable = false
2498 }
2499 }
2500 if skip_spec
2501 || !g.generic_specialization_belongs_to_emit_modules(normalized_generic_types) {
2502 continue
2503 }
2504 spec_name := g.specialized_fn_name(node, normalized_generic_types)
2505 if spec_name == '' || spec_name in seen {
2506 continue
2507 }
2508 if !runtime_specializable && spec_name !in g.called_fn_names {
2509 continue
2510 }
2511 seen[spec_name] = true
2512 specs << GenericFnSpecialization{
2513 name: spec_name
2514 generic_types: normalized_generic_types.clone()
2515 }
2516 }
2517 }
2518 return specs
2519}
2520
2521fn (mut g Gen) generic_types_from_specialized_fn_name(node ast.FnDecl, fn_name string) ?map[string]types.Type {
2522 generic_params := g.generic_fn_param_names(node)
2523 if generic_params.len == 0 {
2524 return none
2525 }
2526 mut prev_generic_types := g.active_generic_types.move()
2527 g.active_generic_types = map[string]types.Type{}
2528 base_name := g.get_fn_name(node)
2529 g.active_generic_types = prev_generic_types.move()
2530 prefix := '${base_name}_T_'
2531 mut suffix := ''
2532 if fn_name.starts_with(prefix) {
2533 suffix = fn_name[prefix.len..]
2534 } else {
2535 specialized_base := generic_call_base_name_for_specialization(fn_name)
2536 specialized_prefix := '${specialized_base}_T_'
2537 if specialized_fn_decl_base_name(fn_name) != base_name
2538 || !fn_name.starts_with(specialized_prefix) {
2539 return none
2540 }
2541 suffix = fn_name[specialized_prefix.len..]
2542 }
2543 tokens := g.split_specialization_suffix(suffix, generic_params.len) or { return none }
2544 mut generic_types := map[string]types.Type{}
2545 for i, param_name in generic_params {
2546 concrete := g.concrete_type_from_specialization_token(tokens[i])
2547 if type_contains_generic_placeholder(concrete)
2548 || !g.generic_concrete_type_is_runtime_specializable(concrete) {
2549 return none
2550 }
2551 generic_types[param_name] = concrete
2552 }
2553 if g.specialized_fn_name(node, generic_types) != fn_name {
2554 return none
2555 }
2556 return generic_types
2557}
2558
2559fn (mut g Gen) split_specialization_suffix(suffix string, parts int) ?[]string {
2560 if parts <= 0 || suffix == '' {
2561 return none
2562 }
2563 if parts == 1 {
2564 concrete := g.concrete_type_from_specialization_token(suffix)
2565 if type_contains_generic_placeholder(concrete)
2566 || !g.generic_concrete_type_is_runtime_specializable(concrete) {
2567 return none
2568 }
2569 return [suffix]
2570 }
2571 for i := 1; i < suffix.len - 1; i++ {
2572 if suffix[i] != `_` {
2573 continue
2574 }
2575 head := suffix[..i]
2576 concrete := g.concrete_type_from_specialization_token(head)
2577 if type_contains_generic_placeholder(concrete)
2578 || !g.generic_concrete_type_is_runtime_specializable(concrete) {
2579 continue
2580 }
2581 tail := suffix[i + 1..]
2582 if tail_tokens := g.split_specialization_suffix(tail, parts - 1) {
2583 mut tokens := [head]
2584 tokens << tail_tokens
2585 return tokens
2586 }
2587 }
2588 return none
2589}
2590
2591// Pass5WorkItem is one unit of parallel Pass 5 work. A small file is one item
2592// covering the whole file (fn_indices empty => use gen_file). A large file is
2593// split into several items, each owning a contiguous slice of the file's FnDecl
2594// statement indices; only the first slice (emit_globals) emits the file globals.
2595pub struct Pass5WorkItem {
2596pub:
2597 file_idx int
2598 fn_indices []int // empty => emit the whole file (gen_file)
2599 emit_globals bool
2600 cost int
2601}
2602
2603// pass5_split_threshold is the per-item codegen-cost ceiling. Files costing more
2604// than this are split into sub-file items so no single file pins a worker. The
2605// largest single self-host files (ssa/builder.v ~545k, transformer.v ~446k,
2606// gen/cleanc/fn.v ~386k) otherwise serialize the whole parallel phase.
2607const pass5_split_threshold = 100_000
2608
2609// pass5_file_fn_indices returns the statement indices of `file_idx`'s top-level FnDecls.
2610fn (g &Gen) pass5_file_fn_indices(file_idx int) []int {
2611 mut out := []int{}
2612 if g.has_flat() {
2613 if file_idx < 0 || file_idx >= g.flat.files.len {
2614 return out
2615 }
2616 stmts := g.flat.file_cursor(file_idx).stmts()
2617 for i in 0 .. stmts.len() {
2618 if stmts.at(i).kind() == .stmt_fn_decl {
2619 out << i
2620 }
2621 }
2622 return out
2623 }
2624 for i, stmt in g.files[file_idx].stmts {
2625 if stmt is ast.FnDecl {
2626 out << i
2627 }
2628 }
2629 return out
2630}
2631
2632fn (g &Gen) pass5_stmt_cost(file_idx int, stmt_idx int) int {
2633 if g.has_flat() {
2634 if file_idx < 0 || file_idx >= g.flat.files.len {
2635 return 1
2636 }
2637 stmts := g.flat.file_cursor(file_idx).stmts()
2638 if stmt_idx < 0 || stmt_idx >= stmts.len() {
2639 return 1
2640 }
2641 return cleanc_stmt_cursor_codegen_cost(stmts.at(stmt_idx))
2642 }
2643 if file_idx < 0 || file_idx >= g.files.len || stmt_idx < 0
2644 || stmt_idx >= g.files[file_idx].stmts.len {
2645 return 1
2646 }
2647 return cleanc_stmt_codegen_cost(g.files[file_idx].stmts[stmt_idx])
2648}
2649
2650fn (g &Gen) pass5_file_name(file_idx int) string {
2651 if g.has_flat() {
2652 if file_idx < 0 || file_idx >= g.flat.files.len {
2653 return ''
2654 }
2655 return g.flat.file_cursor(file_idx).name()
2656 }
2657 if file_idx < 0 || file_idx >= g.files.len {
2658 return ''
2659 }
2660 return g.files[file_idx].name
2661}
2662
2663// build_pass5_work_items turns the emittable file indices into balanced work
2664// items, splitting any file whose codegen cost exceeds pass5_split_threshold
2665// into contiguous FnDecl-index slices.
2666pub fn (g &Gen) build_pass5_work_items(emit_indices []int) []Pass5WorkItem {
2667 mut items := []Pass5WorkItem{cap: emit_indices.len}
2668 for fi in emit_indices {
2669 file_cost := g.pass5_file_cost(fi)
2670 if file_cost <= pass5_split_threshold {
2671 items << Pass5WorkItem{
2672 file_idx: fi
2673 emit_globals: true
2674 cost: file_cost
2675 }
2676 continue
2677 }
2678 fn_indices := g.pass5_file_fn_indices(fi)
2679 if fn_indices.len <= 1 {
2680 // Nothing to split — a single (giant) function or no functions.
2681 items << Pass5WorkItem{
2682 file_idx: fi
2683 emit_globals: true
2684 cost: file_cost
2685 }
2686 continue
2687 }
2688 // Greedily pack contiguous functions into slices of ~pass5_split_threshold cost.
2689 mut slice_start := 0
2690 mut slice_cost := 0
2691 mut first_slice := true
2692 for k, idx in fn_indices {
2693 fn_cost := g.pass5_stmt_cost(fi, idx)
2694 slice_cost += fn_cost
2695 is_last := k == fn_indices.len - 1
2696 if slice_cost >= pass5_split_threshold || is_last {
2697 items << Pass5WorkItem{
2698 file_idx: fi
2699 fn_indices: fn_indices[slice_start..k + 1]
2700 emit_globals: first_slice
2701 cost: slice_cost
2702 }
2703 first_slice = false
2704 slice_start = k + 1
2705 slice_cost = 0
2706 }
2707 }
2708 }
2709 return items
2710}
2711
2712// gen_pass5_work_items emits each assigned work item. Whole-file items go through
2713// gen_file; split items emit only their FnDecl slice via gen_file_range.
2714pub fn (mut g Gen) gen_pass5_work_items(items []Pass5WorkItem) {
2715 stats_enabled := g.cgen_stats_enabled()
2716 for item in items {
2717 if !stats_enabled {
2718 g.gen_pass5_work_item(item)
2719 continue
2720 }
2721 mut sw := time.new_stopwatch()
2722 g.gen_pass5_work_item(item)
2723 elapsed_ms := sw.elapsed().milliseconds()
2724 if elapsed_ms > 0 {
2725 suffix := if item.fn_indices.len == 0 { '' } else { ' [${item.fn_indices.len}fns]' }
2726 g.pass5_file_times << Pass5FileTime{
2727 file: g.pass5_file_name(item.file_idx) + suffix
2728 ms: elapsed_ms
2729 cost: item.cost
2730 worker_id: g.pass5_worker_id
2731 }
2732 }
2733 }
2734}
2735
2736fn (mut g Gen) gen_pass5_work_item(item Pass5WorkItem) {
2737 if item.fn_indices.len == 0 {
2738 if g.has_flat() {
2739 g.gen_file_cursor(g.flat.file_cursor(item.file_idx))
2740 } else {
2741 g.gen_file(g.files[item.file_idx])
2742 }
2743 return
2744 }
2745 g.explicit_slice_active = true
2746 g.explicit_slice_file = item.file_idx
2747 if g.has_flat() {
2748 g.gen_file_cursor_range(g.flat.file_cursor(item.file_idx), item.fn_indices,
2749 item.emit_globals)
2750 } else {
2751 g.gen_file_range(g.files[item.file_idx], item.fn_indices, item.emit_globals)
2752 }
2753 g.explicit_slice_active = false
2754}
2755
2756// gen_pass5_files generates function bodies for a range of file indices.
2757// Used by parallel dispatch — each worker calls this with its assigned chunk.
2758pub fn (mut g Gen) gen_pass5_files(file_indices []int) {
2759 stats_enabled := g.cgen_stats_enabled()
2760 for fi in file_indices {
2761 file_name := g.pass5_file_name(fi)
2762 if stats_enabled {
2763 mut sw := time.new_stopwatch()
2764 if g.has_flat() {
2765 g.gen_file_cursor(g.flat.file_cursor(fi))
2766 } else {
2767 g.gen_file(g.files[fi])
2768 }
2769 elapsed_ms := sw.elapsed().milliseconds()
2770 if elapsed_ms > 0 {
2771 g.pass5_file_times << Pass5FileTime{
2772 file: file_name
2773 ms: elapsed_ms
2774 cost: g.pass5_file_cost(fi)
2775 worker_id: g.pass5_worker_id
2776 }
2777 }
2778 } else {
2779 if g.has_flat() {
2780 g.gen_file_cursor(g.flat.file_cursor(fi))
2781 } else {
2782 g.gen_file(g.files[fi])
2783 }
2784 }
2785 }
2786}
2787
2788pub fn (g &Gen) print_pass5_file_times(limit int) {
2789 if !g.cgen_stats_enabled() || g.pass5_file_times.len == 0 {
2790 return
2791 }
2792 mut times := g.pass5_file_times.clone()
2793 for i := 1; i < times.len; i++ {
2794 mut j := i
2795 for j > 0 && times[j - 1].ms < times[j].ms {
2796 times[j - 1], times[j] = times[j], times[j - 1]
2797 j--
2798 }
2799 }
2800 stats_scope := g.cgen_stats_scope_label()
2801 n := if times.len < limit { times.len } else { limit }
2802 for item in times[..n] {
2803 println(' - C Gen/${stats_scope} pass 5 file ${item.ms}ms worker=${item.worker_id} cost=${item.cost} ${item.file}')
2804 }
2805}
2806
2807// pass5_file_cost estimates relative codegen work for balancing parallel chunks.
2808pub fn (g &Gen) pass5_file_cost(file_idx int) int {
2809 if g.has_flat() {
2810 if file_idx < 0 || file_idx >= g.flat.files.len {
2811 return 1
2812 }
2813 mut cost := 1
2814 stmts := g.flat.file_cursor(file_idx).stmts()
2815 for i in 0 .. stmts.len() {
2816 cost += cleanc_stmt_cursor_codegen_cost(stmts.at(i))
2817 }
2818 return cost
2819 }
2820 if file_idx < 0 || file_idx >= g.files.len {
2821 return 1
2822 }
2823 mut cost := 1
2824 for stmt in g.files[file_idx].stmts {
2825 cost += cleanc_stmt_codegen_cost(stmt)
2826 }
2827 return cost
2828}
2829
2830fn cleanc_stmts_codegen_cost(stmts []ast.Stmt) int {
2831 mut cost := 0
2832 for stmt in stmts {
2833 cost += cleanc_stmt_codegen_cost(stmt)
2834 }
2835 return cost
2836}
2837
2838fn cleanc_cursor_stmts_codegen_cost(stmts ast.CursorList) int {
2839 mut cost := 0
2840 for i in 0 .. stmts.len() {
2841 cost += cleanc_stmt_cursor_codegen_cost(stmts.at(i))
2842 }
2843 return cost
2844}
2845
2846fn cleanc_cursor_children_expr_codegen_cost(c ast.Cursor, start int) int {
2847 mut cost := 0
2848 for i in start .. c.edge_count() {
2849 cost += cleanc_expr_cursor_codegen_cost(c.edge(i))
2850 }
2851 return cost
2852}
2853
2854fn cleanc_cursor_children_stmt_codegen_cost(c ast.Cursor, start int) int {
2855 mut cost := 0
2856 for i in start .. c.edge_count() {
2857 cost += cleanc_stmt_cursor_codegen_cost(c.edge(i))
2858 }
2859 return cost
2860}
2861
2862fn cleanc_stmt_cursor_codegen_cost(stmt ast.Cursor) int {
2863 if !stmt.is_valid() {
2864 return 1
2865 }
2866 match stmt.kind() {
2867 .stmt_fn_decl {
2868 return 50 + cleanc_cursor_stmts_codegen_cost(stmt.list_at(3))
2869 }
2870 .stmt_assign {
2871 return 12 + cleanc_cursor_children_expr_codegen_cost(stmt, 0)
2872 }
2873 .stmt_expr {
2874 return 4 + cleanc_expr_cursor_codegen_cost(stmt.edge(0))
2875 }
2876 .stmt_return {
2877 return 8 + cleanc_cursor_children_expr_codegen_cost(stmt, 0)
2878 }
2879 .stmt_for {
2880 return 30 + cleanc_stmt_cursor_codegen_cost(stmt.edge(0)) +
2881 cleanc_expr_cursor_codegen_cost(stmt.edge(1)) +
2882 cleanc_stmt_cursor_codegen_cost(stmt.edge(2)) +
2883 cleanc_cursor_children_stmt_codegen_cost(stmt, 3)
2884 }
2885 .stmt_for_in {
2886 return 30 + cleanc_cursor_children_expr_codegen_cost(stmt, 0)
2887 }
2888 .stmt_block {
2889 return 4 + cleanc_cursor_children_stmt_codegen_cost(stmt, 0)
2890 }
2891 .stmt_defer {
2892 return 10 + cleanc_cursor_children_stmt_codegen_cost(stmt, 0)
2893 }
2894 .stmt_const_decl {
2895 return 8 + cleanc_expr_cursor_codegen_cost(stmt.edge(0))
2896 }
2897 .stmt_global_decl {
2898 return 20 + stmt.list_at(1).len() * 8
2899 }
2900 .stmt_struct_decl {
2901 return 8 + stmt.list_at(4).len()
2902 }
2903 .stmt_interface_decl {
2904 return 8 + stmt.list_at(3).len()
2905 }
2906 .stmt_type_decl {
2907 return 8 + stmt.list_at(3).len()
2908 }
2909 .stmt_comptime, .stmt_label {
2910 return 4 + cleanc_stmt_cursor_codegen_cost(stmt.edge(0))
2911 }
2912 else {
2913 return 1
2914 }
2915 }
2916}
2917
2918fn cleanc_stmt_codegen_cost(stmt ast.Stmt) int {
2919 match stmt {
2920 ast.FnDecl {
2921 return 50 + cleanc_stmts_codegen_cost(stmt.stmts)
2922 }
2923 ast.AssignStmt {
2924 mut cost := 12
2925 for expr in stmt.lhs {
2926 cost += cleanc_expr_codegen_cost(expr)
2927 }
2928 for expr in stmt.rhs {
2929 cost += cleanc_expr_codegen_cost(expr)
2930 }
2931 return cost
2932 }
2933 ast.ExprStmt {
2934 return 4 + cleanc_expr_codegen_cost(stmt.expr)
2935 }
2936 ast.ReturnStmt {
2937 mut cost := 8
2938 for expr in stmt.exprs {
2939 cost += cleanc_expr_codegen_cost(expr)
2940 }
2941 return cost
2942 }
2943 ast.ForStmt {
2944 return 30 + cleanc_stmt_codegen_cost(stmt.init) + cleanc_expr_codegen_cost(stmt.cond) +
2945 cleanc_stmt_codegen_cost(stmt.post) + cleanc_stmts_codegen_cost(stmt.stmts)
2946 }
2947 ast.ForInStmt {
2948 return 30 + cleanc_expr_codegen_cost(stmt.expr)
2949 }
2950 ast.BlockStmt {
2951 return 4 + cleanc_stmts_codegen_cost(stmt.stmts)
2952 }
2953 ast.DeferStmt {
2954 return 10 + cleanc_stmts_codegen_cost(stmt.stmts)
2955 }
2956 ast.ConstDecl {
2957 mut cost := 8
2958 for field in stmt.fields {
2959 cost += cleanc_expr_codegen_cost(field.value)
2960 }
2961 return cost
2962 }
2963 ast.GlobalDecl {
2964 return 20 + stmt.fields.len * 8
2965 }
2966 ast.StructDecl {
2967 return 8 + stmt.fields.len
2968 }
2969 ast.InterfaceDecl {
2970 return 8 + stmt.fields.len
2971 }
2972 ast.TypeDecl {
2973 return 8 + stmt.variants.len
2974 }
2975 ast.ComptimeStmt {
2976 return 4 + cleanc_stmt_codegen_cost(stmt.stmt)
2977 }
2978 ast.LabelStmt {
2979 return 4 + cleanc_stmt_codegen_cost(stmt.stmt)
2980 }
2981 else {
2982 return 1
2983 }
2984 }
2985}
2986
2987fn cleanc_expr_cursor_codegen_cost(expr ast.Cursor) int {
2988 if !expr.is_valid() {
2989 return 1
2990 }
2991 match expr.kind() {
2992 .aux_list {
2993 return cleanc_cursor_children_expr_codegen_cost(expr, 0)
2994 }
2995 .aux_field_init {
2996 return 4 + cleanc_expr_cursor_codegen_cost(expr.edge(0))
2997 }
2998 .expr_call {
2999 return 18 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3000 }
3001 .expr_call_or_cast {
3002 return 14 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3003 }
3004 .expr_init {
3005 return 10 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3006 }
3007 .expr_array_init {
3008 return 6 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3009 }
3010 .expr_map_init {
3011 return 10 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3012 }
3013 .expr_if {
3014 return 18 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3015 }
3016 .expr_match {
3017 return 20 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3018 }
3019 .expr_infix {
3020 return 6 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3021 }
3022 .expr_prefix, .expr_postfix, .expr_selector, .expr_keyword_operator, .expr_range {
3023 return 4 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3024 }
3025 .expr_index, .expr_cast, .expr_as_cast, .expr_tuple {
3026 return 6 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3027 }
3028 .expr_paren, .expr_modifier {
3029 return cleanc_cursor_children_expr_codegen_cost(expr, 0)
3030 }
3031 .expr_or, .expr_unsafe {
3032 return 12 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3033 }
3034 .expr_lock, .expr_fn_literal, .expr_lambda, .expr_sql {
3035 return 20 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3036 }
3037 .expr_comptime {
3038 return 8 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3039 }
3040 else {
3041 return 1 + cleanc_cursor_children_expr_codegen_cost(expr, 0)
3042 }
3043 }
3044}
3045
3046fn cleanc_exprs_codegen_cost(exprs []ast.Expr) int {
3047 mut cost := 0
3048 for expr in exprs {
3049 cost += cleanc_expr_codegen_cost(expr)
3050 }
3051 return cost
3052}
3053
3054fn cleanc_expr_codegen_cost(expr ast.Expr) int {
3055 match expr {
3056 ast.CallExpr {
3057 if orm_create_call_can_emit(expr) {
3058 return 30 + cleanc_expr_codegen_cost(expr.lhs)
3059 }
3060 return 18 + cleanc_expr_codegen_cost(expr.lhs) + cleanc_exprs_codegen_cost(expr.args)
3061 }
3062 ast.CallOrCastExpr {
3063 return 14 + cleanc_expr_codegen_cost(expr.lhs) + cleanc_expr_codegen_cost(expr.expr)
3064 }
3065 ast.InitExpr {
3066 mut cost := 10
3067 for field in expr.fields {
3068 cost += cleanc_expr_codegen_cost(field.value)
3069 }
3070 return cost
3071 }
3072 ast.ArrayInitExpr {
3073 return 6 + cleanc_exprs_codegen_cost(expr.exprs) + cleanc_expr_codegen_cost(expr.init) +
3074 cleanc_expr_codegen_cost(expr.cap) + cleanc_expr_codegen_cost(expr.len)
3075 }
3076 ast.MapInitExpr {
3077 return 10 + cleanc_exprs_codegen_cost(expr.keys) + cleanc_exprs_codegen_cost(expr.vals)
3078 }
3079 ast.IfExpr {
3080 return 18 + cleanc_expr_codegen_cost(expr.cond) +
3081 cleanc_stmts_codegen_cost(expr.stmts) + cleanc_expr_codegen_cost(expr.else_expr)
3082 }
3083 ast.MatchExpr {
3084 mut cost := 20 + cleanc_expr_codegen_cost(expr.expr)
3085 for branch in expr.branches {
3086 cost += cleanc_exprs_codegen_cost(branch.cond) +
3087 cleanc_stmts_codegen_cost(branch.stmts)
3088 }
3089 return cost
3090 }
3091 ast.InfixExpr {
3092 return 6 + cleanc_expr_codegen_cost(expr.lhs) + cleanc_expr_codegen_cost(expr.rhs)
3093 }
3094 ast.PrefixExpr {
3095 return 4 + cleanc_expr_codegen_cost(expr.expr)
3096 }
3097 ast.PostfixExpr {
3098 return 4 + cleanc_expr_codegen_cost(expr.expr)
3099 }
3100 ast.SelectorExpr {
3101 return 4 + cleanc_expr_codegen_cost(expr.lhs)
3102 }
3103 ast.IndexExpr {
3104 return 6 + cleanc_expr_codegen_cost(expr.lhs) + cleanc_expr_codegen_cost(expr.expr)
3105 }
3106 ast.CastExpr {
3107 return 6 + cleanc_expr_codegen_cost(expr.expr)
3108 }
3109 ast.AsCastExpr {
3110 return 6 + cleanc_expr_codegen_cost(expr.expr)
3111 }
3112 ast.ParenExpr {
3113 return cleanc_expr_codegen_cost(expr.expr)
3114 }
3115 ast.ModifierExpr {
3116 return cleanc_expr_codegen_cost(expr.expr)
3117 }
3118 ast.OrExpr {
3119 return 12 + cleanc_expr_codegen_cost(expr.expr) + cleanc_stmts_codegen_cost(expr.stmts)
3120 }
3121 ast.UnsafeExpr {
3122 return 12 + cleanc_stmts_codegen_cost(expr.stmts)
3123 }
3124 ast.LockExpr {
3125 return 20 + cleanc_exprs_codegen_cost(expr.lock_exprs) +
3126 cleanc_exprs_codegen_cost(expr.rlock_exprs) + cleanc_stmts_codegen_cost(expr.stmts)
3127 }
3128 ast.FieldInit {
3129 return 4 + cleanc_expr_codegen_cost(expr.value)
3130 }
3131 ast.Tuple {
3132 return 6 + cleanc_exprs_codegen_cost(expr.exprs)
3133 }
3134 ast.KeywordOperator {
3135 return 4 + cleanc_exprs_codegen_cost(expr.exprs)
3136 }
3137 ast.RangeExpr {
3138 return 4 + cleanc_expr_codegen_cost(expr.start) + cleanc_expr_codegen_cost(expr.end)
3139 }
3140 ast.ComptimeExpr {
3141 return 8 + cleanc_expr_codegen_cost(expr.expr)
3142 }
3143 ast.FnLiteral {
3144 return 20 + cleanc_exprs_codegen_cost(expr.captured_vars) +
3145 cleanc_stmts_codegen_cost(expr.stmts)
3146 }
3147 ast.LambdaExpr {
3148 return 20 + cleanc_expr_codegen_cost(expr.expr)
3149 }
3150 ast.SqlExpr {
3151 return 20 + cleanc_expr_codegen_cost(expr.expr)
3152 }
3153 else {
3154 return 1
3155 }
3156 }
3157}
3158
3159// new_pass5_worker creates a worker Gen for parallel Pass 5.
3160// file_indices specifies which files this worker will process.
3161// Functions owned by files outside this range are pre-marked as emitted
3162// to prevent duplicate emission across workers.
3163pub fn (g &Gen) new_pass5_worker(file_indices []int, worker_id int) &Gen {
3164 // Build a set of file indices this worker owns
3165 mut owned_files := map[int]bool{}
3166 for fi in file_indices {
3167 owned_files[fi] = true
3168 }
3169 // Clone emitted_types and reserve functions/globals owned by other workers.
3170 mut worker_emitted := g.emitted_types.clone()
3171 mut blocked_fn_keys := map[string]bool{}
3172 for fn_key, owner_fi in g.fn_owner_file {
3173 if owner_fi !in owned_files {
3174 blocked_fn_keys[fn_key] = true
3175 }
3176 }
3177 // Block globals owned by other workers to prevent duplicate definitions.
3178 for gname, owner_fi in g.global_owner_file {
3179 if owner_fi !in owned_files {
3180 worker_emitted['global_${gname}'] = true
3181 }
3182 }
3183 return &Gen{
3184 files: g.files
3185 flat: unsafe { g.flat }
3186 env: unsafe { g.env }
3187 pref: unsafe { g.pref }
3188 imported_symbols_index: g.imported_symbols_index.clone()
3189 v_method_return_index: g.v_method_return_index.clone()
3190 ierror_base_index: g.ierror_base_index.clone()
3191 fn_owner_file: g.fn_owner_file.clone()
3192 sb: strings.new_builder(64_000)
3193 // Read-only lookup maps — clone to avoid COW data races
3194 fn_param_is_ptr: g.fn_param_is_ptr.clone()
3195 fn_param_types: g.fn_param_types.clone()
3196 fn_return_types: g.fn_return_types.clone()
3197 v_fn_return_types: g.v_fn_return_types.clone()
3198 struct_field_types: g.struct_field_types.clone()
3199 enum_value_to_enum: g.enum_value_to_enum.clone()
3200 enum_type_fields: g.enum_type_fields.clone()
3201 array_aliases: g.array_aliases.clone()
3202 map_aliases: g.map_aliases.clone()
3203 result_aliases: g.result_aliases.clone()
3204 option_aliases: g.option_aliases.clone()
3205 alias_base_types: g.alias_base_types.clone()
3206 fixed_array_fields: g.fixed_array_fields.clone()
3207 fixed_array_field_elem: g.fixed_array_field_elem.clone()
3208 fixed_array_globals: g.fixed_array_globals.clone()
3209 fixed_array_ret_wrappers: g.fixed_array_ret_wrappers.clone()
3210 tuple_aliases: g.tuple_aliases.clone()
3211 sum_type_variants: g.sum_type_variants.clone()
3212 embedded_field_owner: g.embedded_field_owner.clone()
3213 primitive_type_aliases: g.primitive_type_aliases.clone()
3214 emit_modules: g.emit_modules.clone()
3215 type_modules: g.type_modules.clone()
3216 emit_files: g.emit_files.clone()
3217 emitted_result_structs: g.emitted_result_structs.clone()
3218 emitted_option_structs: g.emitted_option_structs.clone()
3219 interface_methods: g.interface_methods.clone()
3220 interface_data_fields: g.interface_data_fields.clone()
3221 emitted_interface_bodies: g.emitted_interface_bodies.clone()
3222 interface_wrapper_specs: g.interface_wrapper_specs.clone()
3223 ierror_wrapper_bases: g.ierror_wrapper_bases.clone()
3224 collected_fixed_array_types: g.collected_fixed_array_types.clone()
3225 collected_map_types: g.collected_map_types.clone()
3226 c_file_fn_keys: g.c_file_fn_keys.clone()
3227 global_var_modules: g.global_var_modules.clone()
3228 global_var_types: g.global_var_types.clone()
3229 const_exprs: g.const_exprs.clone()
3230 const_types: g.const_types.clone()
3231 const_c_names: g.const_c_names.clone()
3232 module_storage_vars: g.module_storage_vars.clone()
3233 c_extern_module_storage: g.c_extern_module_storage.clone()
3234 runtime_const_targets: g.runtime_const_targets.clone()
3235 export_const_symbols: g.export_const_symbols
3236 cache_bundle_name: g.cache_bundle_name
3237 cached_init_calls: g.cached_init_calls.clone()
3238 used_fn_keys: g.used_fn_keys.clone()
3239 force_emit_fn_names: g.force_emit_fn_names.clone()
3240 export_fn_names: g.export_fn_names.clone()
3241 called_fn_names: g.called_fn_names.clone()
3242 called_specialized_names: g.called_specialized_names.clone()
3243 called_specialized_names_indexed: g.called_specialized_names_indexed
3244 declared_fn_names: g.declared_fn_names.clone()
3245 should_emit_fn_decl_cache: g.should_emit_fn_decl_cache.clone()
3246 generic_body_scan_cache: g.generic_body_scan_cache.clone()
3247 fn_type_aliases: g.fn_type_aliases.clone()
3248 generic_spec_index: g.generic_spec_index.clone()
3249 generic_fn_decl_index: g.generic_fn_decl_index.clone()
3250 specialized_fn_bases: g.specialized_fn_bases.clone()
3251 specialized_receiver_methods: g.specialized_receiver_methods.clone()
3252 specialized_receiver_method_ambiguous: g.specialized_receiver_method_ambiguous.clone()
3253 specialized_receiver_method_miss: g.specialized_receiver_method_miss.clone()
3254 specialized_receiver_method_indexed: g.specialized_receiver_method_indexed
3255 late_generic_specs: g.late_generic_specs.clone()
3256 generic_scan_called_names: map[string]bool{}
3257 generic_struct_bindings: g.generic_struct_bindings.clone()
3258 generic_struct_instances: g.generic_struct_instances.clone()
3259 c_struct_types: g.c_struct_types.clone()
3260 typedef_c_types: g.typedef_c_types.clone()
3261 // Per-worker mutable state (starts fresh).
3262 // Each worker gets a unique tmp_counter offset to avoid name collisions
3263 // for generated trampolines (_bound_method_N, _bound_recv_N, etc.).
3264 tmp_counter: (worker_id + 1) * 100_000
3265 pass5_worker_id: worker_id
3266 emitted_types: worker_emitted
3267 blocked_fn_keys: blocked_fn_keys
3268 runtime_local_types: map[string]string{}
3269 runtime_decl_types: map[string]string{}
3270 runtime_fn_pointer_types: map[string]types.Type{}
3271 cur_fn_returned_idents: map[string]bool{}
3272 active_generic_types: map[string]types.Type{}
3273 cur_fn_generic_params: map[string]string{}
3274 cur_fn_scope_miss_key: ''
3275 cur_import_modules: map[string]string{}
3276 is_module_ident_cache: map[string]bool{}
3277 not_local_var_cache: map[string]bool{}
3278 resolved_module_names: map[string]string{}
3279 cur_fn_mut_params: map[string]bool{}
3280 cached_env_scopes: map[string]voidptr{}
3281 selector_field_type_cache: map[string]string{}
3282 selector_field_type_miss: map[string]bool{}
3283 struct_field_lookup_cache: map[string]string{}
3284 struct_field_lookup_miss: map[string]bool{}
3285 struct_type_lookup_cache: map[string]types.Struct{}
3286 struct_type_lookup_miss: map[string]bool{}
3287 struct_decl_info_cache: map[string]StructDeclInfo{}
3288 struct_decl_info_miss: map[string]bool{}
3289 flat_struct_decl_exact: g.flat_struct_decl_exact
3290 flat_struct_decl_short_by_mod: g.flat_struct_decl_short_by_mod
3291 flat_struct_decl_short: g.flat_struct_decl_short
3292 flat_struct_decl_indexed: g.flat_struct_decl_indexed
3293 alias_base_lookup_cache: map[string]string{}
3294 alias_base_lookup_miss: map[string]bool{}
3295 needed_interface_wrappers: map[string]bool{}
3296 needed_ierror_wrapper_bases: map[string]bool{}
3297 spawned_fns: map[string]bool{}
3298 exported_const_seen: map[string]bool{}
3299 exported_const_symbols: []ExportedConstSymbol{}
3300 }
3301}
3302
3303// merge_pass5_worker merges a parallel worker's output into the main Gen.
3304pub fn (mut g Gen) merge_pass5_worker(w &Gen) {
3305 // Append worker's generated C code
3306 mut ww := unsafe { w }
3307 worker_output := ww.sb.str()
3308 if worker_output.len > 0 {
3309 g.sb.write_string(worker_output)
3310 }
3311 // Merge accumulator arrays
3312 g.anon_fn_defs << w.anon_fn_defs
3313 g.live_fns << w.live_fns
3314 if w.live_source_file.len > 0 {
3315 g.live_source_file = w.live_source_file
3316 }
3317 g.spawn_wrapper_defs << w.spawn_wrapper_defs
3318 g.trampoline_defs << w.trampoline_defs
3319 g.exported_const_symbols << w.exported_const_symbols
3320 g.pass5_file_times << w.pass5_file_times
3321 // Merge accumulator maps
3322 for k, v in w.needed_interface_wrappers {
3323 g.needed_interface_wrappers[k] = v
3324 }
3325 for k, v in w.needed_ierror_wrapper_bases {
3326 g.needed_ierror_wrapper_bases[k] = v
3327 }
3328 for k, v in w.called_fn_names {
3329 g.called_fn_names[k] = v
3330 if v {
3331 g.remember_called_specialized_name(k)
3332 }
3333 }
3334 // Merge emitted_types so post-pass dedup (e.g. array_contains fallbacks) works
3335 for k, v in w.emitted_types {
3336 g.emitted_types[k] = v
3337 }
3338}
3339
3340fn is_c_identifier_like(name string) bool {
3341 if name.len == 0 {
3342 return false
3343 }
3344 for ch in name {
3345 if !(ch.is_letter() || ch.is_digit() || ch == `_`) {
3346 return false
3347 }
3348 }
3349 return true
3350}
3351
3352fn type_decl_base_is_fn_type(node ast.TypeDecl) bool {
3353 if node.base_type is ast.Type {
3354 return node.base_type is ast.FnType
3355 }
3356 return false
3357}
3358
3359fn (g &Gen) has_live_reload_functions() bool {
3360 if g.cache_bundle_name.len > 0 {
3361 return false
3362 }
3363 if g.pref != unsafe { nil } && g.pref.is_shared_lib {
3364 return false
3365 }
3366 if g.has_flat() {
3367 for i in 0 .. g.flat.files.len {
3368 if g.cursor_stmts_have_live_reload_function(g.flat.file_cursor(i).stmts()) {
3369 return true
3370 }
3371 }
3372 return false
3373 }
3374 for file in g.files {
3375 if g.stmts_have_live_reload_function(file.stmts) {
3376 return true
3377 }
3378 }
3379 return false
3380}
3381
3382fn (g &Gen) cursor_stmts_have_live_reload_function(stmts ast.CursorList) bool {
3383 for i in 0 .. stmts.len() {
3384 if g.cursor_stmt_has_live_reload_function(stmts.at(i)) {
3385 return true
3386 }
3387 }
3388 return false
3389}
3390
3391fn (g &Gen) cursor_stmt_has_live_reload_function(stmt ast.Cursor) bool {
3392 match stmt.kind() {
3393 .stmt_fn_decl {
3394 decl := stmt.fn_decl_signature()
3395 return decl.attributes.has('live') && decl.name != 'main'
3396 }
3397 .stmt_expr {
3398 return g.expr_cursor_has_live_reload_function(stmt.edge(0))
3399 }
3400 else {
3401 return false
3402 }
3403 }
3404}
3405
3406fn (g &Gen) stmts_have_live_reload_function(stmts []ast.Stmt) bool {
3407 for stmt in stmts {
3408 if g.stmt_has_live_reload_function(stmt) {
3409 return true
3410 }
3411 }
3412 return false
3413}
3414
3415fn (g &Gen) stmt_has_live_reload_function(stmt ast.Stmt) bool {
3416 if stmt is ast.FnDecl {
3417 return stmt.attributes.has('live') && stmt.name != 'main'
3418 }
3419 if stmt is ast.ExprStmt {
3420 return g.expr_has_live_reload_function(stmt.expr)
3421 }
3422 return false
3423}
3424
3425fn (g &Gen) expr_cursor_has_live_reload_function(expr ast.Cursor) bool {
3426 if expr.kind() == .expr_comptime {
3427 inner := expr.edge(0)
3428 if inner.kind() == .expr_if {
3429 return g.active_comptime_if_cursor_has_live_reload_function(inner)
3430 }
3431 return g.expr_cursor_has_live_reload_function(inner)
3432 }
3433 return false
3434}
3435
3436fn (g &Gen) expr_has_live_reload_function(expr ast.Expr) bool {
3437 if expr is ast.ComptimeExpr {
3438 if expr.expr is ast.IfExpr {
3439 return g.active_comptime_if_has_live_reload_function(expr.expr)
3440 }
3441 return g.expr_has_live_reload_function(expr.expr)
3442 }
3443 return false
3444}
3445
3446fn (g &Gen) if_expr_cursor_body_has_live_reload_function(node ast.Cursor) bool {
3447 for i in 2 .. node.edge_count() {
3448 if g.cursor_stmt_has_live_reload_function(node.edge(i)) {
3449 return true
3450 }
3451 }
3452 return false
3453}
3454
3455fn (g &Gen) active_comptime_if_cursor_has_live_reload_function(node ast.Cursor) bool {
3456 if g.eval_comptime_cond_cursor(node.edge(0)) {
3457 return g.if_expr_cursor_body_has_live_reload_function(node)
3458 }
3459 else_expr := node.edge(1)
3460 if else_expr.kind() == .expr_if {
3461 if else_expr.edge(0).kind() == .expr_empty {
3462 return g.if_expr_cursor_body_has_live_reload_function(else_expr)
3463 }
3464 return g.active_comptime_if_cursor_has_live_reload_function(else_expr)
3465 }
3466 return false
3467}
3468
3469fn (g &Gen) active_comptime_if_has_live_reload_function(node ast.IfExpr) bool {
3470 if g.eval_comptime_cond(node.cond) {
3471 return g.stmts_have_live_reload_function(node.stmts)
3472 }
3473 if node.else_expr is ast.IfExpr {
3474 else_if := node.else_expr as ast.IfExpr
3475 if else_if.cond is ast.EmptyExpr {
3476 return g.stmts_have_live_reload_function(else_if.stmts)
3477 }
3478 return g.active_comptime_if_has_live_reload_function(else_if)
3479 }
3480 return false
3481}
3482
3483fn (g &Gen) runtime_fn_referenced(name string) bool {
3484 return name in g.called_fn_names || 'builtin__${name}' in g.called_fn_names
3485}
3486
3487fn (g &Gen) runtime_any_fn_referenced(names []string) bool {
3488 for name in names {
3489 if g.runtime_fn_referenced(name) {
3490 return true
3491 }
3492 }
3493 return false
3494}
3495
3496fn (g &Gen) runtime_fallback_needed(name string) bool {
3497 return 'fn_${name}' !in g.emitted_types && g.runtime_fn_referenced(name)
3498}
3499
3500fn c_is_ident_char(ch u8) bool {
3501 return ch.is_letter() || ch.is_digit() || ch == `_`
3502}
3503
3504fn c_skip_spaces(csrc string, start int) int {
3505 mut i := start
3506 for i < csrc.len && csrc[i].is_space() {
3507 i++
3508 }
3509 return i
3510}
3511
3512fn c_previous_non_space(csrc string, start int) int {
3513 mut i := start - 1
3514 for i >= 0 && csrc[i].is_space() {
3515 i--
3516 }
3517 return i
3518}
3519
3520fn c_is_runtime_symbol_context(csrc string, start int, end int) bool {
3521 next := c_skip_spaces(csrc, end)
3522 prev := c_previous_non_space(csrc, start)
3523 if next < csrc.len && csrc[next] == `(` {
3524 return true
3525 }
3526 if prev >= 0 && csrc[prev] == `&` {
3527 return true
3528 }
3529 if next < csrc.len && csrc[next] in [`,`, `)`, `;`] && prev >= 0
3530 && csrc[prev] in [`(`, `,`, `=`] {
3531 return true
3532 }
3533 return false
3534}
3535
3536fn c_source_references_runtime_symbol(csrc string, name string) bool {
3537 mut i := 0
3538 for i < csrc.len {
3539 ch := csrc[i]
3540 if ch == `/` && i + 1 < csrc.len && csrc[i + 1] == `/` {
3541 i += 2
3542 for i < csrc.len && csrc[i] != `\n` {
3543 i++
3544 }
3545 continue
3546 }
3547 if ch == `/` && i + 1 < csrc.len && csrc[i + 1] == `*` {
3548 i += 2
3549 for i + 1 < csrc.len && !(csrc[i] == `*` && csrc[i + 1] == `/`) {
3550 i++
3551 }
3552 i += 2
3553 continue
3554 }
3555 if ch == `"` || ch == `'` {
3556 quote := ch
3557 i++
3558 for i < csrc.len {
3559 if csrc[i] == `\\` {
3560 i += 2
3561 continue
3562 }
3563 if csrc[i] == quote {
3564 i++
3565 break
3566 }
3567 i++
3568 }
3569 continue
3570 }
3571 if ch.is_letter() || ch == `_` {
3572 start := i
3573 for i < csrc.len && c_is_ident_char(csrc[i]) {
3574 i++
3575 }
3576 if csrc[start..i] == name {
3577 if c_is_runtime_symbol_context(csrc, start, i) {
3578 return true
3579 }
3580 }
3581 continue
3582 }
3583 i++
3584 }
3585 return false
3586}
3587
3588fn (g &Gen) runtime_fallback_needed_in_source(name string, csrc string) bool {
3589 return 'fn_${name}' !in g.emitted_types
3590 && (g.runtime_fn_referenced(name) || c_source_references_runtime_symbol(csrc, name))
3591}
3592
3593fn freestanding_heap_runtime_helper_names() []string {
3594 return [
3595 'new_array_from_c_array',
3596 'new_array_from_c_array_no_alloc',
3597 'new_array_from_c_array_noscan',
3598 'new_array_from_array_and_c_array',
3599 'builtin__new_array_from_array_and_c_array',
3600 '__new_array_with_default_noscan',
3601 'new_map',
3602 'new_map_init',
3603 'new_map_init_noscan_key',
3604 'new_map_init_noscan_value',
3605 'new_map_init_noscan_key_value',
3606 'new_dense_array',
3607 'DenseArray__has_index',
3608 'DenseArray__key',
3609 'DenseArray__value',
3610 'DenseArray__zeros_to_end',
3611 'IError__str',
3612 'Array_int_contains',
3613 'Array_string_contains',
3614 'Array_string_index',
3615 'Array_int_str',
3616 '__v2_array_eq',
3617 'array__push',
3618 'array__push_many',
3619 'array__push_noscan',
3620 'array__eq',
3621 'array__insert',
3622 'array__insert_many',
3623 'array__prepend',
3624 'array__prepend_many',
3625 'array__delete',
3626 'array__delete_many',
3627 'array__clear',
3628 'array__slice',
3629 'array__slice_ni',
3630 'array__clone',
3631 'array__clone_to_depth',
3632 'array__repeat',
3633 'array__repeat_to_depth',