v2 / vlib / v2 / ssa / builder.v
9031 lines · 8581 sloc · 291.55 KB · e35325885cca6c2fbd76257048cfdd9e1de249de
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 ssa
6
7import v2.ast
8import v2.markused
9import v2.types
10
11struct DynConstArray {
12 arr_global_name string // V array struct global name
13 data_global_name string // raw data global name
14 elem_count int
15 elem_size int
16}
17
18pub struct Builder {
19pub mut:
20 mod &Module
21 cur_module string = 'main'
22 // When set, build_fn_bodies only builds this function (hot code reload optimization)
23 hot_fn string
24 // When set, build_all skips Phase 4 (function bodies) — caller handles it.
25 skip_fn_bodies bool
26 // When set, only build functions whose decl key is in this map (dead code elimination).
27 used_fn_keys map[string]bool
28 // When set, skip all functions from these modules (dead code elimination for unused backends).
29 skip_modules map[string]bool
30mut:
31 env &types.Environment = unsafe { nil }
32 cur_func int = -1
33 cur_block BlockID = -1
34 // Variable name -> SSA ValueID (alloca pointer)
35 vars map[string]ValueID
36 // Loop break/continue targets
37 loop_stack []LoopInfo
38 // Struct name -> SSA TypeID
39 struct_types map[string]TypeID
40 // Enum name -> field values
41 enum_values map[string]int
42 // Function name -> SSA function index
43 fn_index map[string]int
44 // Function name -> SSA func_ref value
45 fn_refs map[string]ValueID
46 // Global variable name -> SSA global value
47 global_refs map[string]ValueID
48 // Constant name -> evaluated integer value (for inlining)
49 const_values map[string]i64
50 const_value_types map[string]TypeID // SSA type for the constant (e.g., u64 vs i64)
51 // String constant name -> string literal value (for inlining)
52 string_const_values map[string]string
53 // Float constant name -> float literal string (for inlining as f64)
54 float_const_values map[string]string
55 // Label name -> SSA BlockID (for goto/label support)
56 label_blocks map[string]BlockID
57 // Track mut pointer params (e.g., mut buf &u8) that need extra dereference
58 // when used in expressions (buf is ptr(ptr(i8)), but user sees buf as &u8)
59 mut_ptr_params map[string]bool
60 // Set during sum type init _data field building to trigger heap allocation
61 // for &struct_local (prevents dangling stack pointers in returned sum types)
62 in_sumtype_data bool
63 // Constant array globals: names of globals that store raw element data
64 // (not V array structs). build_ident returns the pointer directly.
65 const_array_globals map[string]bool
66 const_array_elem_count map[string]int
67 // Dynamic const arrays: array struct globals that need _vinit initialization.
68 // Key: array struct global name, Value: data global name + metadata.
69 dyn_const_arrays []DynConstArray
70 // Synthetic native wrapper types for ?T / !T.
71 option_wrapper_types map[string]TypeID
72 result_wrapper_types map[string]TypeID
73 // Counter for generating unique anonymous function names
74 anon_fn_counter int
75 // Array element types by variable name (for transformer-generated functions
76 // where checker position info is unavailable). Maps param/var name to element SSA type.
77 array_elem_types map[string]TypeID
78}
79
80struct LoopInfo {
81 cond_block BlockID
82 exit_block BlockID
83}
84
85pub fn Builder.new(mod &Module) &Builder {
86 return Builder.new_with_env(mod, unsafe { nil })
87}
88
89pub fn Builder.new_with_env(mod &Module, env &types.Environment) &Builder {
90 mut b := &Builder{
91 mod: mod
92 vars: map[string]ValueID{}
93 loop_stack: []LoopInfo{}
94 struct_types: map[string]TypeID{}
95 enum_values: map[string]int{}
96 fn_index: map[string]int{}
97 fn_refs: map[string]ValueID{}
98 global_refs: map[string]ValueID{}
99 option_wrapper_types: map[string]TypeID{}
100 result_wrapper_types: map[string]TypeID{}
101 }
102 unsafe {
103 b.env = env
104 mod.env = env
105 }
106 return b
107}
108
109// new_worker_clone creates a Builder for parallel SSA building.
110// Shares read-only maps from the main builder, uses a separate worker Module.
111// worker_idx offsets anon_fn_counter so workers don't generate conflicting names.
112pub fn (mut b Builder) new_worker_clone(worker_mod &Module, worker_idx int) &Builder {
113 // Clone all maps to avoid COW races between threads.
114 // Maps that are read-only in Phase 4 (struct_types, enum_values, const_values, etc.)
115 // still need cloning because V's map read operations can trigger internal COW writes.
116 // Maps that are written in Phase 4 (fn_index, option_wrapper_types, etc.)
117 // obviously need per-worker copies.
118 return &Builder{
119 mod: worker_mod
120 env: b.env
121 struct_types: b.struct_types.clone()
122 enum_values: b.enum_values.clone()
123 fn_index: b.fn_index.clone()
124 global_refs: b.global_refs.clone()
125 const_values: b.const_values.clone()
126 const_value_types: b.const_value_types.clone()
127 string_const_values: b.string_const_values.clone()
128 float_const_values: b.float_const_values.clone()
129 const_array_globals: b.const_array_globals.clone()
130 const_array_elem_count: b.const_array_elem_count.clone()
131 option_wrapper_types: b.option_wrapper_types.clone()
132 result_wrapper_types: b.result_wrapper_types.clone()
133 // Offset anon_fn_counter so each worker generates unique names.
134 // Stride of 100_000 per worker avoids collisions.
135 anon_fn_counter: (worker_idx + 1) * 100_000
136 // Per-function state is reset at start of each build_fn, so empty init is fine
137 fn_refs: map[string]ValueID{}
138 vars: map[string]ValueID{}
139 loop_stack: []LoopInfo{}
140 label_blocks: map[string]BlockID{}
141 mut_ptr_params: map[string]bool{}
142 }
143}
144
145pub fn (mut b Builder) build_all(files []ast.File) {
146 // Register builtin globals needed by all backends
147 i32_t := b.mod.type_store.get_int(32)
148 i8_t := b.mod.type_store.get_int(8)
149 ptr_t := b.mod.type_store.get_ptr(i8_t)
150 ptr_ptr_t := b.mod.type_store.get_ptr(ptr_t)
151 b.mod.add_global('g_main_argc', i32_t, false)
152 b.mod.add_global('g_main_argv', ptr_ptr_t, false)
153
154 // Phase 1a: Register core builtin types first (string, array) since other structs depend on them.
155 // First, register builtin enums (e.g., ArrayFlags) so their types resolve correctly
156 // when registering struct fields for array/string.
157 for file in files {
158 b.cur_module = file_module_name(file)
159 if b.cur_module == 'builtin' {
160 for stmt in file.stmts {
161 if stmt is ast.EnumDecl {
162 b.register_enum(stmt)
163 }
164 }
165 }
166 }
167 for file in files {
168 b.cur_module = file_module_name(file)
169 if b.cur_module == 'builtin' {
170 for stmt in file.stmts {
171 if stmt is ast.StructDecl {
172 if stmt.name in ['string', 'array'] {
173 b.register_struct(stmt)
174 }
175 }
176 }
177 }
178 }
179 // Phase 1b: Register all struct type names (forward declarations) and enums
180 for file in files {
181 b.cur_module = file_module_name(file)
182 b.register_types_pass1(file)
183 }
184 // Phase 1c: Fill in struct field types (now all struct names are known)
185 for file in files {
186 b.cur_module = file_module_name(file)
187 b.register_types_pass2(file)
188 }
189 // Phase 2: Register consts and globals
190 for file in files {
191 b.cur_module = file_module_name(file)
192 b.register_consts_and_globals(file)
193 }
194 // Phase 2b: Re-evaluate constants with forward references
195 // Constants that referenced other constants from later files got value 0.
196 // Now that all constants are collected, re-evaluate them.
197 b.resolve_forward_const_refs(files)
198 // Build global lookup cache once before expression lowering.
199 b.index_global_values()
200 // Phase 3: Register function signatures
201 for file in files {
202 b.cur_module = file_module_name(file)
203 b.register_fn_signatures(file)
204 }
205 // Phase 3.1: Remove globals that collide with function names.
206 // e.g. sgl has both `const default_context = ...` and `fn default_context() ...`.
207 // The function takes precedence; the global would cause the init_consts function
208 // to write to the function's TEXT address (read-only), causing a bus error.
209 for mut gvar in b.mod.globals {
210 if gvar.name in b.fn_index {
211 gvar.linkage = .external // Mark as external so codegen skips data symbol
212 b.global_refs.delete(gvar.name) // Remove from lookup so stores are not generated
213 }
214 }
215
216 // Phase 3.5: Generate synthetic stubs for transformer-generated functions
217 if b.hot_fn.len == 0 {
218 b.generate_array_eq_stub()
219 b.generate_wymix_stub()
220 b.generate_wyhash64_stub()
221 b.generate_wyhash_stub()
222 b.generate_ierror_stubs()
223 b.generate_fd_macro_stubs()
224 }
225
226 // Phase 4: Build function bodies
227 if !b.skip_fn_bodies {
228 b.build_all_fn_bodies(files)
229 }
230
231 // Phase 5: Generate _vinit for dynamic array constant initialization
232 // Always generate _vinit (even if empty) so the symbol is always resolvable
233 if b.hot_fn.len == 0 && !b.skip_fn_bodies {
234 b.generate_vinit()
235 }
236}
237
238// build_all_fn_bodies builds SSA for all function bodies (Phase 4).
239// Separated from build_all to allow the parallel builder to replace this step.
240pub fn (mut b Builder) build_all_fn_bodies(files []ast.File) {
241 for file in files {
242 b.cur_module = file_module_name(file)
243 b.build_fn_bodies(file)
244 }
245}
246
247pub fn file_module_name(file ast.File) string {
248 for stmt in file.stmts {
249 if stmt is ast.ModuleStmt {
250 return stmt.name.replace('.', '_')
251 }
252 }
253 return 'main'
254}
255
256// --- Type resolution using types.Environment ---
257
258fn (mut b Builder) type_to_ssa(t types.Type) TypeID {
259 match t {
260 types.Primitive {
261 if t.props.has(.boolean) {
262 return b.mod.type_store.get_int(1)
263 }
264 if t.props.has(.float) {
265 width := if t.size == 32 { 32 } else { 64 }
266 return b.mod.type_store.get_float(width)
267 }
268 if t.props.has(.integer) {
269 size := if t.size == 0 { 32 } else { int(t.size) }
270 if t.props.has(.unsigned) {
271 return b.mod.type_store.get_uint(size)
272 }
273 return b.mod.type_store.get_int(size)
274 }
275 return b.mod.type_store.get_int(32)
276 }
277 types.Pointer {
278 base := b.type_to_ssa(t.base_type)
279 return b.mod.type_store.get_ptr(base)
280 }
281 types.String {
282 return b.get_string_type()
283 }
284 types.Struct {
285 if t.name in b.struct_types {
286 return b.struct_types[t.name]
287 }
288 // Try module-qualified name: C structs are registered as "os__dirent"
289 // but the type checker stores them as just "dirent"
290 qualified := '${b.cur_module}__${t.name}'
291 if qualified in b.struct_types {
292 return b.struct_types[qualified]
293 }
294 // Try all known module prefixes for cross-module struct access
295 for sname, sid in b.struct_types {
296 if sname.ends_with('__${t.name}') {
297 return sid
298 }
299 }
300 return b.mod.type_store.get_int(64) // fallback
301 }
302 types.Enum {
303 return b.mod.type_store.get_int(32)
304 }
305 types.Void {
306 return 0 // void
307 }
308 types.Char {
309 return b.mod.type_store.get_int(8)
310 }
311 types.Rune {
312 return b.mod.type_store.get_int(32)
313 }
314 types.ISize {
315 return b.mod.type_store.get_int(64)
316 }
317 types.USize {
318 return b.mod.type_store.get_uint(64)
319 }
320 types.Alias {
321 return b.type_to_ssa(t.base_type)
322 }
323 types.Array {
324 // Dynamic arrays are struct-like: {data*, len, cap, element_size}
325 return b.get_array_type()
326 }
327 types.ArrayFixed {
328 // Fixed-size arrays: [N]T → SSA array type
329 elem_type := b.type_to_ssa(t.elem_type)
330 if t.len > 0 && elem_type != 0 {
331 return b.mod.type_store.get_array(elem_type, t.len)
332 }
333 return b.mod.type_store.get_int(64) // fallback
334 }
335 types.Nil {
336 i8_t := b.mod.type_store.get_int(8)
337 return b.mod.type_store.get_ptr(i8_t)
338 }
339 types.None {
340 return 0
341 }
342 types.Tuple {
343 tt := t.get_types()
344 mut elem_types := []TypeID{cap: tt.len}
345 for et in tt {
346 elem_types << b.type_to_ssa(et)
347 }
348 return b.mod.type_store.get_tuple(elem_types)
349 }
350 types.SumType {
351 if t.name in b.struct_types {
352 return b.struct_types[t.name]
353 }
354 // Try module-qualified name
355 qualified_st := '${b.cur_module}__${t.name}'
356 if qualified_st in b.struct_types {
357 return b.struct_types[qualified_st]
358 }
359 // Search all known module prefixes
360 for sname, sid in b.struct_types {
361 if sname.ends_with('__${t.name}') {
362 return sid
363 }
364 }
365 return b.mod.type_store.get_int(64) // fallback
366 }
367 types.Map {
368 return b.struct_types['map'] or { b.mod.type_store.get_int(64) }
369 }
370 types.OptionType {
371 return b.get_option_wrapper_type(b.type_to_ssa(t.base_type))
372 }
373 types.ResultType {
374 return b.get_result_wrapper_type(b.type_to_ssa(t.base_type))
375 }
376 types.FnType {
377 i8_t := b.mod.type_store.get_int(8)
378 return b.mod.type_store.get_ptr(i8_t) // fn pointers
379 }
380 types.Interface {
381 return b.mod.type_store.get_int(64) // interfaces lowered to i64
382 }
383 else {
384 return b.mod.type_store.get_int(64) // fallback for unhandled
385 }
386 }
387}
388
389fn (mut b Builder) get_string_type() TypeID {
390 return b.struct_types['string'] or { 0 }
391}
392
393fn (mut b Builder) get_array_type() TypeID {
394 return b.struct_types['array'] or { 0 }
395}
396
397fn (b &Builder) is_string_like_ssa_type(typ_id TypeID) bool {
398 if typ_id == 0 || int(typ_id) >= b.mod.type_store.types.len {
399 return false
400 }
401 str_type := b.struct_types['string'] or { TypeID(0) }
402 if str_type != 0 && typ_id == str_type {
403 return true
404 }
405 typ := b.mod.type_store.types[typ_id]
406 return typ.kind == .ptr_t && typ.elem_type == str_type
407}
408
409fn (mut b Builder) load_string_like_value(val_id ValueID) ValueID {
410 if val_id <= 0 || int(val_id) >= b.mod.values.len {
411 return val_id
412 }
413 str_type := b.get_string_type()
414 if str_type == 0 {
415 return val_id
416 }
417 typ_id := b.mod.values[val_id].typ
418 if typ_id == str_type {
419 return val_id
420 }
421 if typ_id > 0 && int(typ_id) < b.mod.type_store.types.len {
422 typ := b.mod.type_store.types[typ_id]
423 if typ.kind == .ptr_t && typ.elem_type == str_type {
424 return b.mod.add_instr(.load, b.cur_block, str_type, [val_id])
425 }
426 }
427 return val_id
428}
429
430fn (mut b Builder) get_ierror_storage_type() TypeID {
431 if 'IError' in b.struct_types {
432 return b.struct_types['IError']
433 }
434 return b.mod.type_store.get_int(64)
435}
436
437fn (mut b Builder) get_option_wrapper_type(base_type TypeID) TypeID {
438 key := base_type.str()
439 if type_id := b.option_wrapper_types[key] {
440 return type_id
441 }
442 state_type := b.mod.type_store.get_int(8)
443 err_type := b.get_ierror_storage_type()
444 mut field_types := []TypeID{cap: 3}
445 mut field_names := []string{cap: 3}
446 field_types << state_type
447 field_names << 'state'
448 field_types << err_type
449 field_names << 'err'
450 if base_type != 0 {
451 field_types << base_type
452 field_names << 'data'
453 }
454 type_id := b.mod.type_store.register(Type{
455 kind: .struct_t
456 fields: field_types
457 field_names: field_names
458 })
459 b.option_wrapper_types[key] = type_id
460 return type_id
461}
462
463fn (mut b Builder) get_result_wrapper_type(base_type TypeID) TypeID {
464 key := base_type.str()
465 if type_id := b.result_wrapper_types[key] {
466 return type_id
467 }
468 bool_type := b.mod.type_store.get_int(1)
469 err_type := b.get_ierror_storage_type()
470 mut field_types := []TypeID{cap: 3}
471 mut field_names := []string{cap: 3}
472 field_types << bool_type
473 field_names << 'is_error'
474 field_types << err_type
475 field_names << 'err'
476 if base_type != 0 {
477 field_types << base_type
478 field_names << 'data'
479 }
480 type_id := b.mod.type_store.register(Type{
481 kind: .struct_t
482 fields: field_types
483 field_names: field_names
484 })
485 b.result_wrapper_types[key] = type_id
486 return type_id
487}
488
489fn (b &Builder) is_option_wrapper_type(type_id TypeID) bool {
490 if type_id <= 0 || type_id >= b.mod.type_store.types.len {
491 return false
492 }
493 typ := b.mod.type_store.types[type_id]
494 return typ.kind == .struct_t && typ.field_names.len >= 2 && typ.field_names[0] == 'state'
495 && typ.field_names[1] == 'err'
496}
497
498fn (b &Builder) is_result_wrapper_type(type_id TypeID) bool {
499 if type_id <= 0 || type_id >= b.mod.type_store.types.len {
500 return false
501 }
502 typ := b.mod.type_store.types[type_id]
503 return typ.kind == .struct_t && typ.field_names.len >= 2 && typ.field_names[0] == 'is_error'
504 && typ.field_names[1] == 'err'
505}
506
507fn (b &Builder) is_wrapper_type(type_id TypeID) bool {
508 return b.is_option_wrapper_type(type_id) || b.is_result_wrapper_type(type_id)
509}
510
511fn (b &Builder) wrapper_has_data(type_id TypeID) bool {
512 if type_id <= 0 || type_id >= b.mod.type_store.types.len {
513 return false
514 }
515 typ := b.mod.type_store.types[type_id]
516 return typ.kind == .struct_t && typ.field_names.len >= 3 && typ.field_names[2] == 'data'
517}
518
519fn (b &Builder) wrapper_data_type(type_id TypeID) TypeID {
520 if !b.wrapper_has_data(type_id) {
521 return 0
522 }
523 return b.mod.type_store.types[type_id].fields[2]
524}
525
526fn (b &Builder) current_fn_return_type() TypeID {
527 if b.cur_func >= 0 && b.cur_func < b.mod.funcs.len {
528 return b.mod.funcs[b.cur_func].typ
529 }
530 return 0
531}
532
533fn (mut b Builder) build_unwrapped_postfix(expr ast.PostfixExpr, wrapped_val ValueID) ValueID {
534 if wrapped_val <= 0 || wrapped_val >= b.mod.values.len {
535 return wrapped_val
536 }
537 wrapped_type := b.mod.values[wrapped_val].typ
538 if !b.is_wrapper_type(wrapped_type) {
539 return wrapped_val
540 }
541 wrapper_info := b.mod.type_store.types[wrapped_type]
542 i32_t := b.mod.type_store.get_int(32)
543 bool_t := b.mod.type_store.get_int(1)
544 flag_idx := b.mod.get_or_add_const(i32_t, '0')
545 err_idx := b.mod.get_or_add_const(i32_t, '1')
546 flag_type := wrapper_info.fields[0]
547 flag_val := b.mod.add_instr(.extractvalue, b.cur_block, flag_type, [wrapped_val, flag_idx])
548 mut fail_cond := flag_val
549 if b.is_option_wrapper_type(wrapped_type) {
550 zero_flag := b.mod.get_or_add_const(flag_type, '0')
551 fail_cond = b.mod.add_instr(.ne, b.cur_block, bool_t, [flag_val, zero_flag])
552 }
553 fail_block := b.mod.add_block(b.cur_func, 'postfix_fail')
554 ok_block := b.mod.add_block(b.cur_func, 'postfix_ok')
555 b.mod.add_instr(.br, b.cur_block, 0,
556 [fail_cond, b.mod.blocks[fail_block].val_id, b.mod.blocks[ok_block].val_id])
557 b.add_edge(b.cur_block, fail_block)
558 b.add_edge(b.cur_block, ok_block)
559
560 b.cur_block = fail_block
561 fn_ret_type := b.current_fn_return_type()
562 if fn_ret_type != 0 && b.is_wrapper_type(fn_ret_type) {
563 if fn_ret_type == wrapped_type {
564 b.mod.add_instr(.ret, b.cur_block, 0, [wrapped_val])
565 } else {
566 err_type := if wrapper_info.fields.len > 1 {
567 wrapper_info.fields[1]
568 } else {
569 b.get_ierror_storage_type()
570 }
571 err_val := b.mod.add_instr(.extractvalue, b.cur_block, err_type, [
572 wrapped_val,
573 err_idx,
574 ])
575 propagated := b.build_wrapper_value(fn_ret_type, false, err_val, false)
576 b.mod.add_instr(.ret, b.cur_block, 0, [propagated])
577 }
578 } else {
579 panic_name := if 'builtin__panic' in b.fn_index { 'builtin__panic' } else { 'panic' }
580 if panic_name in b.fn_index {
581 panic_ref := b.get_or_create_fn_ref(panic_name, 0)
582 panic_msg := b.build_string_literal(ast.StringLiteral{
583 kind: .v
584 value: if expr.op == .not {
585 "'postfix ! unwrap failed'"
586 } else {
587 "'postfix ? unwrap failed'"
588 }
589 })
590 b.mod.add_instr(.call, b.cur_block, 0, [panic_ref, panic_msg])
591 }
592 b.mod.add_instr(.unreachable, b.cur_block, 0, []ValueID{})
593 }
594
595 b.cur_block = ok_block
596 if !b.wrapper_has_data(wrapped_type) {
597 return b.mod.get_or_add_const(b.mod.type_store.get_int(32), '0')
598 }
599 data_type := b.wrapper_data_type(wrapped_type)
600 data_idx := b.mod.get_or_add_const(i32_t, '2')
601 return b.mod.add_instr(.extractvalue, b.cur_block, data_type, [wrapped_val, data_idx])
602}
603
604fn (b &Builder) is_none_expr(expr ast.Expr) bool {
605 match expr {
606 ast.Ident {
607 return expr.name == 'none'
608 }
609 ast.Keyword {
610 return expr.tok == .key_none
611 }
612 ast.Type {
613 return expr is ast.NoneType
614 }
615 else {
616 return false
617 }
618 }
619}
620
621fn (b &Builder) is_error_expr(expr ast.Expr) bool {
622 error_fn_names := ['error', 'error_posix', 'error_with_code', 'error_win32']
623 match expr {
624 ast.Ident {
625 return expr.name == 'err'
626 }
627 ast.CallExpr {
628 return expr.lhs is ast.Ident && expr.lhs.name in error_fn_names
629 }
630 ast.CallOrCastExpr {
631 return expr.lhs is ast.Ident && expr.lhs.name in error_fn_names
632 }
633 else {
634 return false
635 }
636 }
637}
638
639fn (mut b Builder) build_wrapper_value(wrapper_type TypeID, success bool, payload ValueID, has_payload bool) ValueID {
640 if wrapper_type <= 0 || wrapper_type >= b.mod.type_store.types.len {
641 return payload
642 }
643 wrapper_info := b.mod.type_store.types[wrapper_type]
644 if wrapper_info.kind != .struct_t || wrapper_info.field_names.len < 2 {
645 return payload
646 }
647 mut wrapper := b.mod.get_or_add_const(wrapper_type, '0')
648 i32_t := b.mod.type_store.get_int(32)
649 flag_idx := b.mod.get_or_add_const(i32_t, '0')
650 err_idx := b.mod.get_or_add_const(i32_t, '1')
651 flag_type := wrapper_info.fields[0]
652 flag_val := if b.is_option_wrapper_type(wrapper_type) {
653 // V options use state==0 for success and state==2 for none/error.
654 b.mod.get_or_add_const(flag_type, if success { '0' } else { '2' })
655 } else {
656 b.mod.get_or_add_const(flag_type, if success { '0' } else { '1' })
657 }
658 wrapper = b.mod.add_instr(.insertvalue, b.cur_block, wrapper_type,
659 [wrapper, flag_val, flag_idx])
660 if !success {
661 err_type := wrapper_info.fields[1]
662 mut err_val := payload
663 if err_val == 0 {
664 err_val = b.mod.get_or_add_const(err_type, '0')
665 } else if b.mod.values[err_val].typ != err_type {
666 err_val = b.cast_value_to_type(err_val, err_type)
667 }
668 wrapper = b.mod.add_instr(.insertvalue, b.cur_block, wrapper_type,
669 [wrapper, err_val, err_idx])
670 }
671 if has_payload && b.wrapper_has_data(wrapper_type) {
672 data_idx := b.mod.get_or_add_const(i32_t, '2')
673 data_type := wrapper_info.fields[2]
674 mut data_val := payload
675 if data_val == 0 {
676 data_val = b.mod.get_or_add_const(data_type, '0')
677 } else if b.mod.values[data_val].typ != data_type {
678 data_val = b.cast_value_to_type(data_val, data_type)
679 }
680 wrapper = b.mod.add_instr(.insertvalue, b.cur_block, wrapper_type, [wrapper, data_val,
681 data_idx])
682 }
683 return wrapper
684}
685
686fn (mut b Builder) coerce_wrapper_value(expr ast.Expr, val ValueID, wrapper_type TypeID) ValueID {
687 if !b.is_wrapper_type(wrapper_type) {
688 return val
689 }
690 if b.is_none_expr(expr) {
691 return b.build_wrapper_value(wrapper_type, false, 0, false)
692 }
693 if b.is_error_expr(expr) {
694 return b.build_wrapper_value(wrapper_type, false, val, false)
695 }
696 if val > 0 && val < b.mod.values.len && b.mod.values[val].typ == wrapper_type {
697 match b.mod.values[val].kind {
698 .argument, .global, .instruction {
699 return val
700 }
701 else {}
702 }
703 }
704 return b.build_wrapper_value(wrapper_type, true, val, true)
705}
706
707fn (mut b Builder) expr_type(e ast.Expr) TypeID {
708 if b.env != unsafe { nil } {
709 pos := e.pos()
710 if pos.id != 0 {
711 if typ := b.env.get_expr_type(pos.id) {
712 return b.type_to_ssa(typ)
713 }
714 }
715 }
716 // Fallback for literals
717 match e {
718 ast.BasicLiteral {
719 if e.kind == .key_true || e.kind == .key_false {
720 return b.mod.type_store.get_int(1)
721 }
722 if e.kind == .number && (e.value.contains('.')
723 || (!e.value.starts_with('0x') && !e.value.starts_with('0X')
724 && (e.value.contains('e') || e.value.contains('E')))) {
725 return b.mod.type_store.get_float(64)
726 }
727 return b.mod.type_store.get_int(64)
728 }
729 ast.StringLiteral {
730 return b.get_string_type()
731 }
732 else {
733 return b.mod.type_store.get_int(64)
734 }
735 }
736}
737
738fn (mut b Builder) const_field_type(field_name string, value ast.Expr) TypeID {
739 if b.env != unsafe { nil } {
740 if scope := b.env.get_scope(b.cur_module) {
741 if obj := scope.lookup_parent(field_name, 0) {
742 obj_type := b.type_to_ssa(obj.typ())
743 if obj_type != 0 {
744 return obj_type
745 }
746 }
747 }
748 }
749 return b.expr_type(value)
750}
751
752fn (mut b Builder) types_type_c_name(t types.Type) string {
753 match t {
754 types.Primitive {
755 if t.props.has(.boolean) {
756 return 'bool'
757 }
758 if t.props.has(.float) {
759 return if t.size == 32 { 'f32' } else { 'f64' }
760 }
761 if t.props.has(.integer) {
762 if t.props.has(.untyped) {
763 return 'int'
764 }
765 size := if t.size == 0 { 32 } else { int(t.size) }
766 is_signed := !t.props.has(.unsigned)
767 return if is_signed {
768 match size {
769 8 { 'i8' }
770 16 { 'i16' }
771 64 { 'i64' }
772 else { 'int' }
773 }
774 } else {
775 match size {
776 8 { 'u8' }
777 16 { 'u16' }
778 32 { 'u32' }
779 else { 'u64' }
780 }
781 }
782 }
783 return 'int'
784 }
785 types.Pointer {
786 return b.types_type_c_name(t.base_type) + '*'
787 }
788 types.String {
789 return 'string'
790 }
791 types.Struct {
792 return t.name
793 }
794 types.Enum {
795 return t.name
796 }
797 types.Void {
798 return 'void'
799 }
800 types.Char {
801 return 'char'
802 }
803 types.Alias {
804 return b.types_type_c_name(t.base_type)
805 }
806 types.Array {
807 // []rune → Array_rune, []int → Array_int, etc.
808 return 'Array_${b.types_type_c_name(t.elem_type)}'
809 }
810 types.Rune {
811 return 'rune'
812 }
813 types.SumType {
814 return t.name
815 }
816 types.Interface {
817 return t.name
818 }
819 types.FnType {
820 return 'FnType'
821 }
822 types.Map {
823 return 'map'
824 }
825 types.OptionType {
826 // Unwrap Option to base type for method resolution
827 // (e.g., r.str() where r was unwrapped from ?int should resolve to int__str)
828 return b.types_type_c_name(t.base_type)
829 }
830 types.ResultType {
831 // Unwrap Result to base type for method resolution
832 return b.types_type_c_name(t.base_type)
833 }
834 types.ArrayFixed {
835 return 'ArrayFixed'
836 }
837 else {
838 return 'int'
839 }
840 }
841}
842
843// --- Phase 1: Register types ---
844
845// Pass 1: Register struct names as forward declarations (empty structs),
846// enums, and sumtypes. This ensures all struct names are in struct_types
847// before any field types are resolved.
848fn (mut b Builder) register_types_pass1(file ast.File) {
849 for stmt in file.stmts {
850 match stmt {
851 ast.StructDecl {
852 b.register_struct_name(stmt)
853 }
854 ast.EnumDecl {
855 b.register_enum(stmt)
856 }
857 ast.TypeDecl {
858 b.register_sumtype(stmt)
859 }
860 else {}
861 }
862 }
863}
864
865// Pass 2: Fill in struct field types. All struct names are now registered,
866// so cross-module struct references (e.g., &scanner.Scanner in Parser)
867// resolve correctly to the struct type instead of falling back to i64.
868fn (mut b Builder) register_types_pass2(file ast.File) {
869 nstmts := file.stmts.len
870 for si in 0 .. nstmts {
871 stmt := file.stmts[si]
872 if stmt is ast.StructDecl {
873 b.register_struct_fields(stmt)
874 }
875 }
876}
877
878fn (mut b Builder) struct_mangled_name(decl ast.StructDecl) string {
879 return if b.cur_module == 'builtin'
880 && decl.name in ['array', 'string', 'map', 'DenseArray', 'IError', 'Error', 'MessageError', 'None__', '_option', '_result', 'Option'] {
881 decl.name
882 } else if b.cur_module != '' && b.cur_module != 'main' {
883 '${b.cur_module}__${decl.name}'
884 } else {
885 decl.name
886 }
887}
888
889// register_struct_name registers a struct name with an empty struct type.
890// The fields will be filled in by register_struct_fields in pass 2.
891fn (mut b Builder) register_struct_name(decl ast.StructDecl) {
892 name := b.struct_mangled_name(decl)
893
894 if name in b.struct_types {
895 return
896 }
897
898 type_id := b.mod.type_store.register(Type{
899 kind: .struct_t
900 is_union: decl.is_union
901 })
902 b.struct_types[name] = type_id
903 b.mod.c_struct_names[type_id] = name
904}
905
906// register_struct_fields fills in the field types for a previously forward-declared struct.
907fn (mut b Builder) register_struct_fields(decl ast.StructDecl) {
908 name := b.struct_mangled_name(decl)
909
910 type_id := b.struct_types[name] or { return }
911
912 // Skip if fields are already populated (e.g., builtin types registered in Phase 1a)
913 if b.mod.type_store.types[type_id].fields.len > 0 {
914 return
915 }
916
917 mut field_types := []TypeID{}
918 mut field_names := []string{}
919 n_embedded := decl.embedded.len
920 n_fields := decl.fields.len
921
922 // Flatten embedded struct fields first (e.g., ObjectCommon in Const)
923 if n_embedded > 0 {
924 b.collect_embedded_fields(decl.embedded, mut field_names, mut field_types)
925 }
926
927 for fi in 0 .. n_fields {
928 field := decl.fields[fi]
929 ft := b.ast_type_to_ssa(field.typ)
930 field_types << ft
931 field_names << field.name
932 }
933
934 b.mod.type_store.types[type_id] = Type{
935 kind: .struct_t
936 fields: field_types
937 field_names: field_names
938 is_union: decl.is_union
939 }
940}
941
942// register_struct is the legacy combined registration (used for Phase 1a core types).
943fn (mut b Builder) register_struct(decl ast.StructDecl) {
944 name := b.struct_mangled_name(decl)
945
946 if name in b.struct_types {
947 return
948 }
949
950 mut field_types := []TypeID{}
951 mut field_names := []string{}
952
953 // Flatten embedded struct fields first
954 b.collect_embedded_fields(decl.embedded, mut field_names, mut field_types)
955
956 for field in decl.fields {
957 ft := b.ast_type_to_ssa(field.typ)
958 field_types << ft
959 field_names << field.name
960 }
961
962 type_id := b.mod.type_store.register(Type{
963 kind: .struct_t
964 fields: field_types
965 field_names: field_names
966 is_union: decl.is_union
967 })
968 b.struct_types[name] = type_id
969 b.mod.c_struct_names[type_id] = name
970}
971
972// collect_embedded_fields resolves embedded type expressions and adds their
973// flattened fields to the field_names and field_types lists.
974// Embedded structs (e.g., `ObjectCommon` in `struct Const { ObjectCommon; int_val int }`)
975// have their fields in a separate `embedded` list in the AST StructDecl.
976// This function looks up each embedded type in struct_types and prepends its fields.
977fn (mut b Builder) collect_embedded_fields(embedded []ast.Expr, mut field_names []string, mut field_types []TypeID) {
978 for emb in embedded {
979 // Get the type name from the embedded expression
980 emb_name := if emb is ast.Ident {
981 emb.name
982 } else if emb is ast.SelectorExpr && emb.lhs is ast.Ident {
983 mod_name := (emb.lhs as ast.Ident).name.replace('.', '_')
984 '${mod_name}__${emb.rhs.name}'
985 } else {
986 ''
987 }
988 if emb_name == '' {
989 continue
990 }
991 // Look up the embedded struct type, trying module-qualified name first
992 mut emb_type_id := TypeID(0)
993 if b.cur_module != '' && b.cur_module != 'main' {
994 qualified := '${b.cur_module}__${emb_name}'
995 if qualified in b.struct_types {
996 emb_type_id = b.struct_types[qualified]
997 }
998 }
999 if emb_type_id == 0 {
1000 if emb_name in b.struct_types {
1001 emb_type_id = b.struct_types[emb_name]
1002 }
1003 }
1004 if emb_type_id == 0 {
1005 continue
1006 }
1007 if emb_type_id < b.mod.type_store.types.len {
1008 emb_typ := b.mod.type_store.types[emb_type_id]
1009 if emb_typ.kind == .struct_t && emb_typ.field_names.len > 0 {
1010 for i, fname in emb_typ.field_names {
1011 field_names << fname
1012 if i < emb_typ.fields.len {
1013 field_types << emb_typ.fields[i]
1014 } else {
1015 field_types << b.mod.type_store.get_int(64)
1016 }
1017 }
1018 }
1019 }
1020 }
1021}
1022
1023fn (mut b Builder) register_enum(decl ast.EnumDecl) {
1024 name := if b.cur_module != '' && b.cur_module != 'main' {
1025 '${b.cur_module}__${decl.name}'
1026 } else {
1027 decl.name
1028 }
1029
1030 is_flag := decl.attributes.has('flag')
1031 for i, field in decl.fields {
1032 key := '${name}__${field.name}'
1033 if is_flag {
1034 // @[flag] enums use power-of-2 values: 1, 2, 4, 8, ...
1035 b.enum_values[key] = 1 << i
1036 } else {
1037 b.enum_values[key] = i
1038 }
1039 }
1040}
1041
1042// is_enum_type checks if a type name corresponds to a registered enum
1043// by looking for any enum_values key that starts with the name followed by '__'.
1044fn (b &Builder) is_enum_type(name string) bool {
1045 prefix := '${name}__'
1046 for key, _ in b.enum_values {
1047 if key.starts_with(prefix) {
1048 return true
1049 }
1050 }
1051 return false
1052}
1053
1054fn (mut b Builder) register_sumtype(decl ast.TypeDecl) {
1055 if decl.variants.len == 0 {
1056 return
1057 }
1058 name := if b.cur_module != '' && b.cur_module != 'main' {
1059 '${b.cur_module}__${decl.name}'
1060 } else {
1061 decl.name
1062 }
1063
1064 if name in b.struct_types {
1065 return
1066 }
1067
1068 i64_t := b.mod.type_store.get_int(64)
1069 type_id := b.mod.type_store.register(Type{
1070 kind: .struct_t
1071 fields: [i64_t, i64_t]
1072 field_names: ['_tag', '_data']
1073 })
1074 b.struct_types[name] = type_id
1075 b.mod.c_struct_names[type_id] = name
1076}
1077
1078fn (mut b Builder) register_consts_and_globals(file ast.File) {
1079 for stmt in file.stmts {
1080 match stmt {
1081 ast.ConstDecl {
1082 for field in stmt.fields {
1083 const_name := if b.cur_module != '' && b.cur_module != 'main' {
1084 '${b.cur_module}__${field.name}'
1085 } else {
1086 field.name
1087 }
1088 mut const_type := b.const_field_type(field.name, field.value)
1089 // Check if this is a string constant - store for inline resolution
1090 str_val := b.try_eval_const_string(field.value)
1091 if str_val.len > 0 {
1092 b.string_const_values[const_name] = str_val
1093 b.string_const_values[field.name] = str_val
1094 }
1095 // Check if this is a float constant - store for inline resolution
1096 if field.value is ast.BasicLiteral && field.value.kind == .number
1097 && (field.value.value.contains('.')
1098 || (!field.value.value.starts_with('0x')
1099 && !field.value.value.starts_with('0X')
1100 && (field.value.value.contains('e') || field.value.value.contains('E')))) {
1101 b.float_const_values[const_name] = field.value.value
1102 b.float_const_values[field.name] = field.value.value
1103 } else if b.is_float_cast_expr(field.value) {
1104 if fval := b.try_eval_computed_float(field.value) {
1105 fval_str := fval.str()
1106 b.float_const_values[const_name] = fval_str
1107 b.float_const_values[field.name] = fval_str
1108 }
1109 }
1110 initial_value := b.try_eval_const_int(field.value)
1111 // Detect sum type constants: these are multi-word values that
1112 // cannot be inlined as a single i64.
1113 // Case 1: Transformer succeeded → InitExpr{_tag: N, _data: ...}
1114 // Case 2: Transformer failed → CastExpr{typ: SumType, expr: ...}
1115 mut is_sumtype_const := false
1116 if field.value is ast.InitExpr {
1117 for init_field in field.value.fields {
1118 if init_field.name == '_tag' {
1119 is_sumtype_const = true
1120 break
1121 }
1122 }
1123 }
1124 if !is_sumtype_const {
1125 mut cast_type_name := ''
1126 if field.value is ast.CastExpr {
1127 if field.value.typ is ast.Ident {
1128 cast_type_name = field.value.typ.name
1129 }
1130 } else if field.value is ast.CallOrCastExpr {
1131 if field.value.lhs is ast.Ident {
1132 cast_type_name = field.value.lhs.name
1133 }
1134 }
1135 if cast_type_name != '' {
1136 qualified_cast := if b.cur_module != '' && b.cur_module != 'main' {
1137 '${b.cur_module}__${cast_type_name}'
1138 } else {
1139 cast_type_name
1140 }
1141 for _, check_name in [cast_type_name, qualified_cast] {
1142 if st_type := b.struct_types[check_name] {
1143 if int(st_type) < b.mod.type_store.types.len {
1144 st := b.mod.type_store.types[st_type]
1145 if st.kind == .struct_t && st.field_names.len >= 2
1146 && st.field_names[0] == '_tag' {
1147 is_sumtype_const = true
1148 // Fix the global type: use the sum type struct
1149 // instead of the i64 fallback from expr_type()
1150 const_type = st_type
1151 break
1152 }
1153 }
1154 }
1155 }
1156 }
1157 }
1158 // For sum type constants detected via InitExpr, also fix const_type
1159 if is_sumtype_const && field.value is ast.InitExpr {
1160 if field.value.typ is ast.Ident {
1161 type_name := field.value.typ.name
1162 if st_type := b.struct_types[type_name] {
1163 const_type = st_type
1164 } else {
1165 // Try with module prefix
1166 qualified_st := '${b.cur_module}__${type_name}'
1167 if st_type2 := b.struct_types[qualified_st] {
1168 const_type = st_type2
1169 }
1170 }
1171 }
1172 }
1173 // Detect constant FIXED arrays with all-literal elements.
1174 if field.value is ast.ArrayInitExpr && field.value.exprs.len > 0 {
1175 mut is_fixed_array := true
1176 // Check type environment to see if this is a dynamic array
1177 if b.env != unsafe { nil } {
1178 fpos := field.value.pos
1179 if fpos.id != 0 {
1180 if ct := b.env.get_expr_type(fpos.id) {
1181 if ct is types.Array {
1182 is_fixed_array = false
1183 }
1184 }
1185 }
1186 }
1187 arr_data := b.try_serialize_const_array(field.value)
1188 if arr_data.len > 0 {
1189 elem_size := arr_data.len / field.value.exprs.len
1190 is_float_arr := b.is_float_array(field.value)
1191 elem_type := if is_float_arr {
1192 b.mod.type_store.get_float(elem_size * 8)
1193 } else if elem_size == 8 {
1194 b.mod.type_store.get_int(64)
1195 } else if elem_size == 4 {
1196 b.mod.type_store.get_int(32)
1197 } else if elem_size == 2 {
1198 b.mod.type_store.get_int(16)
1199 } else {
1200 b.mod.type_store.get_int(8)
1201 }
1202 if is_fixed_array {
1203 b.mod.add_global_with_data(const_name, elem_type, true, arr_data)
1204 b.const_array_globals[const_name] = true
1205 b.const_array_globals[field.name] = true
1206 b.const_array_elem_count[const_name] = field.value.exprs.len
1207 b.const_array_elem_count[field.name] = field.value.exprs.len
1208 continue
1209 } else {
1210 // Dynamic array constant: serialize data, create array struct global
1211 data_name := '${const_name}__data'
1212 b.mod.add_global_with_data(data_name, elem_type, true, arr_data)
1213 b.const_array_globals[data_name] = true
1214 // Add array struct global (initialized in _vinit)
1215 arr_struct_type := b.get_array_type()
1216 b.mod.add_global(const_name, arr_struct_type, false)
1217 b.dyn_const_arrays << DynConstArray{
1218 arr_global_name: const_name
1219 data_global_name: data_name
1220 elem_count: field.value.exprs.len
1221 elem_size: elem_size
1222 }
1223 continue
1224 }
1225 }
1226 }
1227 // For float constants, store bit pattern as initial_value
1228 mut actual_init := initial_value
1229 if const_name in b.float_const_values {
1230 f_val := b.float_const_values[const_name].f64()
1231 actual_init = i64(unsafe { *(&i64(&f_val)) })
1232 const_type = b.mod.type_store.get_float(64)
1233 }
1234 b.mod.add_global_with_value(const_name, const_type, true, actual_init)
1235 if !is_sumtype_const && (initial_value != 0 || b.is_zero_literal(field.value)) {
1236 b.const_values[const_name] = initial_value
1237 b.const_value_types[const_name] = const_type
1238 // Also store without module prefix for transformer-generated references
1239 b.const_values[field.name] = initial_value
1240 b.const_value_types[field.name] = const_type
1241 }
1242 }
1243 }
1244 ast.GlobalDecl {
1245 for field in stmt.fields {
1246 glob_name := if b.cur_module != '' && b.cur_module != 'main' {
1247 '${b.cur_module}__${field.name}'
1248 } else {
1249 field.name
1250 }
1251 glob_type := if field.typ != ast.empty_expr {
1252 b.ast_type_to_ssa(field.typ)
1253 } else if field.value is ast.ArrayInitExpr && field.value.typ != ast.empty_expr {
1254 b.ast_type_to_ssa(field.value.typ)
1255 } else {
1256 b.mod.type_store.get_int(64)
1257 }
1258 initial_value := if field.value != ast.empty_expr {
1259 b.try_eval_const_int(field.value)
1260 } else {
1261 i64(0)
1262 }
1263 b.mod.add_global_with_value(glob_name, glob_type, false, initial_value)
1264 }
1265 }
1266 else {}
1267 }
1268 }
1269}
1270
1271// resolve_forward_const_refs re-evaluates constants that had value 0 due to forward references.
1272// After all constants are registered, references that previously failed can now be resolved.
1273fn (mut b Builder) resolve_forward_const_refs(files []ast.File) {
1274 for file in files {
1275 b.cur_module = file_module_name(file)
1276 for stmt in file.stmts {
1277 if stmt is ast.ConstDecl {
1278 for field in stmt.fields {
1279 const_name := if b.cur_module != '' && b.cur_module != 'main' {
1280 '${b.cur_module}__${field.name}'
1281 } else {
1282 field.name
1283 }
1284 // Skip constants that already have non-zero values
1285 if const_name in b.const_values {
1286 continue
1287 }
1288 // Skip zero literals (they're intentionally 0)
1289 if b.is_zero_literal(field.value) {
1290 continue
1291 }
1292 // Skip sum type constants (multi-word values stored as globals, not inline)
1293 if field.value is ast.InitExpr {
1294 mut has_tag := false
1295 for init_field in field.value.fields {
1296 if init_field.name == '_tag' {
1297 has_tag = true
1298 break
1299 }
1300 }
1301 if has_tag {
1302 continue
1303 }
1304 }
1305 // Re-evaluate the constant expression
1306 new_value := b.try_eval_const_int(field.value)
1307 if new_value != 0 {
1308 b.const_values[const_name] = new_value
1309 b.const_values[field.name] = new_value
1310 ct := b.const_field_type(field.name, field.value)
1311 b.const_value_types[const_name] = ct
1312 b.const_value_types[field.name] = ct
1313 // Update the global variable's initial value
1314 for i, g in b.mod.globals {
1315 if g.name == const_name {
1316 b.mod.globals[i] = GlobalVar{
1317 ...g
1318 initial_value: new_value
1319 }
1320 break
1321 }
1322 }
1323 }
1324 }
1325 }
1326 }
1327 }
1328}
1329
1330// resolve_const_int looks up a constant name in const_values, trying bare, module-qualified,
1331// and builtin-qualified names. Returns 0 if not found.
1332fn (b &Builder) resolve_const_int(name string) int {
1333 if name in b.const_values {
1334 return int(b.const_values[name])
1335 }
1336 qualified := '${b.cur_module}__${name}'
1337 if qualified in b.const_values {
1338 return int(b.const_values[qualified])
1339 }
1340 builtin_q := 'builtin__${name}'
1341 if builtin_q in b.const_values {
1342 return int(b.const_values[builtin_q])
1343 }
1344 return 0
1345}
1346
1347// try_eval_const_int attempts to evaluate a constant expression to an integer value.
1348// Returns 0 for expressions that cannot be evaluated at compile time.
1349fn (b &Builder) is_float_array(arr ast.ArrayInitExpr) bool {
1350 if arr.exprs.len > 0 {
1351 first := arr.exprs[0]
1352 if first is ast.CallOrCastExpr {
1353 if first.lhs is ast.Ident {
1354 return first.lhs.name in ['f32', 'f64']
1355 }
1356 } else if first is ast.CastExpr {
1357 if first.typ is ast.Ident {
1358 return first.typ.name in ['f32', 'f64']
1359 }
1360 }
1361 }
1362 return false
1363}
1364
1365// try_serialize_const_array attempts to serialize a constant array's elements to raw bytes.
1366// Returns the serialized data or empty if any element can't be evaluated at compile time.
1367fn (mut b Builder) try_serialize_const_array(arr ast.ArrayInitExpr) []u8 {
1368 if arr.exprs.len == 0 {
1369 return []u8{}
1370 }
1371 // Determine element size and whether it's a float array from the first element's type hint
1372 mut elem_size := 8 // default to 8 bytes (u64/i64)
1373 mut is_float := false
1374 // Check first element for type cast (e.g., u64(0x123), f64(0.5))
1375 first := arr.exprs[0]
1376 if first is ast.CallOrCastExpr {
1377 if first.lhs is ast.Ident {
1378 match first.lhs.name {
1379 'u8', 'i8', 'byte' {
1380 elem_size = 1
1381 }
1382 'u16', 'i16' {
1383 elem_size = 2
1384 }
1385 'u32', 'i32', 'int' {
1386 elem_size = 4
1387 }
1388 'f32' {
1389 elem_size = 4
1390 is_float = true
1391 }
1392 'u64', 'i64' {
1393 elem_size = 8
1394 }
1395 'f64' {
1396 elem_size = 8
1397 is_float = true
1398 }
1399 else {}
1400 }
1401 }
1402 } else if first is ast.CastExpr {
1403 if first.typ is ast.Ident {
1404 match first.typ.name {
1405 'u8', 'i8', 'byte' {
1406 elem_size = 1
1407 }
1408 'u16', 'i16' {
1409 elem_size = 2
1410 }
1411 'u32', 'i32', 'int' {
1412 elem_size = 4
1413 }
1414 'f32' {
1415 elem_size = 4
1416 is_float = true
1417 }
1418 'u64', 'i64' {
1419 elem_size = 8
1420 }
1421 'f64' {
1422 elem_size = 8
1423 is_float = true
1424 }
1425 else {}
1426 }
1427 }
1428 }
1429 mut data := []u8{cap: arr.exprs.len * elem_size}
1430 for ei, expr in arr.exprs {
1431 _ = ei
1432 if is_float {
1433 // For float arrays, parse as f64 and store IEEE 754 bits
1434 fval := b.try_eval_const_float(expr)
1435 if elem_size == 4 {
1436 fval32 := f32(fval)
1437 bits := unsafe { *(&u32(&fval32)) }
1438 data << u8(bits & 0xFF)
1439 data << u8((bits >> 8) & 0xFF)
1440 data << u8((bits >> 16) & 0xFF)
1441 data << u8((bits >> 24) & 0xFF)
1442 } else {
1443 bits := unsafe { *(&u64(&fval)) }
1444 data << u8(bits & 0xFF)
1445 data << u8((bits >> 8) & 0xFF)
1446 data << u8((bits >> 16) & 0xFF)
1447 data << u8((bits >> 24) & 0xFF)
1448 data << u8((bits >> 32) & 0xFF)
1449 data << u8((bits >> 40) & 0xFF)
1450 data << u8((bits >> 48) & 0xFF)
1451 data << u8((bits >> 56) & 0xFF)
1452 }
1453 } else {
1454 val := b.try_eval_const_int(expr)
1455 match elem_size {
1456 1 {
1457 data << u8(val)
1458 }
1459 2 {
1460 data << u8(val & 0xFF)
1461 data << u8((val >> 8) & 0xFF)
1462 }
1463 4 {
1464 data << u8(val & 0xFF)
1465 data << u8((val >> 8) & 0xFF)
1466 data << u8((val >> 16) & 0xFF)
1467 data << u8((val >> 24) & 0xFF)
1468 }
1469 else {
1470 v := u64(val)
1471 data << u8(v & 0xFF)
1472 data << u8((v >> 8) & 0xFF)
1473 data << u8((v >> 16) & 0xFF)
1474 data << u8((v >> 24) & 0xFF)
1475 data << u8((v >> 32) & 0xFF)
1476 data << u8((v >> 40) & 0xFF)
1477 data << u8((v >> 48) & 0xFF)
1478 data << u8((v >> 56) & 0xFF)
1479 }
1480 }
1481 }
1482 }
1483 return data
1484}
1485
1486// try_eval_const_float evaluates a compile-time float constant expression.
1487fn (b &Builder) try_eval_const_float(expr ast.Expr) f64 {
1488 match expr {
1489 ast.BasicLiteral {
1490 if expr.kind == .number {
1491 return expr.value.f64()
1492 }
1493 }
1494 ast.CallOrCastExpr {
1495 // f64(0.5), f32(1.0), etc.
1496 return b.try_eval_const_float(expr.expr)
1497 }
1498 ast.PrefixExpr {
1499 if expr.op == .minus {
1500 return -b.try_eval_const_float(expr.expr)
1501 }
1502 }
1503 else {}
1504 }
1505
1506 return 0.0
1507}
1508
1509// is_float_cast_expr returns true if the expression is a cast to a float type
1510// (e.g., f64(literal), f32(expr)), which should be evaluated as a float constant.
1511// This prevents pure integer expressions like `64 - 11 - 1` from being stored
1512// as float constants.
1513fn (b &Builder) is_float_cast_expr(expr ast.Expr) bool {
1514 if expr is ast.CastExpr {
1515 if expr.typ is ast.Ident {
1516 return expr.typ.name == 'f64' || expr.typ.name == 'f32'
1517 }
1518 }
1519 if expr is ast.CallOrCastExpr {
1520 if expr.lhs is ast.Ident {
1521 return expr.lhs.name == 'f64' || expr.lhs.name == 'f32'
1522 }
1523 }
1524 if expr is ast.BasicLiteral {
1525 if expr.kind == .number {
1526 return expr.value.contains('.')
1527 || (!expr.value.starts_with('0x') && !expr.value.starts_with('0X')
1528 && (expr.value.contains('e') || expr.value.contains('E')))
1529 }
1530 }
1531 // Check for InfixExpr/PrefixExpr involving float operations
1532 // (e.g., `1.0 / ln2` or `-0.5`)
1533 if expr is ast.PrefixExpr {
1534 return b.is_float_cast_expr(expr.expr)
1535 }
1536 if expr is ast.InfixExpr {
1537 return b.is_float_cast_expr(expr.lhs) || b.is_float_cast_expr(expr.rhs)
1538 }
1539 return false
1540}
1541
1542// try_eval_computed_float evaluates a constant expression to a float value.
1543// Handles computed float constants like `pi / 2.0`, `1.0 / ln2`, etc.
1544// Returns none if the expression can't be evaluated as a compile-time float.
1545fn (mut b Builder) try_eval_computed_float(expr ast.Expr) ?f64 {
1546 match expr {
1547 ast.BasicLiteral {
1548 if expr.kind == .number && expr.value.contains('.') {
1549 return expr.value.f64()
1550 }
1551 if expr.kind == .number {
1552 return f64(expr.value.i64())
1553 }
1554 return none
1555 }
1556 ast.Ident {
1557 // Look up the identifier in float_const_values
1558 if fval := b.float_const_values[expr.name] {
1559 return fval.f64()
1560 }
1561 qualified := '${b.cur_module}__${expr.name}'
1562 if fval := b.float_const_values[qualified] {
1563 return fval.f64()
1564 }
1565 return none
1566 }
1567 ast.InfixExpr {
1568 lhs := b.try_eval_computed_float(expr.lhs) or { return none }
1569 rhs := b.try_eval_computed_float(expr.rhs) or { return none }
1570 return match expr.op {
1571 .plus {
1572 lhs + rhs
1573 }
1574 .minus {
1575 lhs - rhs
1576 }
1577 .mul {
1578 lhs * rhs
1579 }
1580 .div {
1581 if rhs != 0.0 {
1582 lhs / rhs
1583 } else {
1584 f64(0.0)
1585 }
1586 }
1587 else {
1588 return none
1589 }
1590 }
1591 }
1592 ast.PrefixExpr {
1593 if expr.op == .minus {
1594 val := b.try_eval_computed_float(expr.expr) or { return none }
1595 return -val
1596 }
1597 return none
1598 }
1599 ast.CastExpr {
1600 // Only evaluate as float if casting to a float type (f64, f32)
1601 if expr.typ is ast.Ident && (expr.typ.name == 'f64' || expr.typ.name == 'f32') {
1602 return b.try_eval_computed_float(expr.expr)
1603 }
1604 return none
1605 }
1606 ast.CallOrCastExpr {
1607 // Only evaluate as float if casting to a float type (f64, f32)
1608 if expr.lhs is ast.Ident && (expr.lhs.name == 'f64' || expr.lhs.name == 'f32') {
1609 return b.try_eval_computed_float(expr.expr)
1610 }
1611 return none
1612 }
1613 else {
1614 return none
1615 }
1616 }
1617}
1618
1619fn parse_const_uint_literal(lit string) u64 {
1620 if lit.len == 0 {
1621 return 0
1622 }
1623 mut idx := 0
1624 if lit[idx] == `+` || lit[idx] == `-` {
1625 idx++
1626 }
1627 mut base := u64(10)
1628 if idx + 1 < lit.len && lit[idx] == `0` {
1629 match lit[idx + 1] {
1630 `x`, `X` {
1631 base = 16
1632 idx += 2
1633 }
1634 `o`, `O` {
1635 base = 8
1636 idx += 2
1637 }
1638 `b`, `B` {
1639 base = 2
1640 idx += 2
1641 }
1642 else {}
1643 }
1644 }
1645 mut val := u64(0)
1646 for idx < lit.len {
1647 ch := lit[idx]
1648 if ch == `_` {
1649 idx++
1650 continue
1651 }
1652 digit := match true {
1653 ch >= `0` && ch <= `9` { u64(ch - `0`) }
1654 base == 16 && ch >= `a` && ch <= `f` { u64(ch - `a` + 10) }
1655 base == 16 && ch >= `A` && ch <= `F` { u64(ch - `A` + 10) }
1656 else { break }
1657 }
1658
1659 if digit >= base {
1660 break
1661 }
1662 val = val * base + digit
1663 idx++
1664 }
1665 return val
1666}
1667
1668fn parse_const_int_literal(lit string) i64 {
1669 if lit.len == 0 {
1670 return 0
1671 }
1672 neg := lit[0] == `-`
1673 val := parse_const_uint_literal(lit)
1674 if neg {
1675 // Keep the conversion in unsigned space so `-9223372036854775808` stays intact
1676 // even in self-hosted ARM64 builds where string.i64()/u64() are unreliable.
1677 return i64(u64(0) - val)
1678 }
1679 return i64(val)
1680}
1681
1682fn (mut b Builder) try_eval_const_int(expr ast.Expr) i64 {
1683 match expr {
1684 ast.BasicLiteral {
1685 if expr.kind == .number {
1686 // Handle float notation (e.g., 1e10, 1.5e3) cast to integer
1687 // But skip hex values like 0x01e8480000000000 where 'e' is a hex digit
1688 if !expr.value.starts_with('0x') && !expr.value.starts_with('0X')
1689 && (expr.value.contains('e') || expr.value.contains('E')
1690 || expr.value.contains('.')) {
1691 return i64(expr.value.f64())
1692 }
1693 return parse_const_int_literal(expr.value)
1694 }
1695 if expr.kind == .key_true {
1696 return 1
1697 }
1698 if expr.kind == .key_false {
1699 return 0
1700 }
1701 if expr.kind == .char {
1702 return b.resolve_char_const_value(expr.value)
1703 }
1704 }
1705 ast.InfixExpr {
1706 lhs := b.try_eval_const_int(expr.lhs)
1707 rhs := b.try_eval_const_int(expr.rhs)
1708 result2 := match expr.op {
1709 .plus {
1710 lhs + rhs
1711 }
1712 .minus {
1713 lhs - rhs
1714 }
1715 .mul {
1716 lhs * rhs
1717 }
1718 .div {
1719 if rhs != 0 { lhs / rhs } else { i64(0) }
1720 }
1721 .mod {
1722 if rhs != 0 { lhs % rhs } else { i64(0) }
1723 }
1724 .left_shift {
1725 i64(u64(lhs) << u64(rhs))
1726 }
1727 .right_shift {
1728 i64(u64(lhs) >> u64(rhs))
1729 }
1730 .amp {
1731 lhs & rhs
1732 }
1733 .pipe {
1734 lhs | rhs
1735 }
1736 .xor {
1737 lhs ^ rhs
1738 }
1739 else {
1740 i64(0)
1741 }
1742 }
1743
1744 return result2
1745 }
1746 ast.PrefixExpr {
1747 val := b.try_eval_const_int(expr.expr)
1748 return match expr.op {
1749 .minus { -val }
1750 .bit_not { ~val }
1751 else { 0 }
1752 }
1753 }
1754 ast.Ident {
1755 // Try to resolve reference to another constant
1756 if expr.name in b.const_values {
1757 return b.const_values[expr.name]
1758 }
1759 qualified := '${b.cur_module}__${expr.name}'
1760 if qualified in b.const_values {
1761 return b.const_values[qualified]
1762 }
1763 builtin_qual := 'builtin__${expr.name}'
1764 if builtin_qual in b.const_values {
1765 return b.const_values[builtin_qual]
1766 }
1767 }
1768 ast.CastExpr {
1769 return b.try_eval_const_int(expr.expr)
1770 }
1771 ast.CallOrCastExpr {
1772 // V2 parser produces CallOrCastExpr for type casts like u32(52), u64(0xF)
1773 return b.try_eval_const_int(expr.expr)
1774 }
1775 ast.ParenExpr {
1776 return b.try_eval_const_int(expr.expr)
1777 }
1778 ast.InitExpr {
1779 // Handle sum type init: InitExpr{_tag: N, _data: ...}
1780 // Extract the _tag value so struct constants get correct tag in data section
1781 for field in expr.fields {
1782 if field.name == '_tag' {
1783 return b.try_eval_const_int(field.value)
1784 }
1785 }
1786 }
1787 else {}
1788 }
1789
1790 return 0
1791}
1792
1793fn (b &Builder) is_zero_literal(expr ast.Expr) bool {
1794 if expr is ast.BasicLiteral {
1795 return expr.kind == .number && expr.value == '0'
1796 }
1797 return false
1798}
1799
1800// resolve_char_const_value is like resolve_char_value but returns i64 for use in try_eval_const_int.
1801fn (b &Builder) resolve_char_const_value(val string) i64 {
1802 return i64(b.resolve_char_value(val))
1803}
1804
1805// resolve_char_value converts a V character literal value to its numeric byte value.
1806// Handles escape sequences like \n, \t, \r, \\, \', \0, and raw characters.
1807fn (b &Builder) resolve_char_value(val string) int {
1808 mut s := val
1809 // Strip surrounding quotes if present
1810 if s.len >= 2 && s[0] == `\`` && s[s.len - 1] == `\`` {
1811 s = s[1..s.len - 1]
1812 }
1813 if s.len >= 2 && ((s[0] == `'` && s[s.len - 1] == `'`) || (s[0] == `"` && s[s.len - 1] == `"`)) {
1814 s = s[1..s.len - 1]
1815 }
1816 if s.len == 0 {
1817 return 0
1818 }
1819 // Handle escape sequences
1820 if s.len >= 2 && s[0] == `\\` {
1821 return match s[1] {
1822 `n` { 10 }
1823 `t` { 9 }
1824 `r` { 13 }
1825 `\\` { 92 }
1826 `'` { 39 }
1827 `"` { 34 }
1828 `0` { 0 }
1829 `a` { 7 }
1830 `b` { 8 }
1831 `f` { 12 }
1832 `v` { 11 }
1833 `e` { 27 }
1834 else { int(s[1]) }
1835 }
1836 }
1837 // Multi-byte UTF-8 character → decode to Unicode code point
1838 if s.len >= 2 && s[0] >= 0xC0 {
1839 return utf8_to_codepoint(s)
1840 }
1841 // Single ASCII character
1842 return int(s[0])
1843}
1844
1845// utf8_to_codepoint decodes the first UTF-8 character in s to its Unicode code point.
1846fn utf8_to_codepoint(s string) int {
1847 if s.len == 0 {
1848 return 0
1849 }
1850 b0 := s[0]
1851 if b0 < 0x80 {
1852 return int(b0)
1853 }
1854 if b0 < 0xE0 && s.len >= 2 {
1855 return int(u32(b0 & 0x1F) << 6 | u32(s[1] & 0x3F))
1856 }
1857 if b0 < 0xF0 && s.len >= 3 {
1858 return int(u32(b0 & 0x0F) << 12 | u32(s[1] & 0x3F) << 6 | u32(s[2] & 0x3F))
1859 }
1860 if s.len >= 4 {
1861 return int(u32(b0 & 0x07) << 18 | u32(s[1] & 0x3F) << 12 | u32(s[2] & 0x3F) << 6 | u32(s[3] & 0x3F))
1862 }
1863 return int(b0)
1864}
1865
1866// try_eval_const_string attempts to extract a string value from a constant expression.
1867// Returns empty string if the expression is not a string literal.
1868fn (b &Builder) try_eval_const_string(expr ast.Expr) string {
1869 match expr {
1870 ast.StringLiteral {
1871 mut val := expr.value
1872 // Strip surrounding quotes if present
1873 if val.len >= 2 && ((val[0] == `'` && val[val.len - 1] == `'`)
1874 || (val[0] == `"` && val[val.len - 1] == `"`)) {
1875 val = val[1..val.len - 1]
1876 }
1877 return val
1878 }
1879 ast.BasicLiteral {
1880 if expr.kind == .string {
1881 mut val := expr.value
1882 if val.len >= 2 && ((val[0] == `'` && val[val.len - 1] == `'`)
1883 || (val[0] == `"` && val[val.len - 1] == `"`)) {
1884 val = val[1..val.len - 1]
1885 }
1886 return val
1887 }
1888 }
1889 else {}
1890 }
1891
1892 return ''
1893}
1894
1895fn (mut b Builder) ast_type_to_ssa(typ ast.Expr) TypeID {
1896 match typ {
1897 ast.Ident {
1898 return b.ident_type_to_ssa(typ.name)
1899 }
1900 ast.Type {
1901 return b.ast_type_node_to_ssa(typ)
1902 }
1903 ast.PrefixExpr {
1904 if typ.op == .amp {
1905 base := b.ast_type_to_ssa(typ.expr)
1906 return b.mod.type_store.get_ptr(base)
1907 }
1908 if typ.op == .ellipsis {
1909 // Variadic params (...T) are lowered to []T (dynamic array)
1910 return b.get_array_type()
1911 }
1912 return b.mod.type_store.get_int(64)
1913 }
1914 ast.ModifierExpr {
1915 // mut/shared receivers: unwrap the modifier, the pointer is added by the caller
1916 return b.ast_type_to_ssa(typ.expr)
1917 }
1918 ast.SelectorExpr {
1919 // module.Type — e.g., C.dirent, os.Stat
1920 if typ.lhs is ast.Ident {
1921 mod_name := typ.lhs.name
1922 full_name := '${mod_name}.${typ.rhs.name}'
1923 // Try C.StructName → look up as module__C.StructName
1924 qualified := '${b.cur_module}__${full_name}'
1925 if qualified in b.struct_types {
1926 return b.struct_types[qualified]
1927 }
1928 // Also try just the full name (e.g., C.dirent)
1929 if full_name in b.struct_types {
1930 return b.struct_types[full_name]
1931 }
1932 // Try module__StructName (for module.Type references like os.Stat)
1933 mod_qualified := '${mod_name}__${typ.rhs.name}'
1934 if mod_qualified in b.struct_types {
1935 return b.struct_types[mod_qualified]
1936 }
1937 // For C.X types: C structs are registered under their declaring module
1938 // (e.g., C.dirent in os module → "os__dirent")
1939 // Try cur_module__StructName
1940 if mod_name == 'C' {
1941 cur_qualified := '${b.cur_module}__${typ.rhs.name}'
1942 if cur_qualified in b.struct_types {
1943 return b.struct_types[cur_qualified]
1944 }
1945 // Search all modules for this C struct
1946 for sname, sid in b.struct_types {
1947 if sname.ends_with('__${typ.rhs.name}') {
1948 return sid
1949 }
1950 }
1951 }
1952 // Try looking up in the referenced module's scope via type environment
1953 // (e.g., token.Token where Token is an enum, not a struct)
1954 if b.env != unsafe { nil } {
1955 mod_name_v := mod_name.replace('.', '_')
1956 if scope := b.env.get_scope(mod_name_v) {
1957 if obj := scope.lookup_parent(typ.rhs.name, 0) {
1958 return b.type_to_ssa(obj.typ())
1959 }
1960 }
1961 }
1962 }
1963 return b.ident_type_to_ssa(typ.rhs.name)
1964 }
1965 ast.EmptyExpr {
1966 return 0 // void
1967 }
1968 ast.Tuple {
1969 mut elem_types := []TypeID{cap: typ.exprs.len}
1970 for e in typ.exprs {
1971 elem_types << b.ast_type_to_ssa(e)
1972 }
1973 return b.mod.type_store.get_tuple(elem_types)
1974 }
1975 else {
1976 return b.mod.type_store.get_int(64)
1977 }
1978 }
1979}
1980
1981fn (mut b Builder) ast_type_node_to_ssa(typ ast.Type) TypeID {
1982 match typ {
1983 ast.ArrayType {
1984 return b.get_array_type()
1985 }
1986 ast.ArrayFixedType {
1987 // [N]T → SSA array type with N elements of T
1988 elem_type := b.ast_type_to_ssa(typ.elem_type)
1989 arr_len := if typ.len is ast.BasicLiteral {
1990 int(parse_const_int_literal(typ.len.value))
1991 } else if typ.len is ast.Ident {
1992 b.resolve_const_int(typ.len.name)
1993 } else {
1994 0
1995 }
1996 if arr_len > 0 {
1997 return b.mod.type_store.get_array(elem_type, arr_len)
1998 }
1999 return b.mod.type_store.get_int(64) // fallback
2000 }
2001 ast.MapType {
2002 return b.struct_types['map'] or { b.mod.type_store.get_int(64) }
2003 }
2004 ast.FnType {
2005 i8_t := b.mod.type_store.get_int(8)
2006 return b.mod.type_store.get_ptr(i8_t) // fn pointers
2007 }
2008 ast.OptionType {
2009 return b.get_option_wrapper_type(b.ast_type_to_ssa(typ.base_type))
2010 }
2011 ast.ResultType {
2012 return b.get_result_wrapper_type(b.ast_type_to_ssa(typ.base_type))
2013 }
2014 ast.PointerType {
2015 base := b.ast_type_to_ssa(typ.base_type)
2016 return b.mod.type_store.get_ptr(base)
2017 }
2018 ast.TupleType {
2019 mut elem_types := []TypeID{cap: typ.types.len}
2020 for t in typ.types {
2021 elem_types << b.ast_type_to_ssa(t)
2022 }
2023 return b.mod.type_store.get_tuple(elem_types)
2024 }
2025 else {
2026 return b.mod.type_store.get_int(64)
2027 }
2028 }
2029}
2030
2031fn (mut b Builder) ident_type_to_ssa(name string) TypeID {
2032 return match name {
2033 'int' {
2034 b.mod.type_store.get_int(32)
2035 }
2036 'i8' {
2037 b.mod.type_store.get_int(8)
2038 }
2039 'i16' {
2040 b.mod.type_store.get_int(16)
2041 }
2042 'i32' {
2043 b.mod.type_store.get_int(32)
2044 }
2045 'i64' {
2046 b.mod.type_store.get_int(64)
2047 }
2048 'u8', 'byte' {
2049 b.mod.type_store.get_uint(8)
2050 }
2051 'u16' {
2052 b.mod.type_store.get_uint(16)
2053 }
2054 'u32' {
2055 b.mod.type_store.get_uint(32)
2056 }
2057 'u64' {
2058 b.mod.type_store.get_uint(64)
2059 }
2060 'f32' {
2061 b.mod.type_store.get_float(32)
2062 }
2063 'f64' {
2064 b.mod.type_store.get_float(64)
2065 }
2066 'bool' {
2067 b.mod.type_store.get_int(1)
2068 }
2069 'string' {
2070 b.get_string_type()
2071 }
2072 'voidptr' {
2073 i8_t := b.mod.type_store.get_int(8)
2074 b.mod.type_store.get_ptr(i8_t)
2075 }
2076 'rune' {
2077 b.mod.type_store.get_int(32)
2078 }
2079 'char' {
2080 b.mod.type_store.get_int(8)
2081 }
2082 else {
2083 // Pointer types must be checked FIRST: `Array_int*` in a CastExpr means
2084 // "pointer to array struct" (used by sumtype smartcast data access),
2085 // NOT "array of &int". The non-pointer `Array_int` (without `*`) is the
2086 // array struct itself.
2087 if name.ends_with('*') {
2088 // Check for pointer types (e.g., 'StructType*', 'int*', 'Array_int*')
2089 base_name := name[..name.len - 1]
2090 base_type := b.ident_type_to_ssa(base_name)
2091 return b.mod.type_store.get_ptr(base_type)
2092 } else if name.starts_with('Array_fixed_') {
2093 b.fixed_array_type_from_name(name)
2094 } else if name.starts_with('Array_') {
2095 // Array_* are transformer-generated mangled names for []T.
2096 // They always represent the builtin array struct.
2097 b.get_array_type()
2098 } else if name.starts_with('Map_') {
2099 b.struct_types['map'] or { b.mod.type_store.get_int(64) }
2100 } else if name in b.struct_types {
2101 // Check struct types
2102 b.struct_types[name]
2103 } else if name == 'strings__Builder' {
2104 // strings.Builder = []u8 = array (type alias)
2105 b.get_array_type()
2106 } else if name == 'Builder' {
2107 // Builder could be strings.Builder alias or an actual struct
2108 // Try module-qualified first, fall back to array alias
2109 qualified_b := '${b.cur_module}__Builder'
2110 if qualified_b in b.struct_types {
2111 b.struct_types[qualified_b]
2112 } else {
2113 b.get_array_type()
2114 }
2115 } else {
2116 // Try module-qualified
2117 qualified := '${b.cur_module}__${name}'
2118 if qualified in b.struct_types {
2119 b.struct_types[qualified]
2120 } else if b.is_enum_type(name) || b.is_enum_type(qualified) {
2121 // Enum types are always int (i32) in V
2122 b.mod.type_store.get_int(32)
2123 } else if b.env != unsafe { nil } {
2124 // Use the type checker environment to resolve aliases and other types.
2125 // First try the current module scope, then try the module prefix in the name
2126 // (e.g., 'ssa__BlockID' → look up 'BlockID' in 'ssa' module).
2127 mut resolved := false
2128 mut resolved_type := TypeID(0)
2129 if scope := b.env.get_scope(b.cur_module) {
2130 if obj := scope.lookup_parent(name, 0) {
2131 resolved_type = b.type_to_ssa(obj.typ())
2132 resolved = true
2133 }
2134 }
2135 if !resolved && name.contains('__') {
2136 // Try module-prefixed name: 'ssa__BlockID' → module='ssa', type='BlockID'
2137 parts := name.split('__')
2138 if parts.len >= 2 {
2139 mod_name := parts[0]
2140 type_name := parts[1..].join('__')
2141 if mod_scope := b.env.get_scope(mod_name) {
2142 if obj := mod_scope.lookup_parent(type_name, 0) {
2143 resolved_type = b.type_to_ssa(obj.typ())
2144 resolved = true
2145 }
2146 }
2147 }
2148 }
2149 if resolved && resolved_type != 0 {
2150 resolved_type
2151 } else {
2152 b.mod.type_store.get_int(64)
2153 }
2154 } else {
2155 b.mod.type_store.get_int(64)
2156 }
2157 }
2158 }
2159 }
2160}
2161
2162fn (mut b Builder) fixed_array_type_from_name(name string) TypeID {
2163 if !name.starts_with('Array_fixed_') {
2164 return b.mod.type_store.get_int(64)
2165 }
2166 payload := name['Array_fixed_'.len..]
2167 len_str := payload.all_after_last('_')
2168 arr_len := len_str.int()
2169 if arr_len <= 0 {
2170 return b.mod.type_store.get_int(64)
2171 }
2172 elem_name := payload.all_before_last('_')
2173 elem_type := b.ident_type_to_ssa(elem_name)
2174 if elem_type == 0 {
2175 return b.mod.type_store.get_int(64)
2176 }
2177 return b.mod.type_store.get_array(elem_type, arr_len)
2178}
2179
2180// --- Phase 2: Register function signatures ---
2181
2182fn (mut b Builder) register_fn_signatures(file ast.File) {
2183 for stmt in file.stmts {
2184 if stmt is ast.FnDecl {
2185 if stmt.typ.generic_params.len > 0 {
2186 continue
2187 }
2188 b.register_fn_sig(stmt)
2189 }
2190 }
2191}
2192
2193fn (mut b Builder) register_fn_sig(decl ast.FnDecl) {
2194 fn_name := b.mangle_fn_name(decl)
2195 if fn_name in b.fn_index {
2196 return
2197 }
2198
2199 ret_type := b.ast_type_to_ssa(decl.typ.return_type)
2200 idx := b.mod.new_function(fn_name, ret_type, []TypeID{})
2201 b.fn_index[fn_name] = idx
2202 if decl.language == .c {
2203 b.mod.func_set_c_extern(idx, true)
2204 }
2205
2206 // Register parameter types for correct forward declarations.
2207 // For methods, add receiver as the first parameter.
2208 // Skip for static methods (is_static=true) — they have no receiver in the call.
2209 if decl.is_method && !decl.is_static {
2210 recv_type := b.ast_type_to_ssa(decl.receiver.typ)
2211 // For &Type (PrefixExpr), ast_type_to_ssa already returns ptr(Type).
2212 // For mut receivers, the parser sets is_mut on the Parameter (not ModifierExpr),
2213 // so we need to add the pointer level.
2214 actual_type := if decl.receiver.is_mut {
2215 b.mod.type_store.get_ptr(recv_type)
2216 } else {
2217 recv_type
2218 }
2219 receiver_name := if decl.receiver.name != '' {
2220 decl.receiver.name
2221 } else {
2222 'self'
2223 }
2224 param_val := b.mod.add_value_node(.argument, actual_type, receiver_name, 0)
2225 b.mod.func_add_param(idx, param_val)
2226 }
2227 for param in decl.typ.params {
2228 param_type := b.ast_type_to_ssa(param.typ)
2229 // For `mut` params, add a pointer level for pass-by-reference semantics,
2230 // but NOT if the type is already a pointer (e.g., `mut buf &u8` → param is already ptr(i8)).
2231 // In C, `mut buf &u8` is just `u8* buf`, not `u8** buf`.
2232 actual_type := if param.is_mut && !(param_type < b.mod.type_store.types.len
2233 && b.mod.type_store.types[param_type].kind == .ptr_t) {
2234 b.mod.type_store.get_ptr(param_type)
2235 } else {
2236 param_type
2237 }
2238 param_val := b.mod.add_value_node(.argument, actual_type, param.name, 0)
2239 b.mod.func_add_param(idx, param_val)
2240 }
2241}
2242
2243fn (mut b Builder) mangle_fn_name(decl ast.FnDecl) string {
2244 if decl.is_method {
2245 receiver_name := b.receiver_type_name(decl.receiver.typ)
2246 return '${receiver_name}__${decl.name}'
2247 }
2248 // C functions use their bare name (no module prefix)
2249 if decl.language == .c {
2250 return decl.name
2251 }
2252 if decl.name == 'main' {
2253 return 'main'
2254 }
2255 if b.cur_module != '' && b.cur_module != 'main' {
2256 // Don't add module prefix if name already starts with it (e.g., generated
2257 // enum str functions placed back in their source module).
2258 if decl.name.starts_with('${b.cur_module}__') {
2259 return decl.name
2260 }
2261 return '${b.cur_module}__${decl.name}'
2262 }
2263 return decl.name
2264}
2265
2266fn (mut b Builder) receiver_type_name(typ ast.Expr) string {
2267 match typ {
2268 ast.Ident {
2269 if b.cur_module != '' && b.cur_module != 'main' {
2270 return '${b.cur_module}__${typ.name}'
2271 }
2272 return typ.name
2273 }
2274 ast.PrefixExpr {
2275 return b.receiver_type_name(typ.expr)
2276 }
2277 ast.ModifierExpr {
2278 return b.receiver_type_name(typ.expr)
2279 }
2280 ast.SelectorExpr {
2281 return '${typ.lhs.name()}__${typ.rhs.name}'
2282 }
2283 ast.Type {
2284 // Handle type expressions used as receivers (e.g., []rune for (ra []rune) string())
2285 inner := ast.Type(typ)
2286 if inner is ast.PointerType {
2287 return b.receiver_type_name(inner.base_type)
2288 }
2289 if inner is ast.ArrayType {
2290 // []rune → Array_rune, []int → Array_int, etc.
2291 elem_name := if inner.elem_type is ast.Ident {
2292 inner.elem_type.name
2293 } else {
2294 b.receiver_type_name(inner.elem_type)
2295 }
2296 prefix := if b.cur_module != '' && b.cur_module != 'main' {
2297 '${b.cur_module}__'
2298 } else {
2299 ''
2300 }
2301 return '${prefix}Array_${elem_name}'
2302 }
2303 return 'unknown'
2304 }
2305 else {
2306 return 'unknown'
2307 }
2308 }
2309}
2310
2311// --- Phase 3: Build function bodies ---
2312
2313pub fn (mut b Builder) build_fn_bodies(file ast.File) {
2314 nstmts := file.stmts.len
2315 for si in 0 .. nstmts {
2316 if file.stmts[si] is ast.FnDecl {
2317 decl := file.stmts[si] as ast.FnDecl
2318 if decl.language == .c && decl.stmts.len == 0 {
2319 continue
2320 }
2321 if decl.typ.generic_params.len > 0 {
2322 continue
2323 }
2324 // In hot_fn mode, only build the target function
2325 if b.hot_fn.len > 0 {
2326 mangled := b.mangle_fn_name(decl)
2327 if mangled != b.hot_fn {
2328 continue
2329 }
2330 }
2331 // Dead code elimination: skip functions not reachable from main
2332 if !b.should_build_fn(file.name, decl) {
2333 continue
2334 }
2335 b.build_fn(decl)
2336 }
2337 }
2338}
2339
2340// should_build_fn returns true if the function should be compiled.
2341// When used_fn_keys is populated (markused ran), only reachable functions are built.
2342pub fn (mut b Builder) should_build_fn(file_name string, decl ast.FnDecl) bool {
2343 // Skip entire modules for unused backends (e.g., cleanc/eval/x64 when building arm64-only)
2344 if b.skip_modules.len > 0 && b.cur_module in b.skip_modules {
2345 return false
2346 }
2347 if b.used_fn_keys.len == 0 {
2348 return true // No markused data — build everything
2349 }
2350 // Always build init_consts, init, deinit, main
2351 if decl.name.starts_with('__v_init_consts_') {
2352 return true
2353 }
2354 if decl.name == 'init' || decl.name == 'deinit' {
2355 return true
2356 }
2357 if decl.name == 'main' {
2358 return true
2359 }
2360 // Always build functions from core modules that the runtime needs
2361 if b.cur_module in ['builtin', 'strings', 'strconv', 'bits', 'sha256', 'binary'] {
2362 return true
2363 }
2364 // Always build .vh header declarations
2365 if file_name.ends_with('.vh') {
2366 return true
2367 }
2368 // Keep transformer-generated array/map method specializations
2369 if decl.is_method {
2370 mangled := b.mangle_fn_name(decl)
2371 if mangled.contains('__Array_') || mangled.contains('__Map_') {
2372 return true
2373 }
2374 }
2375 // Check markused reachability
2376 key := markused.decl_key(b.cur_module, decl, b.env)
2377 return key in b.used_fn_keys
2378}
2379
2380pub fn (mut b Builder) build_fn(decl ast.FnDecl) {
2381 fn_name := b.mangle_fn_name(decl)
2382 // Skip C-language extern functions without bodies
2383 if decl.language == .c {
2384 return
2385 }
2386 func_idx := b.fn_index[fn_name] or { return }
2387 // Skip if already built (can happen with .c.v and .v files).
2388 // Exception: user-defined methods always override auto-generated non-method functions
2389 // (e.g., custom Token.str() overrides auto-generated token__Token__str enum str).
2390 if b.mod.funcs[func_idx].blocks.len > 0 {
2391 if decl.is_method && decl.stmts.len > 0 {
2392 // Method with a body overrides previously-built auto-generated function.
2393 // Clear blocks so we can rebuild with the real method body.
2394 b.mod.func_clear_blocks(func_idx)
2395 } else {
2396 return
2397 }
2398 }
2399
2400 // Skip functions without a body (e.g., extern declarations).
2401 // Build function bodies for ALL modules so cross-module calls work at runtime.
2402 if decl.stmts.len == 0 {
2403 // Emit a minimal function body (entry + ret) so backends have a valid function
2404 b.cur_func = func_idx
2405 entry := b.mod.add_block(func_idx, 'entry')
2406 b.cur_block = entry
2407 ret_type := b.mod.funcs[func_idx].typ
2408 if ret_type != 0 {
2409 ret_type_info := b.mod.type_store.types[ret_type]
2410 if ret_type_info.kind == .struct_t {
2411 // For struct return types, alloca + zero init + load
2412 ptr_type := b.mod.type_store.get_ptr(ret_type)
2413 alloca := b.mod.add_instr(.alloca, b.cur_block, ptr_type, []ValueID{})
2414 ret_val := b.mod.add_instr(.load, b.cur_block, ret_type, [alloca])
2415 b.mod.add_instr(.ret, b.cur_block, 0, [ret_val])
2416 } else {
2417 zero := b.mod.get_or_add_const(ret_type, '0')
2418 b.mod.add_instr(.ret, b.cur_block, 0, [zero])
2419 }
2420 } else {
2421 b.mod.add_instr(.ret, b.cur_block, 0, []ValueID{})
2422 }
2423 return
2424 }
2425
2426 b.cur_func = func_idx
2427
2428 // Reset local variables
2429 b.vars = map[string]ValueID{}
2430 b.mut_ptr_params = map[string]bool{}
2431 b.label_blocks = map[string]BlockID{}
2432 b.array_elem_types = map[string]TypeID{}
2433
2434 // Clear params (they were registered in register_fn_sig for forward decls,
2435 // but we need to re-create them here with proper alloca bindings)
2436 b.mod.func_set_params(func_idx, []ValueID{})
2437
2438 // Create entry block
2439 entry := b.mod.add_block(func_idx, 'entry')
2440 b.cur_block = entry
2441
2442 // Add parameters
2443 // Skip receiver for static methods (is_static=true) — no receiver in call
2444 if decl.is_method && !decl.is_static {
2445 // Receiver is the first parameter
2446 receiver_name := if decl.receiver.name != '' {
2447 decl.receiver.name
2448 } else {
2449 'self'
2450 }
2451 recv_type := b.ast_type_to_ssa(decl.receiver.typ)
2452 // For &Type (PrefixExpr), ast_type_to_ssa already returns ptr(Type).
2453 // For mut receivers, the parser sets is_mut on the Parameter (not ModifierExpr),
2454 // so we need to add the pointer level.
2455 actual_type := if decl.receiver.is_mut {
2456 b.mod.type_store.get_ptr(recv_type)
2457 } else {
2458 recv_type
2459 }
2460 param_val := b.mod.add_value_node(.argument, actual_type, receiver_name, 0)
2461 b.mod.func_add_param(func_idx, param_val)
2462 // Alloca + store for receiver
2463 alloca := b.mod.add_instr(.alloca, entry, b.mod.type_store.get_ptr(actual_type),
2464 []ValueID{})
2465 b.mod.add_instr(.store, entry, 0, [param_val, alloca])
2466 b.vars[receiver_name] = alloca
2467 }
2468
2469 for param in decl.typ.params {
2470 param_type := b.ast_type_to_ssa(param.typ)
2471 // For `mut` params, add pointer level only if the type isn't already a pointer.
2472 // `mut buf &u8` → param_type is ptr(i8), no extra level needed (like C: u8* buf).
2473 // `mut val int` → param_type is i32, needs ptr(i32) for pass-by-reference.
2474 is_already_ptr := param_type < b.mod.type_store.types.len
2475 && b.mod.type_store.types[param_type].kind == .ptr_t
2476 actual_type := if param.is_mut && !is_already_ptr {
2477 b.mod.type_store.get_ptr(param_type)
2478 } else {
2479 param_type
2480 }
2481 param_val := b.mod.add_value_node(.argument, actual_type, param.name, 0)
2482 b.mod.func_add_param(func_idx, param_val)
2483 // Alloca + store
2484 alloca := b.mod.add_instr(.alloca, entry, b.mod.type_store.get_ptr(actual_type),
2485 []ValueID{})
2486 b.mod.add_instr(.store, entry, 0, [param_val, alloca])
2487 b.vars[param.name] = alloca
2488
2489 // Track array element types for transformer-generated functions (no checker info).
2490 // E.g., param 'a' with type 'Array_int' → element type is 'int' → i32.
2491 if param.typ is ast.Ident {
2492 param_type_name := param.typ.name
2493 if param_type_name.starts_with('Array_') {
2494 elem_name := param_type_name['Array_'.len..]
2495 elem_ssa := b.ident_type_to_ssa(elem_name)
2496 if elem_ssa != 0 {
2497 b.array_elem_types[param.name] = elem_ssa
2498 }
2499 }
2500 }
2501 }
2502
2503 // Build body
2504 b.build_stmts(decl.stmts)
2505
2506 // If no terminator, add implicit return
2507 if !b.block_has_terminator(b.cur_block) {
2508 if fn_name == 'main' {
2509 zero := b.mod.get_or_add_const(b.mod.type_store.get_int(32), '0')
2510 b.mod.add_instr(.ret, b.cur_block, 0, [zero])
2511 } else {
2512 b.mod.add_instr(.ret, b.cur_block, 0, []ValueID{})
2513 }
2514 }
2515}
2516
2517fn (mut b Builder) is_mut_receiver(typ ast.Expr) bool {
2518 if typ is ast.ModifierExpr {
2519 return typ.kind == .key_mut || typ.kind == .key_shared
2520 }
2521 if typ is ast.PrefixExpr {
2522 return typ.op == .amp
2523 }
2524 return false
2525}
2526
2527fn (mut b Builder) block_has_terminator(block BlockID) bool {
2528 instrs := b.mod.blocks[block].instrs
2529 if instrs.len == 0 {
2530 return false
2531 }
2532 last := instrs[instrs.len - 1]
2533 last_instr := b.mod.instrs[b.mod.values[last].index]
2534 return last_instr.op in [.ret, .br, .jmp, .unreachable]
2535}
2536
2537// --- Statement building ---
2538
2539fn (mut b Builder) build_stmts(stmts []ast.Stmt) {
2540 for stmt in stmts {
2541 b.build_stmt(stmt)
2542 }
2543}
2544
2545fn (mut b Builder) build_stmt(stmt ast.Stmt) {
2546 match stmt {
2547 ast.AssignStmt {
2548 b.build_assign(stmt)
2549 }
2550 ast.ExprStmt {
2551 b.build_expr_stmt(stmt)
2552 }
2553 ast.ReturnStmt {
2554 b.build_return(stmt)
2555 }
2556 ast.ForStmt {
2557 b.build_for(stmt)
2558 }
2559 ast.FlowControlStmt {
2560 b.build_flow_control(stmt)
2561 }
2562 ast.BlockStmt {
2563 b.build_stmts(stmt.stmts)
2564 }
2565 ast.LabelStmt {
2566 b.build_label(stmt)
2567 }
2568 ast.ModuleStmt {
2569 b.cur_module = stmt.name.replace('.', '_')
2570 }
2571 ast.ImportStmt {}
2572 ast.ConstDecl {}
2573 ast.StructDecl {}
2574 ast.EnumDecl {}
2575 ast.TypeDecl {}
2576 ast.InterfaceDecl {}
2577 ast.GlobalDecl {}
2578 ast.FnDecl {} // nested fn decls handled separately
2579 ast.Directive {}
2580 ast.ComptimeStmt {}
2581 ast.DeferStmt {}
2582 ast.AssertStmt {
2583 b.build_assert(stmt)
2584 }
2585 []ast.Attribute {}
2586 ast.EmptyStmt {}
2587 ast.AsmStmt {}
2588 ast.ForInStmt {
2589 panic('SSA builder: ForInStmt should have been lowered by transformer')
2590 }
2591 }
2592}
2593
2594fn (mut b Builder) build_expr_stmt(stmt ast.ExprStmt) {
2595 if stmt.expr is ast.UnsafeExpr {
2596 for s in stmt.expr.stmts {
2597 b.build_stmt(s)
2598 }
2599 return
2600 }
2601 if stmt.expr is ast.IfExpr {
2602 b.build_if_stmt(stmt.expr)
2603 return
2604 }
2605 b.build_expr(stmt.expr)
2606}
2607
2608fn (mut b Builder) unwrap_ident(expr ast.Expr) ?ast.Ident {
2609 if expr is ast.Ident {
2610 return expr
2611 }
2612 if expr is ast.ModifierExpr {
2613 return b.unwrap_ident(expr.expr)
2614 }
2615 return none
2616}
2617
2618fn (mut b Builder) build_assign(stmt ast.AssignStmt) {
2619 // Multi-return decomposition: when LHS has more vars than RHS,
2620 // the single RHS is a function call returning a tuple.
2621 if stmt.lhs.len > 1 && stmt.rhs.len == 1 {
2622 mut rhs_val := b.build_expr(stmt.rhs[0])
2623 mut rhs_typ_id := b.mod.values[rhs_val].typ
2624 mut rhs_typ := b.mod.type_store.types[rhs_typ_id]
2625 // If RHS is an Option/Result wrapper, unwrap the data field first.
2626 // The transformer strips the ? postfix in tuple destructuring, leaving
2627 // the raw wrapper struct. Extract .data (field 2) to get the inner tuple.
2628 if b.is_option_wrapper_type(rhs_typ_id) || b.is_result_wrapper_type(rhs_typ_id) {
2629 if b.wrapper_has_data(rhs_typ_id) && rhs_typ.kind == .struct_t
2630 && rhs_typ.fields.len >= 3 {
2631 data_type := rhs_typ.fields[2]
2632 data_idx := b.mod.get_or_add_const(b.mod.type_store.get_int(32), '2')
2633 rhs_val = b.mod.add_instr(.extractvalue, b.cur_block, data_type, [
2634 rhs_val,
2635 data_idx,
2636 ])
2637 rhs_typ_id = data_type
2638 rhs_typ = b.mod.type_store.types[rhs_typ_id]
2639 }
2640 }
2641 for i, lhs in stmt.lhs {
2642 // Extract each element from the tuple
2643 mut elem_val := rhs_val
2644 if rhs_typ.kind == .struct_t && i < rhs_typ.fields.len {
2645 elem_type := rhs_typ.fields[i]
2646 idx_val := b.mod.get_or_add_const(b.mod.type_store.get_int(32), i.str())
2647 elem_val = b.mod.add_instr(.extractvalue, b.cur_block, elem_type, [
2648 rhs_val,
2649 idx_val,
2650 ])
2651 }
2652 if ident := b.unwrap_ident(lhs) {
2653 if stmt.op == .decl_assign {
2654 elem_type := b.mod.values[elem_val].typ
2655 alloca := b.mod.add_instr(.alloca, b.cur_block,
2656 b.mod.type_store.get_ptr(elem_type), []ValueID{})
2657 b.mod.add_instr(.store, b.cur_block, 0, [elem_val, alloca])
2658 b.vars[ident.name] = alloca
2659 } else if ident.name == '_' {
2660 continue
2661 } else {
2662 mut ptr := ValueID(0)
2663 if p := b.vars[ident.name] {
2664 ptr = p
2665 } else if glob_id := b.find_global(ident.name) {
2666 ptr = glob_id
2667 } else if glob_id := b.find_global('${b.cur_module}__${ident.name}') {
2668 ptr = glob_id
2669 } else if glob_id := b.find_global('builtin__${ident.name}') {
2670 ptr = glob_id
2671 }
2672 if ptr != 0 {
2673 b.mod.add_instr(.store, b.cur_block, 0, [elem_val, ptr])
2674 }
2675 }
2676 }
2677 }
2678 return
2679 }
2680 // For multi-assign with plain assignment (a, b = b, a), evaluate all RHS
2681 // values before any stores to avoid aliasing issues.
2682 if stmt.lhs.len > 1 && stmt.rhs.len > 1 && stmt.op == .assign {
2683 mut rhs_vals := []ValueID{cap: stmt.rhs.len}
2684 for rhs_expr in stmt.rhs {
2685 rhs_vals << b.build_expr(rhs_expr)
2686 }
2687 for i, lhs in stmt.lhs {
2688 if i >= rhs_vals.len {
2689 break
2690 }
2691 rhs_val := rhs_vals[i]
2692 if ident := b.unwrap_ident(lhs) {
2693 if ident.name == '_' {
2694 continue
2695 }
2696 mut ptr := ValueID(0)
2697 if p := b.vars[ident.name] {
2698 ptr = p
2699 } else if glob_id := b.find_global(ident.name) {
2700 ptr = glob_id
2701 } else if glob_id := b.find_global('${b.cur_module}__${ident.name}') {
2702 ptr = glob_id
2703 } else if glob_id := b.find_global('builtin__${ident.name}') {
2704 ptr = glob_id
2705 }
2706 if ptr != 0 {
2707 b.mod.add_instr(.store, b.cur_block, 0, [rhs_val, ptr])
2708 }
2709 } else if lhs is ast.SelectorExpr {
2710 base := b.build_addr(lhs)
2711 if base != 0 {
2712 b.mod.add_instr(.store, b.cur_block, 0, [rhs_val, base])
2713 }
2714 } else if lhs is ast.IndexExpr {
2715 base := b.build_addr(lhs)
2716 if base != 0 {
2717 b.mod.add_instr(.store, b.cur_block, 0, [rhs_val, base])
2718 }
2719 }
2720 }
2721 return
2722 }
2723 for i, lhs in stmt.lhs {
2724 if i >= stmt.rhs.len {
2725 break
2726 }
2727 rhs := stmt.rhs[i]
2728 rhs_val := b.build_expr(rhs)
2729
2730 if ident := b.unwrap_ident(lhs) {
2731 if stmt.op == .decl_assign {
2732 // New variable declaration - use the actual type of the built RHS value
2733 rhs_type := b.mod.values[rhs_val].typ
2734 alloca := b.mod.add_instr(.alloca, b.cur_block, b.mod.type_store.get_ptr(rhs_type),
2735 []ValueID{})
2736 b.mod.add_instr(.store, b.cur_block, 0, [rhs_val, alloca])
2737 b.vars[ident.name] = alloca
2738 } else if ident.name == '_' && stmt.op == .assign {
2739 // Discard for plain assignment only; compound assignments (+=, etc.)
2740 // must still execute (e.g. for loop counter `_ += 1`).
2741 continue
2742 } else {
2743 // Assignment to existing variable (local or global)
2744 // Skip stores to const_array_globals — they are already initialized
2745 // in the data segment. The runtime const init function would overwrite
2746 // the first element with a stack pointer.
2747 // Only skip if RHS is an ArrayInitExpr (fixed array runtime init).
2748 // Do NOT skip if RHS is a function call (e.g. new_array_from_c_array
2749 // for dynamic arrays — those need their _vinit assignment).
2750 if rhs is ast.ArrayInitExpr {
2751 if ident.name in b.const_array_globals
2752 || '${b.cur_module}__${ident.name}' in b.const_array_globals
2753 || 'builtin__${ident.name}' in b.const_array_globals {
2754 continue
2755 }
2756 }
2757 mut ptr := ValueID(0)
2758 if p := b.vars[ident.name] {
2759 ptr = p
2760 } else if glob_id := b.find_global(ident.name) {
2761 ptr = glob_id
2762 } else if glob_id := b.find_global('${b.cur_module}__${ident.name}') {
2763 ptr = glob_id
2764 } else if glob_id := b.find_global('builtin__${ident.name}') {
2765 ptr = glob_id
2766 }
2767 if ptr != 0 {
2768 if stmt.op == .assign {
2769 b.mod.add_instr(.store, b.cur_block, 0, [rhs_val, ptr])
2770 } else {
2771 // Compound assignment (+=, -=, etc.)
2772 ptr_typ := b.mod.values[ptr].typ
2773 elem_typ := b.mod.type_store.types[ptr_typ].elem_type
2774 loaded := b.mod.add_instr(.load, b.cur_block, elem_typ, [ptr])
2775 ca_is_float := elem_typ > 0 && int(elem_typ) < b.mod.type_store.types.len
2776 && b.mod.type_store.types[elem_typ].kind == .float_t
2777 op := if ca_is_float {
2778 match stmt.op {
2779 .plus_assign { OpCode.fadd }
2780 .minus_assign { OpCode.fsub }
2781 .mul_assign { OpCode.fmul }
2782 .div_assign { OpCode.fdiv }
2783 .mod_assign { OpCode.frem }
2784 else { OpCode.fadd }
2785 }
2786 } else {
2787 match stmt.op {
2788 .plus_assign { OpCode.add }
2789 .minus_assign { OpCode.sub }
2790 .mul_assign { OpCode.mul }
2791 .div_assign { OpCode.sdiv }
2792 .mod_assign { OpCode.srem }
2793 .left_shift_assign { OpCode.shl }
2794 .right_shift_assign { OpCode.ashr }
2795 .and_assign { OpCode.and_ }
2796 .or_assign { OpCode.or_ }
2797 .xor_assign { OpCode.xor }
2798 else { OpCode.add }
2799 }
2800 }
2801 // If LHS is float but RHS is int, convert RHS to float
2802 mut actual_rhs := rhs_val
2803 if ca_is_float {
2804 rhs_typ := b.mod.values[rhs_val].typ
2805 rhs_is_float := rhs_typ > 0 && int(rhs_typ) < b.mod.type_store.types.len
2806 && b.mod.type_store.types[rhs_typ].kind == .float_t
2807 if !rhs_is_float {
2808 rhs_unsigned := rhs_typ > 0
2809 && int(rhs_typ) < b.mod.type_store.types.len
2810 && b.mod.type_store.types[rhs_typ].is_unsigned
2811 conv_op := if rhs_unsigned {
2812 OpCode.uitofp
2813 } else {
2814 OpCode.sitofp
2815 }
2816 actual_rhs = b.mod.add_instr(conv_op, b.cur_block, elem_typ, [
2817 rhs_val,
2818 ])
2819 }
2820 }
2821 result := b.mod.add_instr(op, b.cur_block, b.mod.values[loaded].typ, [
2822 loaded,
2823 actual_rhs,
2824 ])
2825 b.mod.add_instr(.store, b.cur_block, 0, [result, ptr])
2826 }
2827 }
2828 }
2829 } else if lhs is ast.SelectorExpr {
2830 // Field assignment: obj.field = val or obj.field += val
2831 base := b.build_addr(lhs)
2832 if base != 0 {
2833 if stmt.op == .assign {
2834 b.mod.add_instr(.store, b.cur_block, 0, [rhs_val, base])
2835 } else {
2836 // Compound assignment (+=, -=, etc.)
2837 ptr_typ := b.mod.values[base].typ
2838 elem_typ := if ptr_typ < b.mod.type_store.types.len {
2839 b.mod.type_store.types[ptr_typ].elem_type
2840 } else {
2841 b.mod.values[rhs_val].typ
2842 }
2843 loaded := b.mod.add_instr(.load, b.cur_block, elem_typ, [base])
2844 sf_is_float := elem_typ > 0 && int(elem_typ) < b.mod.type_store.types.len
2845 && b.mod.type_store.types[elem_typ].kind == .float_t
2846 op := if sf_is_float {
2847 match stmt.op {
2848 .plus_assign { OpCode.fadd }
2849 .minus_assign { OpCode.fsub }
2850 .mul_assign { OpCode.fmul }
2851 .div_assign { OpCode.fdiv }
2852 .mod_assign { OpCode.frem }
2853 else { OpCode.fadd }
2854 }
2855 } else {
2856 match stmt.op {
2857 .plus_assign { OpCode.add }
2858 .minus_assign { OpCode.sub }
2859 .mul_assign { OpCode.mul }
2860 .div_assign { OpCode.sdiv }
2861 .mod_assign { OpCode.srem }
2862 .left_shift_assign { OpCode.shl }
2863 .right_shift_assign { OpCode.ashr }
2864 .and_assign { OpCode.and_ }
2865 .or_assign { OpCode.or_ }
2866 .xor_assign { OpCode.xor }
2867 else { OpCode.add }
2868 }
2869 }
2870 // If LHS is float but RHS is int, convert RHS to float
2871 mut actual_rhs := rhs_val
2872 if sf_is_float {
2873 rhs_typ := b.mod.values[rhs_val].typ
2874 rhs_is_float := rhs_typ > 0 && int(rhs_typ) < b.mod.type_store.types.len
2875 && b.mod.type_store.types[rhs_typ].kind == .float_t
2876 if !rhs_is_float {
2877 rhs_unsigned := rhs_typ > 0 && int(rhs_typ) < b.mod.type_store.types.len
2878 && b.mod.type_store.types[rhs_typ].is_unsigned
2879 conv_op := if rhs_unsigned {
2880 OpCode.uitofp
2881 } else {
2882 OpCode.sitofp
2883 }
2884 actual_rhs = b.mod.add_instr(conv_op, b.cur_block, elem_typ, [
2885 rhs_val,
2886 ])
2887 }
2888 }
2889 result := b.mod.add_instr(op, b.cur_block, b.mod.values[loaded].typ, [
2890 loaded,
2891 actual_rhs,
2892 ])
2893 b.mod.add_instr(.store, b.cur_block, 0, [result, base])
2894 }
2895 }
2896 } else if lhs is ast.PrefixExpr && lhs.op == .mul {
2897 // Pointer dereference assignment: *ptr = val
2898 // Build the inner expression to get the pointer, then store to it
2899 ptr := b.build_expr(lhs.expr)
2900 if stmt.op == .assign {
2901 b.mod.add_instr(.store, b.cur_block, 0, [rhs_val, ptr])
2902 } else {
2903 // Compound assignment (*ptr += val)
2904 ptr_typ := b.mod.values[ptr].typ
2905 elem_typ := if int(ptr_typ) < b.mod.type_store.types.len {
2906 b.mod.type_store.types[ptr_typ].elem_type
2907 } else {
2908 b.mod.values[rhs_val].typ
2909 }
2910 loaded := b.mod.add_instr(.load, b.cur_block, elem_typ, [ptr])
2911 pd_is_float := elem_typ > 0 && int(elem_typ) < b.mod.type_store.types.len
2912 && b.mod.type_store.types[elem_typ].kind == .float_t
2913 op := if pd_is_float {
2914 match stmt.op {
2915 .plus_assign { OpCode.fadd }
2916 .minus_assign { OpCode.fsub }
2917 .mul_assign { OpCode.fmul }
2918 .div_assign { OpCode.fdiv }
2919 else { OpCode.fadd }
2920 }
2921 } else {
2922 match stmt.op {
2923 .plus_assign { OpCode.add }
2924 .minus_assign { OpCode.sub }
2925 .mul_assign { OpCode.mul }
2926 .div_assign { OpCode.sdiv }
2927 else { OpCode.add }
2928 }
2929 }
2930 // If LHS is float but RHS is int, convert RHS to float
2931 mut actual_rhs := rhs_val
2932 if pd_is_float {
2933 rhs_typ := b.mod.values[rhs_val].typ
2934 rhs_is_float := rhs_typ > 0 && int(rhs_typ) < b.mod.type_store.types.len
2935 && b.mod.type_store.types[rhs_typ].kind == .float_t
2936 if !rhs_is_float {
2937 rhs_unsigned := rhs_typ > 0 && int(rhs_typ) < b.mod.type_store.types.len
2938 && b.mod.type_store.types[rhs_typ].is_unsigned
2939 conv_op := if rhs_unsigned {
2940 OpCode.uitofp
2941 } else {
2942 OpCode.sitofp
2943 }
2944 actual_rhs = b.mod.add_instr(conv_op, b.cur_block, elem_typ, [
2945 rhs_val,
2946 ])
2947 }
2948 }
2949 result := b.mod.add_instr(op, b.cur_block, b.mod.values[loaded].typ, [
2950 loaded,
2951 actual_rhs,
2952 ])
2953 b.mod.add_instr(.store, b.cur_block, 0, [result, ptr])
2954 }
2955 } else if lhs is ast.IndexExpr {
2956 // Index assignment: arr[i] = val or arr[i] += val
2957 base := b.build_addr(lhs)
2958 if base != 0 {
2959 if stmt.op == .assign {
2960 b.mod.add_instr(.store, b.cur_block, 0, [rhs_val, base])
2961 } else {
2962 // Compound assignment (+=, -=, etc.)
2963 ptr_typ := b.mod.values[base].typ
2964 elem_typ := if ptr_typ < b.mod.type_store.types.len {
2965 b.mod.type_store.types[ptr_typ].elem_type
2966 } else {
2967 b.mod.values[rhs_val].typ
2968 }
2969 loaded := b.mod.add_instr(.load, b.cur_block, elem_typ, [base])
2970 ix_is_float := elem_typ > 0 && int(elem_typ) < b.mod.type_store.types.len
2971 && b.mod.type_store.types[elem_typ].kind == .float_t
2972 op := if ix_is_float {
2973 match stmt.op {
2974 .plus_assign { OpCode.fadd }
2975 .minus_assign { OpCode.fsub }
2976 .mul_assign { OpCode.fmul }
2977 .div_assign { OpCode.fdiv }
2978 .mod_assign { OpCode.frem }
2979 else { OpCode.fadd }
2980 }
2981 } else {
2982 match stmt.op {
2983 .plus_assign { OpCode.add }
2984 .minus_assign { OpCode.sub }
2985 .mul_assign { OpCode.mul }
2986 .div_assign { OpCode.sdiv }
2987 .mod_assign { OpCode.srem }
2988 .left_shift_assign { OpCode.shl }
2989 .right_shift_assign { OpCode.ashr }
2990 .and_assign { OpCode.and_ }
2991 .or_assign { OpCode.or_ }
2992 .xor_assign { OpCode.xor }
2993 else { OpCode.add }
2994 }
2995 }
2996 // If LHS is float but RHS is int, convert RHS to float
2997 mut actual_rhs := rhs_val
2998 if ix_is_float {
2999 rhs_typ := b.mod.values[rhs_val].typ
3000 rhs_is_float := rhs_typ > 0 && int(rhs_typ) < b.mod.type_store.types.len
3001 && b.mod.type_store.types[rhs_typ].kind == .float_t
3002 if !rhs_is_float {
3003 rhs_unsigned := rhs_typ > 0 && int(rhs_typ) < b.mod.type_store.types.len
3004 && b.mod.type_store.types[rhs_typ].is_unsigned
3005 conv_op := if rhs_unsigned {
3006 OpCode.uitofp
3007 } else {
3008 OpCode.sitofp
3009 }
3010 actual_rhs = b.mod.add_instr(conv_op, b.cur_block, elem_typ, [
3011 rhs_val,
3012 ])
3013 }
3014 }
3015 result := b.mod.add_instr(op, b.cur_block, b.mod.values[loaded].typ, [
3016 loaded,
3017 actual_rhs,
3018 ])
3019 b.mod.add_instr(.store, b.cur_block, 0, [result, base])
3020 }
3021 }
3022 }
3023 }
3024}
3025
3026fn (mut b Builder) build_return(stmt ast.ReturnStmt) {
3027 fn_ret_type := if b.cur_func >= 0 && b.cur_func < b.mod.funcs.len {
3028 b.mod.funcs[b.cur_func].typ
3029 } else {
3030 TypeID(0)
3031 }
3032 is_option_ret := b.is_option_wrapper_type(fn_ret_type)
3033 is_result_ret := b.is_result_wrapper_type(fn_ret_type)
3034 if stmt.exprs.len == 0 {
3035 if is_option_ret || is_result_ret {
3036 b.mod.add_instr(.ret, b.cur_block, 0, [
3037 b.build_wrapper_value(fn_ret_type, true, 0, false),
3038 ])
3039 } else {
3040 b.mod.add_instr(.ret, b.cur_block, 0, []ValueID{})
3041 }
3042 } else if stmt.exprs.len == 1 {
3043 ret_expr := stmt.exprs[0]
3044 mut val := b.build_expr(ret_expr)
3045 if (is_option_ret || is_result_ret) && b.mod.values[val].typ != fn_ret_type {
3046 val = b.coerce_wrapper_value(ret_expr, val, fn_ret_type)
3047 } else if fn_ret_type > 0 && int(fn_ret_type) < b.mod.type_store.types.len
3048 && b.mod.type_store.types[fn_ret_type].kind == .float_t {
3049 // If function returns float but value is int, convert (e.g., `return 1` in fn() f64)
3050 val_type := b.mod.values[val].typ
3051 if val_type > 0 && int(val_type) < b.mod.type_store.types.len
3052 && b.mod.type_store.types[val_type].kind != .float_t {
3053 val = b.mod.add_instr(.sitofp, b.cur_block, fn_ret_type, [val])
3054 }
3055 }
3056 b.mod.add_instr(.ret, b.cur_block, 0, [val])
3057 } else {
3058 // Multiple return values -> build a tuple via insertvalue
3059 mut elem_types := []TypeID{cap: stmt.exprs.len}
3060 mut vals := []ValueID{cap: stmt.exprs.len}
3061 for expr in stmt.exprs {
3062 v := b.build_expr(expr)
3063 vals << v
3064 elem_types << b.mod.values[v].typ
3065 }
3066 tuple_type := b.mod.type_store.get_tuple(elem_types)
3067 // Build tuple by chaining insertvalue instructions
3068 mut tuple_val := b.mod.get_or_add_const(tuple_type, 'undef')
3069 for i, v in vals {
3070 idx := b.mod.get_or_add_const(b.mod.type_store.get_int(32), i.str())
3071 tuple_val = b.mod.add_instr(.insertvalue, b.cur_block, tuple_type, [
3072 tuple_val,
3073 v,
3074 idx,
3075 ])
3076 }
3077 // If function returns Option/Result, wrap the tuple in the wrapper
3078 if (is_option_ret || is_result_ret) && tuple_type != fn_ret_type {
3079 tuple_val = b.build_wrapper_value(fn_ret_type, true, tuple_val, true)
3080 }
3081 b.mod.add_instr(.ret, b.cur_block, 0, [tuple_val])
3082 }
3083}
3084
3085fn (mut b Builder) build_for(stmt ast.ForStmt) {
3086 has_init := stmt.init !is ast.EmptyStmt
3087 has_cond := stmt.cond !is ast.EmptyExpr
3088 has_post := stmt.post !is ast.EmptyStmt
3089
3090 // Build init in current block
3091 if has_init {
3092 b.build_stmt(stmt.init)
3093 }
3094
3095 // Create blocks
3096 cond_block := b.mod.add_block(b.cur_func, 'for_cond')
3097 body_block := b.mod.add_block(b.cur_func, 'for_body')
3098 post_block := if has_post {
3099 b.mod.add_block(b.cur_func, 'for_post')
3100 } else {
3101 cond_block
3102 }
3103 exit_block := b.mod.add_block(b.cur_func, 'for_exit')
3104
3105 // Jump to condition
3106 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[cond_block].val_id])
3107 b.add_edge(b.cur_block, cond_block)
3108
3109 // Condition block
3110 b.cur_block = cond_block
3111 if has_cond {
3112 cond_val := b.build_expr(stmt.cond)
3113 b.mod.add_instr(.br, b.cur_block, 0,
3114 [cond_val, b.mod.blocks[body_block].val_id, b.mod.blocks[exit_block].val_id])
3115 b.add_edge(cond_block, body_block)
3116 b.add_edge(cond_block, exit_block)
3117 } else {
3118 // Infinite loop
3119 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[body_block].val_id])
3120 b.add_edge(cond_block, body_block)
3121 }
3122
3123 // Body block
3124 b.cur_block = body_block
3125 b.loop_stack << LoopInfo{
3126 cond_block: post_block
3127 exit_block: exit_block
3128 }
3129 b.build_stmts(stmt.stmts)
3130 b.loop_stack.delete_last()
3131
3132 if !b.block_has_terminator(b.cur_block) {
3133 // Jump to post (or back to cond)
3134 target := if has_post { post_block } else { cond_block }
3135 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[target].val_id])
3136 b.add_edge(b.cur_block, target)
3137 }
3138
3139 // Post block
3140 if has_post {
3141 b.cur_block = post_block
3142 b.build_stmt(stmt.post)
3143 if !b.block_has_terminator(b.cur_block) {
3144 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[cond_block].val_id])
3145 b.add_edge(post_block, cond_block)
3146 }
3147 }
3148
3149 b.cur_block = exit_block
3150}
3151
3152fn (mut b Builder) build_if_stmt(node ast.IfExpr) {
3153 if node.cond is ast.EmptyExpr {
3154 // Pure else block
3155 b.build_stmts(node.stmts)
3156 return
3157 }
3158
3159 then_block := b.mod.add_block(b.cur_func, 'if_then')
3160 merge_block := b.mod.add_block(b.cur_func, 'if_merge')
3161
3162 has_else := node.else_expr !is ast.EmptyExpr
3163 else_block := if has_else {
3164 b.mod.add_block(b.cur_func, 'if_else')
3165 } else {
3166 merge_block
3167 }
3168
3169 // Condition
3170 cond_val := b.build_expr(node.cond)
3171 b.mod.add_instr(.br, b.cur_block, 0,
3172 [cond_val, b.mod.blocks[then_block].val_id, b.mod.blocks[else_block].val_id])
3173 b.add_edge(b.cur_block, then_block)
3174 b.add_edge(b.cur_block, else_block)
3175
3176 // Then
3177 b.cur_block = then_block
3178 b.build_stmts(node.stmts)
3179 if !b.block_has_terminator(b.cur_block) {
3180 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[merge_block].val_id])
3181 b.add_edge(b.cur_block, merge_block)
3182 }
3183
3184 // Else
3185 if has_else {
3186 b.cur_block = else_block
3187 if node.else_expr is ast.IfExpr {
3188 else_if := node.else_expr as ast.IfExpr
3189 if else_if.cond is ast.EmptyExpr {
3190 // Pure else
3191 b.build_stmts(else_if.stmts)
3192 } else {
3193 b.build_if_stmt(else_if)
3194 }
3195 } else if node.else_expr is ast.UnsafeExpr {
3196 for s in node.else_expr.stmts {
3197 b.build_stmt(s)
3198 }
3199 }
3200 if !b.block_has_terminator(b.cur_block) {
3201 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[merge_block].val_id])
3202 b.add_edge(b.cur_block, merge_block)
3203 }
3204 }
3205
3206 b.cur_block = merge_block
3207 // If the merge block has no predecessors (both branches returned/jumped elsewhere),
3208 // mark it as unreachable so no implicit return is added
3209 if b.mod.blocks[merge_block].preds.len == 0 {
3210 b.mod.add_instr(.unreachable, b.cur_block, 0, []ValueID{})
3211 }
3212}
3213
3214fn (mut b Builder) build_flow_control(stmt ast.FlowControlStmt) {
3215 if stmt.op == .key_goto {
3216 // goto label — jump to the label's block (create if not yet seen)
3217 target := b.get_or_create_label_block(stmt.label)
3218 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[target].val_id])
3219 b.add_edge(b.cur_block, target)
3220 // Create a new unreachable block for any code after the goto
3221 dead_block := b.mod.add_block(b.cur_func, 'after_goto')
3222 b.cur_block = dead_block
3223 return
3224 }
3225 if b.loop_stack.len == 0 {
3226 return
3227 }
3228 loop_info := b.loop_stack[b.loop_stack.len - 1]
3229 if stmt.op == .key_break {
3230 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[loop_info.exit_block].val_id])
3231 b.add_edge(b.cur_block, loop_info.exit_block)
3232 } else if stmt.op == .key_continue {
3233 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[loop_info.cond_block].val_id])
3234 b.add_edge(b.cur_block, loop_info.cond_block)
3235 }
3236}
3237
3238fn (mut b Builder) get_or_create_label_block(name string) BlockID {
3239 if name in b.label_blocks {
3240 return b.label_blocks[name]
3241 }
3242 block := b.mod.add_block(b.cur_func, 'label_${name}')
3243 b.label_blocks[name] = block
3244 return block
3245}
3246
3247fn (mut b Builder) build_label(stmt ast.LabelStmt) {
3248 label_block := b.get_or_create_label_block(stmt.name)
3249 // Fall through from current block to label block
3250 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[label_block].val_id])
3251 b.add_edge(b.cur_block, label_block)
3252 b.cur_block = label_block
3253}
3254
3255fn (mut b Builder) build_assert(stmt ast.AssertStmt) {
3256 // assert expr -> if (!expr) { exit(1) }
3257 cond := b.build_expr(stmt.expr)
3258 pass_block := b.mod.add_block(b.cur_func, 'assert_pass')
3259 fail_block := b.mod.add_block(b.cur_func, 'assert_fail')
3260
3261 b.mod.add_instr(.br, b.cur_block, 0,
3262 [cond, b.mod.blocks[pass_block].val_id, b.mod.blocks[fail_block].val_id])
3263 b.add_edge(b.cur_block, pass_block)
3264 b.add_edge(b.cur_block, fail_block)
3265
3266 // Fail block: call exit(1)
3267 b.cur_block = fail_block
3268 one := b.mod.get_or_add_const(b.mod.type_store.get_int(32), '1')
3269 exit_ref := b.get_or_create_fn_ref('exit', b.mod.type_store.get_int(32))
3270 b.mod.add_instr(.call, b.cur_block, 0, [exit_ref, one])
3271 b.mod.add_instr(.unreachable, b.cur_block, 0, []ValueID{})
3272
3273 b.cur_block = pass_block
3274}
3275
3276// --- Expression building ---
3277
3278fn (mut b Builder) build_expr(expr ast.Expr) ValueID {
3279 match expr {
3280 ast.BasicLiteral {
3281 return b.build_basic_literal(expr)
3282 }
3283 ast.StringLiteral {
3284 return b.build_string_literal(expr)
3285 }
3286 ast.Ident {
3287 return b.build_ident(expr)
3288 }
3289 ast.InfixExpr {
3290 return b.build_infix(expr)
3291 }
3292 ast.PrefixExpr {
3293 return b.build_prefix(expr)
3294 }
3295 ast.CallExpr {
3296 return b.build_call(expr)
3297 }
3298 ast.SelectorExpr {
3299 return b.build_selector(expr)
3300 }
3301 ast.IndexExpr {
3302 return b.build_index(expr)
3303 }
3304 ast.IfExpr {
3305 return b.build_if_expr(expr)
3306 }
3307 ast.InitExpr {
3308 return b.build_init_expr(expr)
3309 }
3310 ast.CastExpr {
3311 return b.build_cast(expr)
3312 }
3313 ast.ParenExpr {
3314 return b.build_expr(expr.expr)
3315 }
3316 ast.ModifierExpr {
3317 return b.build_expr(expr.expr)
3318 }
3319 ast.UnsafeExpr {
3320 if expr.stmts.len > 0 {
3321 for i := 0; i < expr.stmts.len - 1; i++ {
3322 b.build_stmt(expr.stmts[i])
3323 }
3324 last := expr.stmts[expr.stmts.len - 1]
3325 if last is ast.ExprStmt {
3326 return b.build_expr(last.expr)
3327 }
3328 b.build_stmt(last)
3329 }
3330 return b.mod.get_or_add_const(b.mod.type_store.get_int(32), '0')
3331 }
3332 ast.Keyword {
3333 return b.build_keyword(expr)
3334 }
3335 ast.KeywordOperator {
3336 return b.build_keyword_operator(expr)
3337 }
3338 ast.PostfixExpr {
3339 return b.build_postfix(expr)
3340 }
3341 ast.ArrayInitExpr {
3342 return b.build_array_init_expr(expr)
3343 }
3344 ast.MapInitExpr {
3345 // TODO: map init
3346 return b.mod.get_or_add_const(b.mod.type_store.get_int(64), '0')
3347 }
3348 ast.StringInterLiteral {
3349 return b.build_string_inter_literal(expr)
3350 }
3351 ast.MatchExpr {
3352 // Should be lowered by transformer
3353 return b.mod.get_or_add_const(b.mod.type_store.get_int(32), '0')
3354 }
3355 ast.OrExpr {
3356 return b.build_expr(expr.expr)
3357 }
3358 ast.FnLiteral {
3359 return b.build_fn_literal(expr)
3360 }
3361 ast.RangeExpr {
3362 return b.mod.get_or_add_const(b.mod.type_store.get_int(64), '0')
3363 }
3364 ast.CallOrCastExpr {
3365 return b.build_call_or_cast(expr)
3366 }
3367 ast.AsCastExpr {
3368 return b.build_as_cast(expr)
3369 }
3370 ast.AssocExpr {
3371 return b.mod.get_or_add_const(b.mod.type_store.get_int(64), '0')
3372 }
3373 ast.Tuple {
3374 if expr.exprs.len > 0 {
3375 return b.build_expr(expr.exprs[0])
3376 }
3377 return b.mod.get_or_add_const(b.mod.type_store.get_int(32), '0')
3378 }
3379 else {
3380 return b.mod.get_or_add_const(b.mod.type_store.get_int(32), '0')
3381 }
3382 }
3383}
3384
3385fn (mut b Builder) build_basic_literal(lit ast.BasicLiteral) ValueID {
3386 match lit.kind {
3387 .key_true {
3388 return b.mod.get_or_add_const(b.mod.type_store.get_int(1), '1')
3389 }
3390 .key_false {
3391 return b.mod.get_or_add_const(b.mod.type_store.get_int(1), '0')
3392 }
3393 .char {
3394 // Resolve character literal to its numeric value (Unicode code point)
3395 char_val := b.resolve_char_value(lit.value)
3396 // Always use 32-bit (rune) type — V rune literals are i32
3397 typ := b.mod.type_store.get_int(32)
3398 return b.mod.get_or_add_const(typ, char_val.str())
3399 }
3400 .number {
3401 if lit.value.contains('.')
3402 || (!lit.value.starts_with('0x') && !lit.value.starts_with('0X')
3403 && (lit.value.contains('e') || lit.value.contains('E'))) {
3404 typ := b.mod.type_store.get_float(64)
3405 return b.mod.get_or_add_const(typ, lit.value)
3406 }
3407 mut typ := b.expr_type(ast.Expr(lit))
3408 // Number literals should never have bool (i1) type. This can happen when
3409 // expr_type resolves a literal like "1" in "-1" to bool in && contexts.
3410 // Override to i32 to prevent 1-bit arithmetic overflow in negation.
3411 if typ != 0 && int(typ) < b.mod.type_store.types.len {
3412 t0 := b.mod.type_store.types[typ]
3413 if t0.kind == .int_t && t0.width == 1 {
3414 typ = b.mod.type_store.get_int(32)
3415 }
3416 }
3417 // Widen to i64 if the literal value overflows the assigned type.
3418 // The type checker may assign `int` (i32) to a literal like 9223372036854775807
3419 // which doesn't fit in 32 bits.
3420 if typ != 0 && int(typ) < b.mod.type_store.types.len {
3421 t := b.mod.type_store.types[typ]
3422 if t.kind == .int_t && t.width <= 32 {
3423 val := lit.value
3424 // Check if the value exceeds i32 range
3425 if val.len >= 10 {
3426 parsed := val.i64()
3427 if parsed > 2147483647 || parsed < -2147483648 {
3428 return b.mod.get_or_add_const(b.mod.type_store.get_int(64), val)
3429 }
3430 }
3431 }
3432 }
3433 return b.mod.get_or_add_const(typ, lit.value)
3434 }
3435 else {
3436 // Integer or other
3437 typ := b.expr_type(ast.Expr(lit))
3438 return b.mod.get_or_add_const(typ, lit.value)
3439 }
3440 }
3441}
3442
3443fn (mut b Builder) build_string_literal(lit ast.StringLiteral) ValueID {
3444 // Strip surrounding quotes from V string literal values
3445 mut val := lit.value
3446 if val.len >= 2 && ((val[0] == `'` && val[val.len - 1] == `'`)
3447 || (val[0] == `"` && val[val.len - 1] == `"`)) {
3448 val = val[1..val.len - 1]
3449 }
3450 if lit.kind == .c {
3451 // C string literal -> raw i8* pointer
3452 i8_t := b.mod.type_store.get_int(8)
3453 ptr_t := b.mod.type_store.get_ptr(i8_t)
3454 return b.mod.add_value_node(.c_string_literal, ptr_t, val, 0)
3455 }
3456 // Process V escape sequences for non-raw strings
3457 if lit.kind != .raw {
3458 val = process_v_escapes(val)
3459 }
3460 typ := b.get_string_type()
3461 result := b.mod.add_value_node(.string_literal, typ, val, val.len)
3462 return result
3463}
3464
3465// process_v_escapes converts V escape sequences (\n, \t, etc.) in a string
3466// to their actual byte values. The V2 scanner stores raw escape sequences
3467// (e.g., \t as two chars '\' + 't'), not processed bytes.
3468fn process_v_escapes(s string) string {
3469 // Fast path: no backslashes means no escapes
3470 if !s.contains('\\') {
3471 return s
3472 }
3473 mut result := []u8{cap: s.len}
3474 mut i := 0
3475 for i < s.len {
3476 if s[i] == `\\` && i + 1 < s.len {
3477 match s[i + 1] {
3478 `n` {
3479 result << 0x0A
3480 }
3481 `t` {
3482 result << 0x09
3483 }
3484 `r` {
3485 result << 0x0D
3486 }
3487 `\\` {
3488 result << 0x5C
3489 }
3490 `'` {
3491 result << 0x27
3492 }
3493 `"` {
3494 result << 0x22
3495 }
3496 `0` {
3497 result << 0x00
3498 }
3499 `a` {
3500 result << 0x07
3501 }
3502 `b` {
3503 result << 0x08
3504 }
3505 `f` {
3506 result << 0x0C
3507 }
3508 `v` {
3509 result << 0x0B
3510 }
3511 `e` {
3512 result << 0x1B
3513 }
3514 0x0A {
3515 // Backslash followed by newline: line continuation
3516 // Skip the backslash, newline, and leading whitespace on next line
3517 i += 2
3518 for i < s.len && (s[i] == ` ` || s[i] == `\t`) {
3519 i++
3520 }
3521 continue
3522 }
3523 `x` {
3524 // \xNN hex escape
3525 if i + 3 < s.len {
3526 hi := hex_digit(s[i + 2])
3527 lo := hex_digit(s[i + 3])
3528 if hi >= 0 && lo >= 0 {
3529 result << u8(hi * 16 + lo)
3530 i += 4
3531 continue
3532 }
3533 }
3534 result << s[i]
3535 i++
3536 continue
3537 }
3538 `u` {
3539 code, ok := parse_hex_escape(s, i + 2, 4)
3540 if ok && append_utf8_codepoint(mut result, code) {
3541 i += 6
3542 continue
3543 }
3544 result << s[i]
3545 i++
3546 continue
3547 }
3548 `U` {
3549 code, ok := parse_hex_escape(s, i + 2, 8)
3550 if ok && append_utf8_codepoint(mut result, code) {
3551 i += 10
3552 continue
3553 }
3554 result << s[i]
3555 i++
3556 continue
3557 }
3558 else {
3559 result << s[i]
3560 i++
3561 continue
3562 }
3563 }
3564
3565 i += 2
3566 } else {
3567 result << s[i]
3568 i++
3569 }
3570 }
3571 return result.bytestr()
3572}
3573
3574fn parse_hex_escape(s string, start int, count int) (int, bool) {
3575 if start + count > s.len {
3576 return 0, false
3577 }
3578 mut code := 0
3579 for j := 0; j < count; j++ {
3580 digit := hex_digit(s[start + j])
3581 if digit < 0 {
3582 return 0, false
3583 }
3584 code = code * 16 + digit
3585 }
3586 return code, true
3587}
3588
3589fn append_utf8_codepoint(mut result []u8, code int) bool {
3590 if code < 0 || code > 0x10FFFF {
3591 return false
3592 }
3593 if code <= 0x7F {
3594 result << u8(code)
3595 } else if code <= 0x7FF {
3596 result << u8(0xC0 | (code >> 6))
3597 result << u8(0x80 | (code & 0x3F))
3598 } else if code <= 0xFFFF {
3599 result << u8(0xE0 | (code >> 12))
3600 result << u8(0x80 | ((code >> 6) & 0x3F))
3601 result << u8(0x80 | (code & 0x3F))
3602 } else {
3603 result << u8(0xF0 | (code >> 18))
3604 result << u8(0x80 | ((code >> 12) & 0x3F))
3605 result << u8(0x80 | ((code >> 6) & 0x3F))
3606 result << u8(0x80 | (code & 0x3F))
3607 }
3608 return true
3609}
3610
3611fn hex_digit(c u8) int {
3612 if c >= `0` && c <= `9` {
3613 return int(c - `0`)
3614 }
3615 if c >= `a` && c <= `f` {
3616 return int(c - `a` + 10)
3617 }
3618 if c >= `A` && c <= `F` {
3619 return int(c - `A` + 10)
3620 }
3621 return -1
3622}
3623
3624fn (mut b Builder) build_string_inter_literal(expr ast.StringInterLiteral) ValueID {
3625 // String interpolation: concatenate literal parts and expression-to-string conversions.
3626 // Pattern: values[0] + str(inters[0]) + values[1] + str(inters[1]) + ...
3627 str_type := b.get_string_type()
3628 plus_fn := b.get_or_create_fn_ref('builtin__string__+', str_type)
3629
3630 mut parts := []ValueID{}
3631 for i, raw_val in expr.values {
3632 // Strip surrounding quotes from first and last values (parser artifact)
3633 mut val := raw_val
3634 if i == 0 && val.len > 0 && (val[0] == `'` || val[0] == `"`) {
3635 val = val[1..]
3636 }
3637 if i == expr.values.len - 1 && val.len > 0
3638 && (val[val.len - 1] == `'` || val[val.len - 1] == `"`) {
3639 val = val[..val.len - 1]
3640 }
3641 // Process V escape sequences in interpolation literal parts
3642 val = process_v_escapes(val)
3643 if val.len > 0 {
3644 parts << b.mod.add_value_node(.string_literal, str_type, val, val.len)
3645 }
3646 if i < expr.inters.len {
3647 inter := expr.inters[i]
3648 // Check if this interpolation needs C.snprintf formatting.
3649 // Use snprintf for explicit format specifiers (:.3f, :03d, :c, :x, :o, etc.)
3650 // but not for :s (string) or unformatted — those use V string concatenation.
3651 needs_snprintf := inter.format != .unformatted && inter.format != .string
3652 && inter.resolved_fmt.len > 0
3653 if needs_snprintf {
3654 // Use C.snprintf to format the value with the resolved format string.
3655 // 1. Allocate a stack buffer (64 bytes)
3656 i8_t := b.mod.type_store.get_int(8)
3657 ptr_type := b.mod.type_store.get_ptr(i8_t)
3658 count_64 := b.mod.get_or_add_const(i8_t, '64')
3659 buf_ptr := b.mod.add_instr(.alloca, b.cur_block, ptr_type, [count_64])
3660 // 2. Build the value expression (use raw value, not str()-converted)
3661 inter_val := b.build_expr(inter.expr)
3662 formatted_val := b.prepare_snprintf_arg(inter_val, inter.resolved_fmt)
3663 // 3. Call snprintf(buf, 64, fmt, val)
3664 i32_t := b.mod.type_store.get_int(32)
3665 snprintf_ref := b.get_or_create_fn_ref('snprintf', i32_t)
3666 size_val := b.mod.get_or_add_const(i32_t, '64')
3667 fmt_val := b.mod.add_value_node(.c_string_literal, ptr_type, inter.resolved_fmt, 0)
3668 sn_len := b.mod.add_instr(.call, b.cur_block, i32_t, [snprintf_ref, buf_ptr, size_val,
3669 fmt_val, formatted_val])
3670 // 4. Call builtin__tos(buf_ptr, len) to make a V string
3671 tos_ref := b.get_or_create_fn_ref('builtin__tos', str_type)
3672 str_val := b.mod.add_instr(.call, b.cur_block, str_type, [tos_ref, buf_ptr, sn_len])
3673 parts << str_val
3674 } else {
3675 // Unformatted or simple format: use string concatenation.
3676 // If the inter.expr is a SelectorExpr accessing '.str' on a string,
3677 // use the base expression (the V string) directly.
3678 mut use_expr := inter.expr
3679 if inter.expr is ast.SelectorExpr {
3680 sel := inter.expr as ast.SelectorExpr
3681 if sel.rhs.name == 'str' {
3682 use_expr = sel.lhs
3683 }
3684 }
3685 inter_val := b.build_expr(use_expr)
3686 inter_type := b.mod.values[inter_val].typ
3687 // Convert the interpolation value to string
3688 str_val := b.convert_to_string(inter_val, inter_type)
3689 parts << str_val
3690 }
3691 }
3692 }
3693
3694 if parts.len == 0 {
3695 return b.mod.add_value_node(.string_literal, str_type, '', 0)
3696 }
3697 if parts.len == 1 {
3698 return parts[0]
3699 }
3700
3701 // Concatenate all parts with string__+
3702 mut result := parts[0]
3703 for i := 1; i < parts.len; i++ {
3704 result = b.mod.add_instr(.call, b.cur_block, str_type, [plus_fn, result, parts[i]])
3705 }
3706 return result
3707}
3708
3709fn (mut b Builder) prepare_snprintf_arg(val ValueID, fmt string) ValueID {
3710 if fmt.len == 0 || val <= 0 || val >= b.mod.values.len {
3711 return val
3712 }
3713 typ_id := b.mod.values[val].typ
3714 if typ_id <= 0 || typ_id >= b.mod.type_store.types.len {
3715 return val
3716 }
3717 typ := b.mod.type_store.types[typ_id]
3718 fmt_char := fmt[fmt.len - 1]
3719 match fmt_char {
3720 `d`, `i`, `u`, `o`, `x`, `X`, `c` {
3721 if typ.kind != .int_t {
3722 return val
3723 }
3724 needs_u32 := fmt_char in [`u`, `o`, `x`, `X`]
3725 if fmt.contains('ll') {
3726 target := if needs_u32 {
3727 b.mod.type_store.get_uint(64)
3728 } else {
3729 b.mod.type_store.get_int(64)
3730 }
3731 return b.cast_value_to_type(val, target)
3732 }
3733 target := if needs_u32 {
3734 b.mod.type_store.get_uint(32)
3735 } else {
3736 b.mod.type_store.get_int(32)
3737 }
3738 return b.cast_value_to_type(val, target)
3739 }
3740 `f`, `e`, `g` {
3741 if typ.kind == .float_t && typ.width == 32 {
3742 return b.cast_value_to_type(val, b.mod.type_store.get_float(64))
3743 }
3744 }
3745 else {}
3746 }
3747
3748 return val
3749}
3750
3751fn (mut b Builder) convert_to_string(val ValueID, typ TypeID) ValueID {
3752 str_type := b.get_string_type()
3753 // If already a string, return as-is
3754 if str_type != 0 && typ == str_type {
3755 return val
3756 }
3757 // Also check if the type is a struct named 'string' (may have different ID)
3758 if typ < b.mod.type_store.types.len {
3759 tinfo := b.mod.type_store.types[typ]
3760 if tinfo.kind == .struct_t {
3761 // Check if this is the string struct by field count (str, len, is_lit)
3762 if tinfo.fields.len == 3 || typ == str_type {
3763 return val
3764 }
3765 }
3766 }
3767 // Determine the conversion function based on the type
3768 type_info := b.mod.type_store.types[typ]
3769 mut fn_name := ''
3770 if type_info.kind == .int_t {
3771 if type_info.width == 1 {
3772 // bool
3773 fn_name = 'builtin__bool__str'
3774 } else if type_info.width == 64 {
3775 fn_name = 'builtin__i64__str'
3776 } else {
3777 fn_name = 'builtin__int__str'
3778 }
3779 } else if type_info.kind == .float_t {
3780 if type_info.width == 32 {
3781 fn_name = 'builtin__f32__str'
3782 } else {
3783 fn_name = 'builtin__f64__str'
3784 }
3785 }
3786 if fn_name != '' {
3787 fn_ref := b.get_or_create_fn_ref(fn_name, str_type)
3788 return b.mod.add_instr(.call, b.cur_block, str_type, [fn_ref, val])
3789 }
3790 // Fallback: return empty string
3791 return b.mod.add_value_node(.string_literal, str_type, '', 0)
3792}
3793
3794fn (mut b Builder) build_ident(ident ast.Ident) ValueID {
3795 // Handle 'nil' identifier (generated by transformer from `unsafe { nil }`)
3796 if ident.name == 'nil' {
3797 i8_t := b.mod.type_store.get_int(8)
3798 ptr_t := b.mod.type_store.get_ptr(i8_t)
3799 return b.mod.get_or_add_const(ptr_t, '0')
3800 }
3801 if ident.name in b.vars {
3802 ptr := b.vars[ident.name]
3803 ptr_typ := b.mod.values[ptr].typ
3804 elem_typ := b.mod.type_store.types[ptr_typ].elem_type
3805 val := b.mod.add_instr(.load, b.cur_block, elem_typ, [ptr])
3806 // For mut pointer params (e.g., mut buf &u8), the alloca stores ptr(ptr(T)).
3807 // One load gives ptr(ptr(T)), but user sees buf as ptr(T).
3808 // Add extra dereference to get the actual pointer value.
3809 if ident.name in b.mut_ptr_params {
3810 inner_typ := b.mod.type_store.types[elem_typ].elem_type
3811 if inner_typ != 0 {
3812 return b.mod.add_instr(.load, b.cur_block, inner_typ, [val])
3813 }
3814 }
3815 return val
3816 }
3817 // Could be a constant, enum value, or function reference
3818 if ident.name in b.enum_values {
3819 val := b.enum_values[ident.name]
3820 return b.mod.get_or_add_const(b.mod.type_store.get_int(32), val.str())
3821 }
3822 // Try enum value with module prefix (e.g., Token__key_fn → token__Token__key_fn)
3823 {
3824 enum_qualified := '${b.cur_module}__${ident.name}'
3825 if enum_qualified in b.enum_values {
3826 val := b.enum_values[enum_qualified]
3827 return b.mod.get_or_add_const(b.mod.type_store.get_int(32), val.str())
3828 }
3829 }
3830 // Try as function reference
3831 if ident.name in b.fn_index {
3832 return b.get_or_create_fn_ref(ident.name, 0)
3833 }
3834 // Try with module prefix (transformer strips module prefix for builtin functions)
3835 builtin_name := 'builtin__${ident.name}'
3836 if builtin_name in b.fn_index {
3837 return b.get_or_create_fn_ref(builtin_name, 0)
3838 }
3839 qualified_name := '${b.cur_module}__${ident.name}'
3840 if qualified_name in b.fn_index {
3841 return b.get_or_create_fn_ref(qualified_name, 0)
3842 }
3843 // Try as float constant (inline as f64)
3844 if fval := b.float_const_values[ident.name] {
3845 return b.mod.get_or_add_const(b.mod.type_store.get_float(64), fval)
3846 }
3847 if fval := b.float_const_values[qualified_name] {
3848 return b.mod.get_or_add_const(b.mod.type_store.get_float(64), fval)
3849 }
3850 builtin_const := 'builtin__${ident.name}'
3851 if fval := b.float_const_values[builtin_const] {
3852 return b.mod.get_or_add_const(b.mod.type_store.get_float(64), fval)
3853 }
3854 // Try as compile-time constant (inline the value directly)
3855 if ident.name in b.const_values {
3856 ct := if ident.name in b.const_value_types {
3857 b.const_value_types[ident.name]
3858 } else {
3859 b.mod.type_store.get_int(64)
3860 }
3861 return b.mod.get_or_add_const(ct, b.const_values[ident.name].str())
3862 }
3863 if qualified_name in b.const_values {
3864 ct := if qualified_name in b.const_value_types {
3865 b.const_value_types[qualified_name]
3866 } else {
3867 b.mod.type_store.get_int(64)
3868 }
3869 return b.mod.get_or_add_const(ct, b.const_values[qualified_name].str())
3870 }
3871 if builtin_const in b.const_values {
3872 ct := if builtin_const in b.const_value_types {
3873 b.const_value_types[builtin_const]
3874 } else {
3875 b.mod.type_store.get_int(64)
3876 }
3877 return b.mod.get_or_add_const(ct, b.const_values[builtin_const].str())
3878 }
3879 // Try as string constant (inline the string literal directly)
3880 if ident.name in b.string_const_values {
3881 return b.build_string_literal(ast.StringLiteral{
3882 kind: .v
3883 value: b.string_const_values[ident.name]
3884 })
3885 }
3886 if qualified_name in b.string_const_values {
3887 return b.build_string_literal(ast.StringLiteral{
3888 kind: .v
3889 value: b.string_const_values[qualified_name]
3890 })
3891 }
3892 if builtin_const in b.string_const_values {
3893 return b.build_string_literal(ast.StringLiteral{
3894 kind: .v
3895 value: b.string_const_values[builtin_const]
3896 })
3897 }
3898 // Try as constant array global (return pointer directly for indexing)
3899 if ident.name in b.const_array_globals {
3900 if glob_id := b.find_global(ident.name) {
3901 return glob_id
3902 }
3903 // Cross-module const array: transformer strips the module prefix
3904 // (e.g., "strconv__pow5_split_64_x" → "pow5_split_64_x").
3905 // Scan globals for a match with any module prefix.
3906 suffix := '__${ident.name}'
3907 for v in b.mod.values {
3908 if v.kind == .global && v.name.ends_with(suffix) {
3909 return v.id
3910 }
3911 }
3912 }
3913 if qualified_name in b.const_array_globals {
3914 if glob_id := b.find_global(qualified_name) {
3915 return glob_id
3916 }
3917 }
3918 if builtin_const in b.const_array_globals {
3919 if glob_id := b.find_global(builtin_const) {
3920 return glob_id
3921 }
3922 }
3923 // Try as global variable
3924 if glob_id := b.find_global(ident.name) {
3925 glob_typ := b.mod.values[glob_id].typ
3926 elem_typ := b.mod.type_store.types[glob_typ].elem_type
3927 return b.mod.add_instr(.load, b.cur_block, elem_typ, [glob_id])
3928 }
3929 // Try with prefixes for globals too
3930 if glob_id := b.find_global(builtin_const) {
3931 glob_typ := b.mod.values[glob_id].typ
3932 elem_typ := b.mod.type_store.types[glob_typ].elem_type
3933 return b.mod.add_instr(.load, b.cur_block, elem_typ, [glob_id])
3934 }
3935 if glob_id := b.find_global(qualified_name) {
3936 glob_typ := b.mod.values[glob_id].typ
3937 elem_typ := b.mod.type_store.types[glob_typ].elem_type
3938 return b.mod.add_instr(.load, b.cur_block, elem_typ, [glob_id])
3939 }
3940 return b.mod.get_or_add_const(b.mod.type_store.get_int(64), '0')
3941}
3942
3943fn (mut b Builder) find_global(name string) ?ValueID {
3944 if name in b.global_refs {
3945 return b.global_refs[name]
3946 }
3947 return none
3948}
3949
3950fn (mut b Builder) index_global_values() {
3951 for v in b.mod.values {
3952 if v.kind == .global {
3953 b.global_refs[v.name] = v.id
3954 }
3955 }
3956}
3957
3958fn (mut b Builder) build_infix(expr ast.InfixExpr) ValueID {
3959 // Short-circuit evaluation for logical || and &&
3960 if expr.op == .logical_or {
3961 bool_type := b.mod.type_store.get_int(1)
3962 // Evaluate LHS
3963 lhs := b.build_expr(expr.lhs)
3964 lhs_block := b.cur_block
3965 // Create blocks: rhs_block (evaluate RHS), merge_block
3966 rhs_block := b.mod.add_block(b.cur_func, 'or_rhs')
3967 merge_block := b.mod.add_block(b.cur_func, 'or_merge')
3968 // If LHS is true, short-circuit to merge; else evaluate RHS
3969 b.mod.add_instr(.br, b.cur_block, 0,
3970 [lhs, b.mod.blocks[merge_block].val_id, b.mod.blocks[rhs_block].val_id])
3971 b.add_edge(b.cur_block, merge_block)
3972 b.add_edge(b.cur_block, rhs_block)
3973 // RHS block
3974 b.cur_block = rhs_block
3975 rhs := b.build_expr(expr.rhs)
3976 rhs_end_block := b.cur_block
3977 if !b.block_has_terminator(b.cur_block) {
3978 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[merge_block].val_id])
3979 b.add_edge(b.cur_block, merge_block)
3980 }
3981 // Merge block: use phi to select result (no alloca/store/load)
3982 b.cur_block = merge_block
3983 one := b.mod.get_or_add_const(bool_type, '1')
3984 phi_val := b.mod.add_instr(.phi, merge_block, bool_type, [one, b.mod.blocks[lhs_block].val_id,
3985 rhs, b.mod.blocks[rhs_end_block].val_id])
3986 return phi_val
3987 }
3988 if expr.op == .and {
3989 bool_type := b.mod.type_store.get_int(1)
3990 // Evaluate LHS
3991 lhs := b.build_expr(expr.lhs)
3992 lhs_block := b.cur_block
3993 // Create blocks: rhs_block (evaluate RHS), merge_block
3994 rhs_block := b.mod.add_block(b.cur_func, 'and_rhs')
3995 merge_block := b.mod.add_block(b.cur_func, 'and_merge')
3996 // If LHS is false, short-circuit to merge with false; else evaluate RHS
3997 b.mod.add_instr(.br, b.cur_block, 0,
3998 [lhs, b.mod.blocks[rhs_block].val_id, b.mod.blocks[merge_block].val_id])
3999 b.add_edge(b.cur_block, rhs_block)
4000 b.add_edge(b.cur_block, merge_block)
4001 // RHS block
4002 b.cur_block = rhs_block
4003 rhs := b.build_expr(expr.rhs)
4004 rhs_end_block := b.cur_block
4005 if !b.block_has_terminator(b.cur_block) {
4006 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[merge_block].val_id])
4007 b.add_edge(b.cur_block, merge_block)
4008 }
4009 // Merge block: use phi to select result (no alloca/store/load)
4010 b.cur_block = merge_block
4011 zero := b.mod.get_or_add_const(bool_type, '0')
4012 phi_val := b.mod.add_instr(.phi, merge_block, bool_type, [zero, b.mod.blocks[lhs_block].val_id,
4013 rhs, b.mod.blocks[rhs_end_block].val_id])
4014 return phi_val
4015 }
4016
4017 lhs := b.build_expr(expr.lhs)
4018 rhs := b.build_expr(expr.rhs)
4019 result_type := b.expr_type(ast.Expr(expr))
4020 // Handle Option/Result comparison with none: x == none / x != none
4021 // Compare the state field (field 0) with 0 (success state) instead of
4022 // doing a whole-struct comparison with an integer.
4023 if (expr.op == .eq || expr.op == .ne) && (b.is_none_expr(expr.rhs) || b.is_none_expr(expr.lhs)) {
4024 option_val := if b.is_none_expr(expr.rhs) { lhs } else { rhs }
4025 option_type := b.mod.values[option_val].typ
4026 if b.is_option_wrapper_type(option_type) {
4027 // Extract state field and check: state != 0 means none/error
4028 i32_t := b.mod.type_store.get_int(32)
4029 bool_t := b.mod.type_store.get_int(1)
4030 flag_idx := b.mod.get_or_add_const(i32_t, '0')
4031 flag_type := b.mod.type_store.types[option_type].fields[0]
4032 flag_val := b.mod.add_instr(.extractvalue, b.cur_block, flag_type, [
4033 option_val,
4034 flag_idx,
4035 ])
4036 zero_flag := b.mod.get_or_add_const(flag_type, '0')
4037 if expr.op == .eq {
4038 // x == none → state != 0 (non-zero state means none)
4039 return b.mod.add_instr(.ne, b.cur_block, bool_t, [flag_val, zero_flag])
4040 } else {
4041 // x != none → state == 0 (zero state means success/has value)
4042 return b.mod.add_instr(.eq, b.cur_block, bool_t, [flag_val, zero_flag])
4043 }
4044 }
4045 if b.is_result_wrapper_type(option_type) {
4046 i32_t := b.mod.type_store.get_int(32)
4047 bool_t := b.mod.type_store.get_int(1)
4048 flag_idx := b.mod.get_or_add_const(i32_t, '0')
4049 flag_type := b.mod.type_store.types[option_type].fields[0]
4050 flag_val := b.mod.add_instr(.extractvalue, b.cur_block, flag_type, [
4051 option_val,
4052 flag_idx,
4053 ])
4054 zero_flag := b.mod.get_or_add_const(flag_type, '0')
4055 if expr.op == .eq {
4056 return b.mod.add_instr(.ne, b.cur_block, bool_t, [flag_val, zero_flag])
4057 } else {
4058 return b.mod.add_instr(.eq, b.cur_block, bool_t, [flag_val, zero_flag])
4059 }
4060 }
4061 }
4062 // Check for string comparison: if either operand is a string-like type
4063 // (string or &string), emit string calls instead of integer comparisons.
4064 // This handles match-on-string expressions where the transformer creates
4065 // InfixExpr{.eq, ...} that bypasses the normal string comparison lowering.
4066 str_type := b.get_string_type()
4067 if str_type != 0 {
4068 lhs_type := b.mod.values[lhs].typ
4069 rhs_type := b.mod.values[rhs].typ
4070 if b.is_string_like_ssa_type(lhs_type) || b.is_string_like_ssa_type(rhs_type) {
4071 string_lhs := b.load_string_like_value(lhs)
4072 string_rhs := b.load_string_like_value(rhs)
4073 if expr.op in [.eq, .ne] {
4074 bool_type := b.mod.type_store.get_int(1)
4075 fn_ref := b.get_or_create_fn_ref('builtin__string__==', bool_type)
4076 eq_result := b.mod.add_instr(.call, b.cur_block, bool_type, [fn_ref, string_lhs,
4077 string_rhs])
4078 if expr.op == .ne {
4079 return b.mod.add_instr(.xor, b.cur_block, bool_type, [eq_result,
4080 b.mod.get_or_add_const(bool_type, '1')])
4081 }
4082 return eq_result
4083 }
4084 if expr.op in [.lt, .gt, .le, .ge] {
4085 bool_type := b.mod.type_store.get_int(1)
4086 fn_ref := b.get_or_create_fn_ref('builtin__string__<', bool_type)
4087 lt_lhs := if expr.op in [.gt, .le] { string_rhs } else { string_lhs }
4088 lt_rhs := if expr.op in [.gt, .le] { string_lhs } else { string_rhs }
4089 lt_result := b.mod.add_instr(.call, b.cur_block, bool_type,
4090 [fn_ref, lt_lhs, lt_rhs])
4091 if expr.op in [.le, .ge] {
4092 return b.mod.add_instr(.xor, b.cur_block, bool_type, [lt_result,
4093 b.mod.get_or_add_const(bool_type, '1')])
4094 }
4095 return lt_result
4096 }
4097 if expr.op == .plus {
4098 fn_ref := b.get_or_create_fn_ref('builtin__string__+', str_type)
4099 return b.mod.add_instr(.call, b.cur_block, str_type,
4100 [fn_ref, string_lhs, string_rhs])
4101 }
4102 }
4103 }
4104
4105 // Check for array comparison: if either operand is an array type,
4106 // call array__eq instead of bitwise comparison.
4107 // This handles cases where the transformer didn't lower the comparison
4108 // (e.g., type aliases like `type Strings = []string`).
4109 array_type := b.get_array_type()
4110 if array_type != 0 && (expr.op == .eq || expr.op == .ne) {
4111 lhs_type := b.mod.values[lhs].typ
4112 rhs_type := b.mod.values[rhs].typ
4113 if lhs_type == array_type || rhs_type == array_type {
4114 bool_type := b.mod.type_store.get_int(1)
4115 fn_ref := b.get_or_create_fn_ref('array__eq', bool_type)
4116 eq_result := b.mod.add_instr(.call, b.cur_block, bool_type, [fn_ref, lhs, rhs])
4117 if expr.op == .ne {
4118 return b.mod.add_instr(.xor, b.cur_block, bool_type, [eq_result,
4119 b.mod.get_or_add_const(bool_type, '1')])
4120 }
4121 return eq_result
4122 }
4123 }
4124
4125 // Pointer arithmetic: ptr + int or int + ptr → GEP for proper element scaling.
4126 // Without this, `*i32 + 1` adds 1 byte instead of 4 bytes.
4127 if expr.op == .plus || expr.op == .minus {
4128 lhs_t := b.mod.values[lhs].typ
4129 rhs_t := b.mod.values[rhs].typ
4130 mut ptr_val := ValueID(0)
4131 mut int_val := ValueID(0)
4132 mut is_ptr_arith := false
4133 if lhs_t > 0 && int(lhs_t) < b.mod.type_store.types.len
4134 && b.mod.type_store.types[lhs_t].kind == .ptr_t {
4135 ptr_val = lhs
4136 int_val = rhs
4137 is_ptr_arith = true
4138 } else if rhs_t > 0 && int(rhs_t) < b.mod.type_store.types.len
4139 && b.mod.type_store.types[rhs_t].kind == .ptr_t {
4140 ptr_val = rhs
4141 int_val = lhs
4142 is_ptr_arith = true
4143 }
4144 if is_ptr_arith {
4145 ptr_type := b.mod.values[ptr_val].typ
4146 if expr.op == .minus {
4147 // ptr - int: negate the integer, then GEP
4148 int_val = b.mod.add_instr(.sub, b.cur_block, b.mod.values[int_val].typ, [
4149 b.mod.get_or_add_const(b.mod.values[int_val].typ, '0'),
4150 int_val,
4151 ])
4152 }
4153 return b.mod.add_instr(.get_element_ptr, b.cur_block, ptr_type, [
4154 ptr_val,
4155 int_val,
4156 ])
4157 }
4158 }
4159
4160 // Check if operands are float type to use float opcodes.
4161 // When one operand is float and the other is int, promote the int to float.
4162 mut lhs_v := lhs
4163 mut rhs_v := rhs
4164 lhs_type := b.mod.values[lhs_v].typ
4165 rhs_type_id := b.mod.values[rhs_v].typ
4166 lhs_is_float := lhs_type > 0 && int(lhs_type) < b.mod.type_store.types.len
4167 && b.mod.type_store.types[lhs_type].kind == .float_t
4168 rhs_is_float := rhs_type_id > 0 && int(rhs_type_id) < b.mod.type_store.types.len
4169 && b.mod.type_store.types[rhs_type_id].kind == .float_t
4170 is_float := lhs_is_float || rhs_is_float
4171 if is_float {
4172 if lhs_is_float && !rhs_is_float {
4173 // Promote RHS int to float: use uitofp for unsigned types, sitofp for signed
4174 rhs_unsigned := rhs_type_id > 0 && int(rhs_type_id) < b.mod.type_store.types.len
4175 && b.mod.type_store.types[rhs_type_id].is_unsigned
4176 conv_op := if rhs_unsigned { OpCode.uitofp } else { OpCode.sitofp }
4177 rhs_v = b.mod.add_instr(conv_op, b.cur_block, lhs_type, [rhs_v])
4178 } else if rhs_is_float && !lhs_is_float {
4179 // Promote LHS int to float: use uitofp for unsigned types, sitofp for signed
4180 lhs_unsigned := lhs_type > 0 && int(lhs_type) < b.mod.type_store.types.len
4181 && b.mod.type_store.types[lhs_type].is_unsigned
4182 conv_op := if lhs_unsigned { OpCode.uitofp } else { OpCode.sitofp }
4183 lhs_v = b.mod.add_instr(conv_op, b.cur_block, rhs_type_id, [lhs_v])
4184 }
4185 }
4186
4187 mut forced_result_type := TypeID(0)
4188 if !is_float && expr.op == .minus {
4189 lhs_t := b.mod.values[lhs_v].typ
4190 rhs_t := b.mod.values[rhs_v].typ
4191 if lhs_t > 0 && rhs_t > 0 && int(lhs_t) < b.mod.type_store.types.len
4192 && int(rhs_t) < b.mod.type_store.types.len {
4193 lhs_info := b.mod.type_store.types[lhs_t]
4194 rhs_info := b.mod.type_store.types[rhs_t]
4195 if lhs_info.kind == .int_t && rhs_info.kind == .int_t && lhs_info.width < 32
4196 && rhs_info.width < 32 {
4197 int_t := b.mod.type_store.get_int(32)
4198 if lhs_t != int_t {
4199 lhs_v = b.mod.add_instr(if lhs_info.is_unsigned { .zext } else { .sext },
4200 b.cur_block, int_t, [lhs_v])
4201 }
4202 if rhs_t != int_t {
4203 rhs_v = b.mod.add_instr(if rhs_info.is_unsigned { .zext } else { .sext },
4204 b.cur_block, int_t, [rhs_v])
4205 }
4206 forced_result_type = int_t
4207 }
4208 }
4209 }
4210
4211 op := if is_float {
4212 match expr.op {
4213 .plus { OpCode.fadd }
4214 .minus { OpCode.fsub }
4215 .mul { OpCode.fmul }
4216 .div { OpCode.fdiv }
4217 .mod { OpCode.frem }
4218 // Comparisons use the same opcodes - the arm64 codegen
4219 // already checks float type for eq/ne/lt/gt/le/ge
4220 .eq { OpCode.eq }
4221 .ne { OpCode.ne }
4222 .lt { OpCode.lt }
4223 .gt { OpCode.gt }
4224 .le { OpCode.le }
4225 .ge { OpCode.ge }
4226 else { OpCode.fadd }
4227 }
4228 } else {
4229 is_unsigned := lhs_type > 0 && int(lhs_type) < b.mod.type_store.types.len
4230 && b.mod.type_store.types[lhs_type].is_unsigned
4231 match expr.op {
4232 .plus {
4233 OpCode.add
4234 }
4235 .minus {
4236 OpCode.sub
4237 }
4238 .mul {
4239 OpCode.mul
4240 }
4241 .div {
4242 if is_unsigned {
4243 OpCode.udiv
4244 } else {
4245 OpCode.sdiv
4246 }
4247 }
4248 .mod {
4249 if is_unsigned {
4250 OpCode.urem
4251 } else {
4252 OpCode.srem
4253 }
4254 }
4255 .eq {
4256 OpCode.eq
4257 }
4258 .ne {
4259 OpCode.ne
4260 }
4261 .lt {
4262 if is_unsigned {
4263 OpCode.ult
4264 } else {
4265 OpCode.lt
4266 }
4267 }
4268 .gt {
4269 if is_unsigned {
4270 OpCode.ugt
4271 } else {
4272 OpCode.gt
4273 }
4274 }
4275 .le {
4276 if is_unsigned {
4277 OpCode.ule
4278 } else {
4279 OpCode.le
4280 }
4281 }
4282 .ge {
4283 if is_unsigned {
4284 OpCode.uge
4285 } else {
4286 OpCode.ge
4287 }
4288 }
4289 .amp {
4290 OpCode.and_
4291 }
4292 .pipe {
4293 OpCode.or_
4294 }
4295 .xor {
4296 OpCode.xor
4297 }
4298 .left_shift {
4299 OpCode.shl
4300 }
4301 .right_shift {
4302 if is_unsigned {
4303 OpCode.lshr
4304 } else {
4305 OpCode.ashr
4306 }
4307 }
4308 else {
4309 OpCode.add
4310 }
4311 }
4312 }
4313
4314 // If expr_type() returned a struct/aggregate type for an arithmetic/bitwise operation,
4315 // it's a stale checker position (e.g., transformer-reconstructed expressions from .map()).
4316 // Fall back to the LHS operand type which is always correct for arithmetic.
4317 mut final_type := result_type
4318 if forced_result_type != 0 {
4319 final_type = forced_result_type
4320 }
4321 if final_type > 0 && int(final_type) < b.mod.type_store.types.len {
4322 kind := b.mod.type_store.types[final_type].kind
4323 if kind == .struct_t || kind == .array_t {
4324 final_type = b.mod.values[lhs_v].typ
4325 }
4326 }
4327 // Widen result type to match operands: if either operand is wider than the
4328 // result type (e.g., lhs is i64 but type checker assigned i32 to the expression),
4329 // use the wider operand type to avoid truncation.
4330 if final_type > 0 && int(final_type) < b.mod.type_store.types.len {
4331 ft := b.mod.type_store.types[final_type]
4332 if ft.kind == .int_t {
4333 lhs_t := b.mod.values[lhs_v].typ
4334 rhs_t := b.mod.values[rhs_v].typ
4335 if lhs_t > 0 && int(lhs_t) < b.mod.type_store.types.len {
4336 lt := b.mod.type_store.types[lhs_t]
4337 if lt.kind == .int_t && lt.width > ft.width {
4338 final_type = lhs_t
4339 }
4340 }
4341 if rhs_t > 0 && int(rhs_t) < b.mod.type_store.types.len {
4342 rt := b.mod.type_store.types[rhs_t]
4343 if rt.kind == .int_t && rt.width > ft.width {
4344 final_type = rhs_t
4345 }
4346 }
4347 }
4348 }
4349 return b.mod.add_instr(op, b.cur_block, final_type, [lhs_v, rhs_v])
4350}
4351
4352fn (mut b Builder) build_prefix(expr ast.PrefixExpr) ValueID {
4353 // Special case: &InitExpr (heap/pointer to struct init)
4354 // Build the struct via alloca+GEP+store but return the pointer instead of loading
4355 if expr.op == .amp && expr.expr is ast.InitExpr {
4356 return b.build_init_expr_ptr(expr.expr)
4357 }
4358
4359 // Special case: &T(expr) → bitcast to *T (pointer type cast)
4360 // In V, &u8(val) means "cast val to type &u8", NOT "address-of (u8 cast of val)".
4361 // The parser interprets &u8(val) as PrefixExpr(.amp, CastExpr/CallOrCastExpr)
4362 // but the V semantic is always a pointer type cast.
4363 // Handle both pointer and integer sources (int→ptr is needed for &u8(u64_val)).
4364 if expr.op == .amp && expr.expr is ast.CastExpr {
4365 cast_expr := expr.expr as ast.CastExpr
4366 inner_val := b.build_expr(cast_expr.expr)
4367 inner_type := b.mod.values[inner_val].typ
4368 if inner_type != 0 && int(inner_type) < b.mod.type_store.types.len {
4369 inner_t := b.mod.type_store.types[inner_type]
4370 if inner_t.kind == .ptr_t || inner_t.kind == .int_t {
4371 target_elem := b.ast_type_to_ssa(cast_expr.typ)
4372 ptr_type := b.mod.type_store.get_ptr(target_elem)
4373 return b.mod.add_instr(.bitcast, b.cur_block, ptr_type, [inner_val])
4374 }
4375 }
4376 }
4377 // Same for &CallOrCastExpr (parser uses CallOrCastExpr when it cannot distinguish
4378 // between a function call and a type cast, e.g. u8(expr))
4379 if expr.op == .amp && expr.expr is ast.CallOrCastExpr {
4380 coce := expr.expr as ast.CallOrCastExpr
4381 inner_val := b.build_expr(coce.expr)
4382 inner_type := b.mod.values[inner_val].typ
4383 if inner_type != 0 && int(inner_type) < b.mod.type_store.types.len {
4384 inner_t := b.mod.type_store.types[inner_type]
4385 if inner_t.kind == .ptr_t || inner_t.kind == .int_t {
4386 target_elem := b.ast_type_to_ssa(coce.lhs)
4387 ptr_type := b.mod.type_store.get_ptr(target_elem)
4388 return b.mod.add_instr(.bitcast, b.cur_block, ptr_type, [inner_val])
4389 }
4390 }
4391 }
4392 // Handle &&T(expr) — nested ampersand pointer-type cast pattern.
4393 // Parser creates PrefixExpr(.amp, PrefixExpr(.amp, CallOrCastExpr/CastExpr(T, expr)))
4394 // for &&T(expr), meaning "cast expr to type **T", not "address-of address-of cast".
4395 if expr.op == .amp && expr.expr is ast.PrefixExpr {
4396 inner_prefix := expr.expr as ast.PrefixExpr
4397 if inner_prefix.op == .amp {
4398 if inner_prefix.expr is ast.CallOrCastExpr {
4399 coce := inner_prefix.expr as ast.CallOrCastExpr
4400 inner_val := b.build_expr(coce.expr)
4401 inner_type := b.mod.values[inner_val].typ
4402 if inner_type != 0 && int(inner_type) < b.mod.type_store.types.len {
4403 inner_t := b.mod.type_store.types[inner_type]
4404 if inner_t.kind == .ptr_t || inner_t.kind == .int_t {
4405 // &&T(expr): cast to **T = ptr(ptr(T))
4406 target_elem := b.ast_type_to_ssa(coce.lhs)
4407 ptr_type := b.mod.type_store.get_ptr(target_elem)
4408 ptr_ptr_type := b.mod.type_store.get_ptr(ptr_type)
4409 return b.mod.add_instr(.bitcast, b.cur_block, ptr_ptr_type, [
4410 inner_val,
4411 ])
4412 }
4413 }
4414 } else if inner_prefix.expr is ast.CastExpr {
4415 cast_expr := inner_prefix.expr as ast.CastExpr
4416 inner_val := b.build_expr(cast_expr.expr)
4417 inner_type := b.mod.values[inner_val].typ
4418 if inner_type != 0 && int(inner_type) < b.mod.type_store.types.len {
4419 inner_t := b.mod.type_store.types[inner_type]
4420 if inner_t.kind == .ptr_t || inner_t.kind == .int_t {
4421 target_elem := b.ast_type_to_ssa(cast_expr.typ)
4422 ptr_type := b.mod.type_store.get_ptr(target_elem)
4423 ptr_ptr_type := b.mod.type_store.get_ptr(ptr_type)
4424 return b.mod.add_instr(.bitcast, b.cur_block, ptr_ptr_type, [
4425 inner_val,
4426 ])
4427 }
4428 }
4429 }
4430 }
4431 }
4432
4433 // Special case: negation of float literal → create negated constant directly.
4434 // This handles -0.0 correctly (fsub(0,0) = +0.0 per IEEE 754, but -0.0 has sign bit set).
4435 if expr.op == .minus && expr.expr is ast.BasicLiteral {
4436 lit := expr.expr as ast.BasicLiteral
4437 is_float_lit := lit.value.contains('.')
4438 || (!lit.value.starts_with('0x') && !lit.value.starts_with('0X')
4439 && (lit.value.contains('e') || lit.value.contains('E')))
4440 if is_float_lit {
4441 neg_str := '-' + lit.value
4442 float_type := b.mod.type_store.get_float(64)
4443 return b.mod.get_or_add_const(float_type, neg_str)
4444 }
4445 }
4446
4447 val := b.build_expr(expr.expr)
4448
4449 match expr.op {
4450 .minus {
4451 val_type := b.mod.values[val].typ
4452 is_float := val_type > 0 && int(val_type) < b.mod.type_store.types.len
4453 && b.mod.type_store.types[val_type].kind == .float_t
4454 if is_float {
4455 // Float negation at runtime: XOR with sign bit mask.
4456 // Use integer type for XOR since it's a bit operation.
4457 i64_type := b.mod.type_store.get_int(64)
4458 sign_mask := b.mod.get_or_add_const(i64_type, '0x8000000000000000')
4459 int_val := b.mod.add_instr(.bitcast, b.cur_block, i64_type, [val])
4460 xored := b.mod.add_instr(.xor, b.cur_block, i64_type, [int_val, sign_mask])
4461 return b.mod.add_instr(.bitcast, b.cur_block, val_type, [xored])
4462 }
4463 zero := b.mod.get_or_add_const(val_type, '0')
4464 return b.mod.add_instr(.sub, b.cur_block, val_type, [zero, val])
4465 }
4466 .not {
4467 // Logical NOT: !x → (x == 0)
4468 // Returns 1 if x is 0, 0 if x is non-zero
4469 zero := b.mod.get_or_add_const(b.mod.values[val].typ, '0')
4470 return b.mod.add_instr(.eq, b.cur_block, b.mod.type_store.get_int(1), [
4471 val,
4472 zero,
4473 ])
4474 }
4475 .amp {
4476 // Address-of: return the alloca pointer for the variable
4477 addr := b.build_addr(expr.expr)
4478 if addr != 0 {
4479 // Sumtype `_data` stores escape the current scope. Materialize a heap copy
4480 // instead of passing through the address of stack-backed data.
4481 if b.in_sumtype_data {
4482 if heap_ptr := b.heap_copy_from_address(addr) {
4483 return heap_ptr
4484 }
4485 }
4486 return addr
4487 }
4488 // No addressable location (e.g. function call return value) –
4489 // For function references, &fn_name is just the function pointer
4490 // itself — no extra indirection needed.
4491 if b.mod.values[val].kind == .func_ref {
4492 return val
4493 }
4494 // For sumtype boxing, the pointee must outlive the wrapping scope.
4495 if b.in_sumtype_data {
4496 if heap_ptr := b.heap_copy_value(val) {
4497 return heap_ptr
4498 }
4499 }
4500 // For struct types, use heap allocation so the pointer survives
4501 // the current scope (needed for sum type boxing where _data
4502 // must outlive the wrapping function).
4503 // For scalars, use stack alloca (they're typically short-lived).
4504 val_type := b.mod.values[val].typ
4505 if val_type != 0 {
4506 ptr_type := b.mod.type_store.get_ptr(val_type)
4507 typ_info := b.mod.type_store.types[val_type]
4508 if typ_info.kind == .struct_t {
4509 // Heap-allocate struct values to ensure pointer validity
4510 heap_ptr := b.mod.add_instr(.heap_alloc, b.cur_block, ptr_type, []ValueID{})
4511 b.mod.add_instr(.store, b.cur_block, 0, [val, heap_ptr])
4512 return heap_ptr
4513 }
4514 alloca := b.mod.add_instr(.alloca, b.cur_block, ptr_type, []ValueID{})
4515 b.mod.add_instr(.store, b.cur_block, 0, [val, alloca])
4516 return alloca
4517 }
4518 return val
4519 }
4520 .bit_not {
4521 neg_one := b.mod.get_or_add_const(b.mod.values[val].typ, '-1')
4522 return b.mod.add_instr(.xor, b.cur_block, b.mod.values[val].typ, [val, neg_one])
4523 }
4524 .mul {
4525 // Dereference: load from pointer
4526 val_type := b.mod.values[val].typ
4527 if val_type != 0 && int(val_type) < b.mod.type_store.types.len {
4528 typ := b.mod.type_store.types[val_type]
4529 if typ.kind == .ptr_t && typ.elem_type != 0 {
4530 return b.mod.add_instr(.load, b.cur_block, typ.elem_type, [val])
4531 }
4532 }
4533 return val
4534 }
4535 else {
4536 return val
4537 }
4538 }
4539}
4540
4541fn (mut b Builder) build_call(expr ast.CallExpr) ValueID {
4542 // Resolve function name
4543 fn_name := b.resolve_call_name(expr)
4544 mut module_call_name := ''
4545 if expr.lhs is ast.SelectorExpr {
4546 module_call_name = b.selector_module_name(expr.lhs as ast.SelectorExpr) or { '' }
4547 }
4548
4549 // Check if this is a type cast disguised as a call (e.g., IError(ptr)).
4550 // If the name is a struct type and not a registered function, treat as cast.
4551 if fn_name !in b.fn_index && expr.args.len == 1 {
4552 // Try to find a struct type matching this name
4553 if target_type := b.struct_types[fn_name] {
4554 val := b.build_expr(expr.args[0])
4555 src_type := b.mod.values[val].typ
4556 if src_type == target_type {
4557 return val
4558 }
4559 return b.mod.add_instr(.bitcast, b.cur_block, target_type, [val])
4560 }
4561 // Also try with module prefix
4562 qualified := '${b.cur_module}__${fn_name}'
4563 if target_type := b.struct_types[qualified] {
4564 val := b.build_expr(expr.args[0])
4565 src_type := b.mod.values[val].typ
4566 if src_type == target_type {
4567 return val
4568 }
4569 return b.mod.add_instr(.bitcast, b.cur_block, target_type, [val])
4570 }
4571 }
4572
4573 mut ret_type := b.expr_type(ast.Expr(expr))
4574 // If the function is registered, always use its declared return type.
4575 // This is critical for void functions like push_noscan: the checker may
4576 // annotate the transformed call expression with the type of the original
4577 // expression (e.g., array struct for `arr << val`), but the actual
4578 // function returns void. Using the wrong return type causes the ARM64
4579 // backend to emit incorrect struct-return ABI handling.
4580 if fn_name in b.fn_index {
4581 fn_idx := b.fn_index[fn_name]
4582 ret_type = b.mod.funcs[fn_idx].typ
4583 }
4584 // Check if this is a function pointer field call (e.g., m.hash_fn(pkey))
4585 // rather than a method call. If the selector field matches a struct field name
4586 // and the resolved function name is not in fn_index, it's a function pointer call.
4587 if expr.lhs is ast.SelectorExpr {
4588 sel := expr.lhs as ast.SelectorExpr
4589 if module_call_name == '' && fn_name !in b.fn_index {
4590 field_name := sel.rhs.name
4591 mut is_fnptr_field_call := b.is_struct_field(sel.lhs, field_name)
4592 // Interface method calls are transformed to selector-based function-pointer
4593 // calls (e.g. `i.msg(i._object)`), but the receiver type is `Interface`,
4594 // so `is_struct_field` alone returns false.
4595 if !is_fnptr_field_call && b.env != unsafe { nil } {
4596 sel_pos := sel.pos
4597 if sel_pos.is_valid() {
4598 if sel_type := b.env.get_expr_type(sel_pos.id) {
4599 if sel_type is types.FnType {
4600 is_fnptr_field_call = true
4601 }
4602 }
4603 }
4604 }
4605 if is_fnptr_field_call {
4606 // Build the selector expression to get the function pointer value
4607 fn_ptr := b.build_selector(sel)
4608 // Recover return type for function-pointer field calls from checker
4609 // metadata. The transformed AST can lose direct call expression typing,
4610 // which would otherwise fall back to i64 and break ABI lowering.
4611 mut call_ret := ret_type
4612 if b.env != unsafe { nil } {
4613 lhs_pos := sel.pos
4614 if lhs_pos.is_valid() {
4615 if field_type := b.env.get_expr_type(lhs_pos.id) {
4616 unwrapped := b.unwrap_alias_type(field_type)
4617 if unwrapped is types.FnType {
4618 if fn_ret := unwrapped.get_return_type() {
4619 call_ret = b.type_to_ssa(fn_ret)
4620 }
4621 }
4622 }
4623 }
4624 }
4625 // Build arguments (no receiver - this is a field access, not a method call)
4626 mut fnptr_args := []ValueID{}
4627 for arg in expr.args {
4628 if arg is ast.ModifierExpr && arg.kind == .key_mut {
4629 addr := b.build_addr(arg.expr)
4630 if addr != 0 {
4631 fnptr_args << addr
4632 } else {
4633 fnptr_args << b.build_expr(arg)
4634 }
4635 } else {
4636 fnptr_args << b.build_expr(arg)
4637 }
4638 }
4639 mut operands := []ValueID{cap: fnptr_args.len + 1}
4640 operands << fn_ptr
4641 operands << fnptr_args
4642 return b.mod.add_instr(.call_indirect, b.cur_block, call_ret, operands)
4643 }
4644 }
4645 }
4646
4647 // Build arguments
4648 mut args := []ValueID{}
4649 // For method calls, add receiver as first arg
4650 if expr.lhs is ast.SelectorExpr {
4651 sel := expr.lhs as ast.SelectorExpr
4652 // Check if this is a method call (not a module function call)
4653 if module_call_name == '' {
4654 // Check if method expects pointer receiver (mut receiver)
4655 mut expects_ptr := false
4656 if fn_name in b.fn_index {
4657 fn_idx := b.fn_index[fn_name]
4658 if b.mod.funcs[fn_idx].params.len > 0 {
4659 param_type := b.mod.values[b.mod.funcs[fn_idx].params[0]].typ
4660 if param_type < b.mod.type_store.types.len
4661 && b.mod.type_store.types[param_type].kind == .ptr_t {
4662 expects_ptr = true
4663 }
4664 }
4665 }
4666 mut receiver := if expects_ptr {
4667 // Mut receiver: pass address (pointer to struct)
4668 addr := b.build_addr(sel.lhs)
4669 if addr != 0 {
4670 // build_addr for an Ident returns the alloca.
4671 // For a mut receiver variable, the alloca is ptr(ptr(Struct)),
4672 // storing the struct pointer. We need to load from the alloca
4673 // to get the actual struct pointer ptr(Struct).
4674 addr_typ := b.mod.values[addr].typ
4675 if addr_typ < b.mod.type_store.types.len
4676 && b.mod.type_store.types[addr_typ].kind == .ptr_t {
4677 inner := b.mod.type_store.types[addr_typ].elem_type
4678 if inner < b.mod.type_store.types.len
4679 && b.mod.type_store.types[inner].kind == .ptr_t {
4680 pointee := b.mod.type_store.types[inner].elem_type
4681 if pointee < b.mod.type_store.types.len
4682 && b.mod.type_store.types[pointee].kind == .struct_t {
4683 // addr is ptr(ptr(Struct)) — load to get ptr(Struct)
4684 b.mod.add_instr(.load, b.cur_block, inner, [addr])
4685 } else {
4686 addr
4687 }
4688 } else {
4689 addr
4690 }
4691 } else {
4692 addr
4693 }
4694 } else {
4695 b.build_expr(sel.lhs)
4696 }
4697 } else {
4698 b.build_expr(sel.lhs)
4699 }
4700 // Auto-deref pointer receivers: if receiver is a pointer to a struct
4701 // but the method expects a value receiver, load through the pointer
4702 if !expects_ptr {
4703 recv_typ := b.mod.values[receiver].typ
4704 if recv_typ < b.mod.type_store.types.len
4705 && b.mod.type_store.types[recv_typ].kind == .ptr_t {
4706 pointee := b.mod.type_store.types[recv_typ].elem_type
4707 if pointee < b.mod.type_store.types.len
4708 && b.mod.type_store.types[pointee].kind == .struct_t {
4709 receiver = b.mod.add_instr(.load, b.cur_block, pointee, [
4710 receiver,
4711 ])
4712 }
4713 }
4714 }
4715 args << receiver
4716 }
4717 }
4718 // Determine parameter types to decide if we should pass addresses for mut receivers
4719 mut fn_params := []ValueID{}
4720 if fn_name in b.fn_index {
4721 fn_params = b.mod.funcs[b.fn_index[fn_name]].params.clone()
4722 }
4723 for arg in expr.args {
4724 // For mut arguments, pass the address (pointer) instead of the value.
4725 // But if the value is already a pointer (e.g., &FileSet field or parameter),
4726 // pass it directly instead of creating an extra level of indirection.
4727 if arg is ast.ModifierExpr && arg.kind == .key_mut {
4728 // Check if the parameter expects ptr(struct)
4729 param_idx := args.len
4730 mut param_wants_ptr_to_struct := false
4731 if param_idx < fn_params.len {
4732 param_type := b.mod.values[fn_params[param_idx]].typ
4733 if param_type < b.mod.type_store.types.len
4734 && b.mod.type_store.types[param_type].kind == .ptr_t {
4735 pointee := b.mod.type_store.types[param_type].elem_type
4736 if pointee < b.mod.type_store.types.len
4737 && b.mod.type_store.types[pointee].kind == .struct_t {
4738 param_wants_ptr_to_struct = true
4739 }
4740 }
4741 }
4742 if param_wants_ptr_to_struct {
4743 // Check if the value is already a pointer — if so, pass it directly
4744 val := b.build_expr(arg.expr)
4745 val_type := b.mod.values[val].typ
4746 val_is_ptr := val_type < b.mod.type_store.types.len
4747 && b.mod.type_store.types[val_type].kind == .ptr_t
4748 if val_is_ptr {
4749 args << val
4750 } else {
4751 // Value type: take its address
4752 addr := b.build_addr(arg.expr)
4753 if addr != 0 {
4754 args << addr
4755 } else {
4756 alloca_type := b.mod.type_store.get_ptr(val_type)
4757 tmp_alloca := b.mod.add_instr(.alloca, b.cur_block, alloca_type,
4758 []ValueID{})
4759 b.mod.add_instr(.store, b.cur_block, 0, [val, tmp_alloca])
4760 args << tmp_alloca
4761 }
4762 }
4763 } else {
4764 // Check if the parameter type is already a pointer (e.g., `mut buf &u8`).
4765 // If so, the function takes a plain pointer, not pointer-to-pointer,
4766 // so evaluate the expression directly instead of taking its address.
4767 param_idx2 := args.len
4768 mut param_is_already_ptr := false
4769 if param_idx2 < fn_params.len {
4770 pt := b.mod.values[fn_params[param_idx2]].typ
4771 if pt < b.mod.type_store.types.len && b.mod.type_store.types[pt].kind == .ptr_t {
4772 pointee2 := b.mod.type_store.types[pt].elem_type
4773 // Only skip addr-of if pointee is NOT a struct (struct mut params need addr-of)
4774 if pointee2 < b.mod.type_store.types.len
4775 && b.mod.type_store.types[pointee2].kind != .struct_t {
4776 param_is_already_ptr = true
4777 }
4778 }
4779 }
4780 if param_is_already_ptr
4781 || (arg.expr is ast.PrefixExpr && (arg.expr as ast.PrefixExpr).op == .amp) {
4782 // Parameter already expects a pointer value (not pointer-to-value),
4783 // or argument explicitly provides a pointer with &.
4784 args << b.build_expr(arg.expr)
4785 } else {
4786 addr := b.build_addr(arg.expr)
4787 if addr != 0 {
4788 args << addr
4789 } else {
4790 val := b.build_expr(arg.expr)
4791 val_type := b.mod.values[val].typ
4792 alloca_type := b.mod.type_store.get_ptr(val_type)
4793 tmp_alloca := b.mod.add_instr(.alloca, b.cur_block, alloca_type,
4794 []ValueID{})
4795 b.mod.add_instr(.store, b.cur_block, 0, [val, tmp_alloca])
4796 args << tmp_alloca
4797 }
4798 }
4799 }
4800 } else {
4801 // Use args.len as param index (accounts for receiver already in args)
4802 param_idx := args.len
4803 mut param_wants_struct_ptr := false
4804 if param_idx < fn_params.len {
4805 param_type := b.mod.values[fn_params[param_idx]].typ
4806 // Only use build_addr for ptr(struct_t) params (mut receivers/params),
4807 // NOT for ptr(int_t), ptr(ptr_t) etc. (normal pointer params like C.puts)
4808 if param_type < b.mod.type_store.types.len
4809 && b.mod.type_store.types[param_type].kind == .ptr_t {
4810 pointee := b.mod.type_store.types[param_type].elem_type
4811 if pointee < b.mod.type_store.types.len
4812 && b.mod.type_store.types[pointee].kind == .struct_t {
4813 param_wants_struct_ptr = true
4814 }
4815 }
4816 }
4817 // For args where the function expects ptr(struct) (mut receiver/param),
4818 // try to pass the original variable address instead of a copy
4819 if param_wants_struct_ptr && (arg is ast.Ident || arg is ast.SelectorExpr
4820 || arg is ast.IndexExpr || arg is ast.ParenExpr) {
4821 // First try build_expr: if it already returns ptr(struct), use it directly
4822 val := b.build_expr(arg)
4823 val_typ := b.mod.values[val].typ
4824 val_is_ptr := val_typ < b.mod.type_store.types.len
4825 && b.mod.type_store.types[val_typ].kind == .ptr_t
4826 if val_is_ptr {
4827 // Already a pointer (e.g., mut receiver re-passing its own pointer)
4828 args << val
4829 } else {
4830 // Value type (local mut variable): use build_addr to get the alloca
4831 addr := b.build_addr(arg)
4832 if addr != 0 {
4833 args << addr
4834 } else {
4835 args << val
4836 }
4837 }
4838 } else {
4839 args << b.build_expr(arg)
4840 }
4841 }
4842 }
4843
4844 // Auto-deref/ref arguments to match function parameter types
4845 if fn_params.len > 0 {
4846 for ai := 0; ai < args.len && ai < fn_params.len; ai++ {
4847 arg_type := b.mod.values[args[ai]].typ
4848 param_type := b.mod.values[fn_params[ai]].typ
4849 if arg_type < b.mod.type_store.types.len && param_type < b.mod.type_store.types.len {
4850 arg_kind := b.mod.type_store.types[arg_type].kind
4851 param_kind := b.mod.type_store.types[param_type].kind
4852 // Pointer arg but value param: auto-deref
4853 // Only auto-deref when the pointee is a struct and the
4854 // parameter expects that struct value. Do NOT deref raw
4855 // pointers (ptr(i8)/voidptr) when the param is a plain
4856 // int type (e.g., i64 from variadic params).
4857 if arg_kind == .ptr_t && param_kind == .struct_t {
4858 pointee := b.mod.type_store.types[arg_type].elem_type
4859 if pointee == param_type {
4860 args[ai] = b.mod.add_instr(.load, b.cur_block, pointee, [
4861 args[ai],
4862 ])
4863 }
4864 }
4865 // Value arg but pointer param: auto-ref (alloca + store + pass pointer)
4866 if arg_kind == .struct_t && param_kind == .ptr_t {
4867 ptr_type := b.mod.type_store.get_ptr(arg_type)
4868 alloca := b.mod.add_instr(.alloca, b.cur_block, ptr_type, []ValueID{})
4869 b.mod.add_instr(.store, b.cur_block, 0, [args[ai], alloca])
4870 args[ai] = alloca
4871 }
4872 if arg_kind == .array_t && param_kind == .ptr_t {
4873 pointee := b.mod.type_store.types[param_type].elem_type
4874 if pointee == arg_type {
4875 ptr_type := b.mod.type_store.get_ptr(arg_type)
4876 alloca := b.mod.add_instr(.alloca, b.cur_block, ptr_type, []ValueID{})
4877 b.mod.add_instr(.store, b.cur_block, 0, [args[ai], alloca])
4878 args[ai] = alloca
4879 }
4880 }
4881 // Int arg but float param: auto-convert (sitofp)
4882 if arg_kind == .int_t && param_kind == .float_t {
4883 args[ai] = b.mod.add_instr(.sitofp, b.cur_block, param_type, [
4884 args[ai],
4885 ])
4886 }
4887 // Scalar arg but voidptr param: auto-ref for builtin methods
4888 // (array insert/prepend, map delete/set/get, new_array_with_default, etc. take voidptr = "pointer to element")
4889 i8_t := b.mod.type_store.get_int(8)
4890 voidptr_t := b.mod.type_store.get_ptr(i8_t)
4891 if param_type == voidptr_t && arg_kind in [.int_t, .float_t]
4892 && (fn_name.contains('array__') || fn_name.contains('map__')
4893 || fn_name.contains('new_array')) {
4894 ptr_type := b.mod.type_store.get_ptr(arg_type)
4895 alloca := b.mod.add_instr(.alloca, b.cur_block, ptr_type, []ValueID{})
4896 b.mod.add_instr(.store, b.cur_block, 0, [args[ai], alloca])
4897 args[ai] = alloca
4898 }
4899 }
4900 }
4901 }
4902
4903 // Check if fn_name is a local variable holding a function pointer
4904 // (e.g., `cfn(s)` where cfn is a parameter of type `fn(string) string`)
4905 if fn_name in b.vars {
4906 // The ret_type from expr_type may be wrong — it uses the expression position,
4907 // which for fn-by-name .map() expansion points to the function identifier
4908 // (typed as FnType → ptr(i8)) instead of the call return type.
4909 // Extract the actual return type from the FnType.
4910 mut call_ret := ret_type
4911 if expr.lhs is ast.Ident {
4912 lhs_pos := expr.lhs.pos
4913 if lhs_pos.is_valid() && b.env != unsafe { nil } {
4914 if var_type := b.env.get_expr_type(lhs_pos.id) {
4915 unwrapped := b.unwrap_alias_type(var_type)
4916 if unwrapped is types.FnType {
4917 if fn_ret := unwrapped.get_return_type() {
4918 call_ret = b.type_to_ssa(fn_ret)
4919 }
4920 }
4921 }
4922 }
4923 }
4924 fn_ptr := b.build_ident(ast.Ident{ name: fn_name })
4925 mut indirect_operands := []ValueID{cap: args.len + 1}
4926 indirect_operands << fn_ptr
4927 indirect_operands << args
4928 return b.mod.add_instr(.call_indirect, b.cur_block, call_ret, indirect_operands)
4929 }
4930
4931 // Check if fn_name is a global variable holding a function pointer
4932 // (e.g., `__live_hot_fn(args)` where __live_hot_fn is a global voidptr)
4933 if glob_id := b.find_global(fn_name) {
4934 glob_typ := b.mod.values[glob_id].typ
4935 elem_typ := b.mod.type_store.types[glob_typ].elem_type
4936 fn_ptr := b.mod.add_instr(.load, b.cur_block, elem_typ, [glob_id])
4937 mut indirect_operands := []ValueID{cap: args.len + 1}
4938 indirect_operands << fn_ptr
4939 indirect_operands << args
4940 return b.mod.add_instr(.call_indirect, b.cur_block, ret_type, indirect_operands)
4941 }
4942
4943 fn_ref := b.get_or_create_fn_ref(fn_name, ret_type)
4944 mut operands := []ValueID{cap: args.len + 1}
4945 operands << fn_ref
4946 operands << args
4947
4948 call_val := b.mod.add_instr(.call, b.cur_block, ret_type, operands)
4949
4950 // array__first/last/pop/pop_left return voidptr (pointer to element).
4951 // Dereference the result to get the actual element value.
4952 if fn_name in ['array__first', 'array__last', 'array__pop', 'array__pop_left',
4953 'builtin__array__first', 'builtin__array__last', 'builtin__array__pop',
4954 'builtin__array__pop_left'] {
4955 elem_type := b.infer_array_elem_type_from_receiver(expr)
4956 if elem_type != 0 {
4957 elem_ptr := b.mod.type_store.get_ptr(elem_type)
4958 typed_ptr := b.mod.add_instr(.bitcast, b.cur_block, elem_ptr, [call_val])
4959 return b.mod.add_instr(.load, b.cur_block, elem_type, [typed_ptr])
4960 }
4961 }
4962
4963 return call_val
4964}
4965
4966// infer_array_elem_type_from_receiver infers the element type of an array
4967// from the first argument (receiver) of array methods like first/last/pop.
4968fn (mut b Builder) infer_array_elem_type_from_receiver(expr ast.CallExpr) TypeID {
4969 if b.env == unsafe { nil } || expr.args.len == 0 {
4970 return 0
4971 }
4972 receiver := expr.args[0]
4973 pos := receiver.pos()
4974 if pos.id != 0 {
4975 if arr_typ := b.env.get_expr_type(pos.id) {
4976 if arr_typ is types.Array {
4977 return b.type_to_ssa(arr_typ.elem_type)
4978 }
4979 }
4980 }
4981 return 0
4982}
4983
4984fn (mut b Builder) selector_module_name(sel ast.SelectorExpr) ?string {
4985 if sel.lhs !is ast.Ident {
4986 return none
4987 }
4988 mod_ident := sel.lhs as ast.Ident
4989 if mod_ident.name == 'C' {
4990 return 'C'
4991 }
4992 if b.env != unsafe { nil } {
4993 if scope := b.env.get_scope(b.cur_module) {
4994 if obj := scope.lookup_parent(mod_ident.name, 0) {
4995 if obj is types.Module {
4996 return obj.name.replace('.', '_')
4997 }
4998 return none
4999 }
5000 }
5001 }
5002 qualified := '${mod_ident.name}__${sel.rhs.name}'
5003 if qualified in b.fn_index {
5004 return mod_ident.name
5005 }
5006 return none
5007}
5008
5009fn (mut b Builder) is_module_name(expr ast.Expr) bool {
5010 if expr is ast.Ident {
5011 if b.env != unsafe { nil } {
5012 if scope := b.env.get_scope(b.cur_module) {
5013 if obj := scope.lookup_parent(expr.name, 0) {
5014 return obj is types.Module
5015 }
5016 }
5017 }
5018 }
5019 return false
5020}
5021
5022fn (mut b Builder) resolve_call_name(expr ast.CallExpr) string {
5023 match expr.lhs {
5024 ast.Ident {
5025 name := expr.lhs.name
5026 // Try module-qualified FIRST to avoid shadowing by C functions.
5027 // E.g., os.getenv() should resolve to os__getenv, not C.getenv.
5028 qualified := '${b.cur_module}__${name}'
5029 if qualified in b.fn_index {
5030 return qualified
5031 }
5032 // Check if it's a known function
5033 if name in b.fn_index {
5034 return name
5035 }
5036 // Try builtin-qualified (transformer remaps builtin__X to X)
5037 builtin_qualified := 'builtin__${name}'
5038 if builtin_qualified in b.fn_index {
5039 return builtin_qualified
5040 }
5041 // For names with module prefix (e.g., v__error), try replacing
5042 // the module prefix with builtin__
5043 if idx := name.index('__') {
5044 suffix := name[idx + 2..]
5045 alt := 'builtin__${suffix}'
5046 if alt in b.fn_index {
5047 return alt
5048 }
5049 }
5050 // Resolve operator method names: the transformer generates e.g.
5051 // 'string__plus' but the SSA builder registers 'builtin__string__+'
5052 // Map transformer names to operator symbols.
5053 op_map := {
5054 'plus': '+'
5055 'minus': '-'
5056 'mult': '*'
5057 'div': '/'
5058 'mod': '%'
5059 'eq': '=='
5060 'ne': '!='
5061 'lt': '<'
5062 'gt': '>'
5063 'le': '<='
5064 'ge': '>='
5065 }
5066 if idx2 := name.last_index('__') {
5067 method_part := name[idx2 + 2..]
5068 if op_sym := op_map[method_part] {
5069 type_part := name[..idx2]
5070 op_name := '${type_part}__${op_sym}'
5071 if op_name in b.fn_index {
5072 return op_name
5073 }
5074 op_builtin := 'builtin__${op_name}'
5075 if op_builtin in b.fn_index {
5076 return op_builtin
5077 }
5078 }
5079 }
5080 // Self-hosted ARM64 builds can lower byte helper calls through the
5081 // signed 8-bit prefix even though the builtin methods live on `u8`.
5082 if name.starts_with('i8__') {
5083 byte_name := 'u8__${name['i8__'.len..]}'
5084 if byte_name in b.fn_index {
5085 return byte_name
5086 }
5087 builtin_byte_name := 'builtin__${byte_name}'
5088 if builtin_byte_name in b.fn_index {
5089 return builtin_byte_name
5090 }
5091 }
5092 if name.starts_with('builtin__i8__') {
5093 byte_name := 'builtin__u8__${name['builtin__i8__'.len..]}'
5094 if byte_name in b.fn_index {
5095 return byte_name
5096 }
5097 }
5098 // C functions: strip C__ prefix for direct C interop
5099 if name.starts_with('C__') {
5100 return name[3..]
5101 }
5102 // Transformer generates Array_module__Type__method (V1 naming) but SSA
5103 // registers as module__Array_Type__method. Remap:
5104 // Array_ast__Attribute__has → ast__Array_Attribute__has
5105 if name.starts_with('Array_') {
5106 rest := name[6..] // after "Array_"
5107 if mod_end := rest.index('__') {
5108 mod_name := rest[..mod_end]
5109 after_mod := rest[mod_end + 2..]
5110 remap := '${mod_name}__Array_${after_mod}'
5111 if remap in b.fn_index {
5112 return remap
5113 }
5114 }
5115 }
5116 // V1 C backend naming uses single underscore for type_method (e.g., array_push_noscan),
5117 // but SSA builder uses double underscore (array__push_noscan).
5118 // Try converting known type prefixes from single to double underscore.
5119 v1_type_prefixes := ['array_', 'string_', 'int_', 'i8_', 'i16_', 'i64_', 'u8_', 'u16_',
5120 'u32_', 'u64_', 'f32_', 'f64_', 'bool_', 'map_', 'rune_', 'char_']
5121 // Check bare name (e.g., "array_push_noscan") and builtin-prefixed
5122 for prefix in v1_type_prefixes {
5123 // Check bare name
5124 if name.starts_with(prefix) {
5125 type_name := prefix[..prefix.len - 1]
5126 method_part := name[prefix.len..]
5127 double_name := '${type_name}__${method_part}'
5128 if double_name in b.fn_index {
5129 return double_name
5130 }
5131 double_builtin := 'builtin__${double_name}'
5132 if double_builtin in b.fn_index {
5133 return double_builtin
5134 }
5135 }
5136 // Check builtin__type_method
5137 bp := 'builtin__${prefix}'
5138 if name.starts_with(bp) {
5139 type_name := prefix[..prefix.len - 1]
5140 method_part := name[bp.len..]
5141 double_name := 'builtin__${type_name}__${method_part}'
5142 if double_name in b.fn_index {
5143 return double_name
5144 }
5145 }
5146 }
5147 return name
5148 }
5149 ast.SelectorExpr {
5150 sel := expr.lhs as ast.SelectorExpr
5151 if mod_name := b.selector_module_name(sel) {
5152 // Module function call: module.fn()
5153 // C functions: C.puts() → just 'puts' for direct C interop
5154 if mod_name == 'C' {
5155 return sel.rhs.name
5156 }
5157 qualified := '${mod_name}__${sel.rhs.name}'
5158 if qualified in b.fn_index {
5159 return qualified
5160 }
5161 return qualified
5162 }
5163 // Method call: expr.method()
5164 mut receiver_type := b.get_receiver_type_name(sel.lhs)
5165 // Strip pointer prefix for method resolution (e.g., Point* → Point)
5166 for receiver_type.ends_with('*') {
5167 receiver_type = receiver_type[..receiver_type.len - 1]
5168 }
5169 method_name := '${receiver_type}__${sel.rhs.name}'
5170 if method_name in b.fn_index {
5171 return method_name
5172 }
5173 // Try with builtin__ prefix (e.g., Array_rune__string → builtin__Array_rune__string)
5174 builtin_method := 'builtin__${method_name}'
5175 if builtin_method in b.fn_index {
5176 return builtin_method
5177 }
5178 // Try with current module prefix
5179 if b.cur_module != '' && b.cur_module != 'main' {
5180 mod_method := '${b.cur_module}__${method_name}'
5181 if mod_method in b.fn_index {
5182 return mod_method
5183 }
5184 }
5185 // Try alias names: strings.Builder = []u8 = array. The receiver_type
5186 // from SSA may be 'array', but the method is registered under
5187 // 'strings__Builder'. Find all struct_types names with the same TypeID.
5188 if type_id := b.struct_types[receiver_type] {
5189 for alt_name, alt_id in b.struct_types {
5190 if alt_id == type_id && alt_name != receiver_type {
5191 alt_method := '${alt_name}__${sel.rhs.name}'
5192 if alt_method in b.fn_index {
5193 return alt_method
5194 }
5195 alt_builtin := 'builtin__${alt_method}'
5196 if alt_builtin in b.fn_index {
5197 return alt_builtin
5198 }
5199 }
5200 }
5201 }
5202 // Handle Array_T patterns: Array_u8, Array_int, Array_string, etc.
5203 // These are SSA names for V generic arrays ([]u8, []int, etc.).
5204 // Methods may be registered under 'array__method' (base) or
5205 // 'strings__Builder__method' (for Array_u8 = []u8 = strings.Builder).
5206 if receiver_type.starts_with('Array_') {
5207 // Try array__method (base array type)
5208 array_method := 'array__${sel.rhs.name}'
5209 if array_method in b.fn_index {
5210 return array_method
5211 }
5212 builtin_array := 'builtin__array__${sel.rhs.name}'
5213 if builtin_array in b.fn_index {
5214 return builtin_array
5215 }
5216 // For Array_u8, also try strings__Builder__method
5217 if receiver_type == 'Array_u8' {
5218 builder_method := 'strings__Builder__${sel.rhs.name}'
5219 if builder_method in b.fn_index {
5220 return builder_method
5221 }
5222 builtin_builder := 'builtin__strings__Builder__${sel.rhs.name}'
5223 if builtin_builder in b.fn_index {
5224 return builtin_builder
5225 }
5226 }
5227 // Try specialized Array_T__method (e.g., Array_string__join)
5228 spec_method := 'builtin__${method_name}'
5229 if spec_method in b.fn_index {
5230 return spec_method
5231 }
5232 // For Array_ast__T, try ast__Array_T__method (strip module prefix from type)
5233 if receiver_type.starts_with('Array_ast__') {
5234 inner := receiver_type.replace('Array_ast__', 'Array_')
5235 ast_method := 'ast__${inner}__${sel.rhs.name}'
5236 if ast_method in b.fn_index {
5237 return ast_method
5238 }
5239 }
5240 }
5241 // Self-hosted ARM64 builds can infer byte-oriented receivers as `i8`
5242 // instead of `u8`; fall back to the existing byte helper methods only
5243 // after the direct `i8`/`Array_i8` lookup path failed.
5244 if receiver_type == 'i8' {
5245 byte_method := 'u8__${sel.rhs.name}'
5246 if byte_method in b.fn_index {
5247 return byte_method
5248 }
5249 builtin_byte_method := 'builtin__${byte_method}'
5250 if builtin_byte_method in b.fn_index {
5251 return builtin_byte_method
5252 }
5253 }
5254 if receiver_type == 'Array_i8' {
5255 byte_array_method := 'Array_u8__${sel.rhs.name}'
5256 if byte_array_method in b.fn_index {
5257 return byte_array_method
5258 }
5259 builtin_byte_array_method := 'builtin__${byte_array_method}'
5260 if builtin_byte_array_method in b.fn_index {
5261 return builtin_byte_array_method
5262 }
5263 }
5264 // i64 aliases: time.Duration = i64
5265 if receiver_type == 'i64' {
5266 dur_method := 'time__Duration__${sel.rhs.name}'
5267 if dur_method in b.fn_index {
5268 return dur_method
5269 }
5270 }
5271 // void/voidptr methods
5272 if receiver_type == 'void' || receiver_type == 'voidptr' {
5273 voidptr_method := 'builtin__voidptr__${sel.rhs.name}'
5274 if voidptr_method in b.fn_index {
5275 return voidptr_method
5276 }
5277 }
5278 // array base type: try specialized Array_string__ for array-specific methods
5279 if receiver_type == 'array' {
5280 arr_string_method := 'builtin__Array_string__${sel.rhs.name}'
5281 if arr_string_method in b.fn_index {
5282 return arr_string_method
5283 }
5284 }
5285 // OptionType/ResultType aliased to base Type
5286 if receiver_type.contains('Option') || receiver_type.contains('Result') {
5287 base_method := 'types__Type__${sel.rhs.name}'
5288 if base_method in b.fn_index {
5289 return base_method
5290 }
5291 }
5292 // When method resolution failed completely, scan fn_index for matching suffix.
5293 // Only for 'unknown' receiver (env type lookup failed completely).
5294 if receiver_type == 'unknown' {
5295 method_suffix := '__${sel.rhs.name}'
5296 mut best_match := ''
5297 for fn_name, _ in b.fn_index {
5298 if fn_name.ends_with(method_suffix) {
5299 if b.cur_module != '' && fn_name.starts_with('${b.cur_module}__') {
5300 return fn_name
5301 }
5302 if best_match == '' {
5303 best_match = fn_name
5304 }
5305 }
5306 }
5307 if best_match != '' {
5308 return best_match
5309 }
5310 }
5311 return method_name
5312 }
5313 else {
5314 return 'unknown_fn'
5315 }
5316 }
5317}
5318
5319fn (mut b Builder) get_receiver_type_name(expr ast.Expr) string {
5320 // For literals used as method receivers (e.g., `A`.length_in_bytes(), 65.str()),
5321 // the env type at the literal's position may be the method's function type
5322 // rather than the literal's value type. Handle these cases before env lookup.
5323 if expr is ast.BasicLiteral {
5324 if expr.kind == .char {
5325 return 'rune'
5326 }
5327 if expr.kind == .number {
5328 if expr.value.contains('.') {
5329 return 'f64'
5330 }
5331 return 'int'
5332 }
5333 if expr.kind == .key_true || expr.kind == .key_false {
5334 return 'bool'
5335 }
5336 }
5337 if expr is ast.StringLiteral || expr is ast.StringInterLiteral {
5338 return 'string'
5339 }
5340 if b.env != unsafe { nil } {
5341 pos := expr.pos()
5342 if pos.id != 0 {
5343 if typ := b.env.get_expr_type(pos.id) {
5344 return b.types_type_c_name(typ)
5345 }
5346 }
5347 }
5348 if expr is ast.Ident {
5349 // Try to get the type from the SSA variable's alloca type.
5350 // This is more reliable than env.get_expr_type in ARM64-compiled binaries
5351 // where the type checker's expr_type_values may have corrupt entries.
5352 if var_id := b.vars[expr.name] {
5353 mut var_type := b.mod.values[var_id].typ
5354 // Alloca types are ptr(T), unwrap the pointer to get base type
5355 if var_type != 0 {
5356 ts_type := b.mod.type_store.types[int(var_type)]
5357 if ts_type.kind == .ptr_t && ts_type.elem_type != 0 {
5358 var_type = ts_type.elem_type
5359 }
5360 name := b.type_id_to_receiver_name(var_type)
5361 if name != 'unknown' {
5362 return name
5363 }
5364 }
5365 }
5366 return expr.name
5367 }
5368 // For CallExpr/CallOrCastExpr, try to infer receiver type from the called
5369 // function's return type. This is needed when env.get_expr_type fails (e.g.,
5370 // in ARM64-compiled binaries where the checker's type store may be unreliable).
5371 if expr is ast.CallExpr {
5372 call_fn_name := b.resolve_call_name(expr)
5373 if call_fn_name in b.fn_index {
5374 fn_idx := b.fn_index[call_fn_name]
5375 ret_typ := b.mod.funcs[fn_idx].typ
5376 if ret_typ != 0 {
5377 return b.type_id_to_receiver_name(ret_typ)
5378 }
5379 }
5380 }
5381 if expr is ast.CallOrCastExpr {
5382 // Try resolving as CallExpr for receiver type inference
5383 call_expr := ast.CallExpr{
5384 lhs: expr.lhs
5385 args: if expr.expr is ast.EmptyExpr { []ast.Expr{} } else { [expr.expr] }
5386 }
5387 call_fn_name := b.resolve_call_name(call_expr)
5388 if call_fn_name in b.fn_index {
5389 fn_idx := b.fn_index[call_fn_name]
5390 ret_typ := b.mod.funcs[fn_idx].typ
5391 if ret_typ != 0 {
5392 return b.type_id_to_receiver_name(ret_typ)
5393 }
5394 }
5395 }
5396 // For SelectorExpr (e.g., obj.field), try to resolve the field's type
5397 // by looking up the LHS type and then the field type in struct definitions.
5398 if expr is ast.SelectorExpr {
5399 if b.env != unsafe { nil } {
5400 lhs_pos := expr.lhs.pos()
5401 if lhs_pos.id != 0 {
5402 if lhs_typ := b.env.get_expr_type(lhs_pos.id) {
5403 // If the LHS is a struct, look up the field type
5404 if lhs_typ is types.Struct {
5405 for field in lhs_typ.fields {
5406 if field.name == expr.rhs.name {
5407 return b.types_type_c_name(field.typ)
5408 }
5409 }
5410 }
5411 }
5412 }
5413 }
5414 }
5415 return 'unknown'
5416}
5417
5418// type_id_to_receiver_name converts an SSA TypeID to a C-style receiver name
5419// for method resolution (e.g., 'string', 'array', 'int', struct names).
5420fn (mut b Builder) type_id_to_receiver_name(typ TypeID) string {
5421 // Check against known struct types (reverse lookup)
5422 for name, id in b.struct_types {
5423 if id == typ {
5424 return name
5425 }
5426 }
5427 // Check the SSA type kind for primitives
5428 if int(typ) >= 0 && int(typ) < b.mod.type_store.types.len {
5429 t := b.mod.type_store.types[int(typ)]
5430 match t.kind {
5431 .int_t {
5432 if t.is_unsigned {
5433 return match t.width {
5434 8 { 'u8' }
5435 16 { 'u16' }
5436 64 { 'u64' }
5437 else { 'u32' }
5438 }
5439 }
5440 return match t.width {
5441 8 { 'i8' }
5442 16 { 'i16' }
5443 64 { 'i64' }
5444 else { 'int' }
5445 }
5446 }
5447 .float_t {
5448 return if t.width == 32 { 'f32' } else { 'f64' }
5449 }
5450 .ptr_t {
5451 return 'unknown'
5452 }
5453 else {
5454 return 'unknown'
5455 }
5456 }
5457 }
5458 return 'unknown'
5459}
5460
5461fn (mut b Builder) build_selector(expr ast.SelectorExpr) ValueID {
5462 // Check for enum shorthand: .field in certain contexts
5463 if expr.lhs is ast.EmptyExpr || (expr.lhs is ast.Ident && expr.lhs.name == '') {
5464 // Enum shorthand — try to look up a resolved name
5465 field_name := expr.rhs.name
5466 // Collect all matching enum values (not just the first)
5467 suffix := '__${field_name}'
5468 mut match_keys := []string{}
5469 mut match_vals := []int{}
5470 for key, val in b.enum_values {
5471 if key.ends_with(suffix) {
5472 match_keys << key
5473 match_vals << val
5474 }
5475 }
5476 if match_keys.len == 1 {
5477 return b.mod.get_or_add_const(b.mod.type_store.get_int(32), match_vals[0].str())
5478 }
5479 if match_keys.len > 1 {
5480 // Disambiguate: prefer enum from current module
5481 if b.cur_module != '' {
5482 if b.cur_module == 'main' {
5483 // Main-module enums are registered without module prefix (`Enum__field`).
5484 // Prefer those over similarly-named fields from imported modules.
5485 for i, mk in match_keys {
5486 if mk.split('__').len == 2 {
5487 return b.mod.get_or_add_const(b.mod.type_store.get_int(32),
5488 match_vals[i].str())
5489 }
5490 }
5491 } else {
5492 for i, mk in match_keys {
5493 if mk.starts_with('${b.cur_module}__') {
5494 return b.mod.get_or_add_const(b.mod.type_store.get_int(32),
5495 match_vals[i].str())
5496 }
5497 }
5498 }
5499 }
5500 // Fallback: use the first match
5501 return b.mod.get_or_add_const(b.mod.type_store.get_int(32), match_vals[0].str())
5502 }
5503 return b.mod.get_or_add_const(b.mod.type_store.get_int(32), '0')
5504 }
5505
5506 // Check for qualified enum access: EnumType.value
5507 if expr.lhs is ast.Ident {
5508 // Try: EnumType__value (local) or module__EnumType__value (qualified)
5509 enum_key := '${expr.lhs.name}__${expr.rhs.name}'
5510 if enum_key in b.enum_values {
5511 return b.mod.get_or_add_const(b.mod.type_store.get_int(32),
5512 b.enum_values[enum_key].str())
5513 }
5514 qualified_key := '${b.cur_module}__${enum_key}'
5515 if qualified_key in b.enum_values {
5516 return b.mod.get_or_add_const(b.mod.type_store.get_int(32),
5517 b.enum_values[qualified_key].str())
5518 }
5519 }
5520
5521 // C constant/global access: C.SEEK_END, C.stdout, C.stderr, etc.
5522 if expr.lhs is ast.Ident && expr.lhs.name == 'C' {
5523 c_name := expr.rhs.name
5524 // Well-known C preprocessor constants — emit inline integer values
5525 // since the native backend cannot resolve C macros.
5526 c_const_val := match c_name {
5527 'SEEK_SET' { '0' }
5528 'SEEK_CUR' { '1' }
5529 'SEEK_END' { '2' }
5530 'EOF' { '-1' }
5531 'NULL' { '0' }
5532 'O_RDONLY' { '0' }
5533 'O_WRONLY' { '1' }
5534 'O_RDWR' { '2' }
5535 'O_CREAT' { '512' }
5536 'O_TRUNC' { '1024' }
5537 'O_EXCL' { '2048' }
5538 'O_APPEND' { '8' }
5539 'S_IRUSR' { '256' }
5540 'S_IWUSR' { '128' }
5541 'S_IXUSR' { '64' }
5542 'S_IREAD' { '256' }
5543 'S_IWRITE' { '128' }
5544 'S_IEXEC' { '64' }
5545 'PROT_READ' { '1' }
5546 'PROT_WRITE' { '2' }
5547 'SIGTERM' { '15' }
5548 'SIGKILL' { '9' }
5549 'SIGINT' { '2' }
5550 'STDIN_FILENO' { '0' }
5551 'STDOUT_FILENO' { '1' }
5552 'STDERR_FILENO' { '2' }
5553 'DT_DIR' { '4' }
5554 'DT_REG' { '8' }
5555 'DT_LNK' { '10' }
5556 'DT_UNKNOWN' { '0' }
5557 'ENOENT' { '2' }
5558 'EXIT_SUCCESS' { '0' }
5559 'EXIT_FAILURE' { '1' }
5560 // Termios
5561 'ICANON' { '256' }
5562 'ECHO' { '8' }
5563 'TCSANOW' { '0' }
5564 'ISIG' { '128' }
5565 'IEXTEN' { '1024' }
5566 'TOSTOP' { '4194304' }
5567 // ioctl
5568 'TIOCGWINSZ' { '1074295912' }
5569 // Time
5570 'CLOCK_MONOTONIC' { '6' }
5571 'CLOCK_REALTIME' { '0' }
5572 // System
5573 '_SC_PAGESIZE' { '29' }
5574 '_SC_NPROCESSORS_ONLN' { '58' }
5575 '_SC_PHYS_PAGES' { '200' }
5576 // Errno
5577 'EINTR' { '4' }
5578 'EINVAL' { '22' }
5579 'EAGAIN' { '35' }
5580 'EWOULDBLOCK' { '35' }
5581 'EINPROGRESS' { '36' }
5582 'EACCES' { '13' }
5583 'EFAULT' { '14' }
5584 'EBUSY' { '16' }
5585 'ETIMEDOUT' { '60' }
5586 // Stat mode bits (macOS)
5587 'S_IFBLK' { '24576' }
5588 'S_IFCHR' { '8192' }
5589 'S_IFDIR' { '16384' }
5590 'S_IFIFO' { '4096' }
5591 'S_IFLNK' { '40960' }
5592 'S_IFMT' { '61440' }
5593 'S_IFREG' { '32768' }
5594 'S_IFSOCK' { '49152' }
5595 'S_IRGRP' { '32' }
5596 'S_IROTH' { '4' }
5597 'S_IWGRP' { '16' }
5598 'S_IWOTH' { '2' }
5599 'S_IXGRP' { '8' }
5600 'S_IXOTH' { '1' }
5601 // Ptrace
5602 'PT_DETACH' { '11' }
5603 'PT_TRACE_ME' { '0' }
5604 // Signals
5605 'SIG_ERR' { '-1' }
5606 'SIG_BLOCK' { '1' }
5607 'SIG_UNBLOCK' { '2' }
5608 'SIG_SETMASK' { '3' }
5609 'SIGCONT' { '19' }
5610 'SIGSTOP' { '17' }
5611 // Wait
5612 'WNOHANG' { '1' }
5613 // I/O buffering
5614 '_IOFBF' { '0' }
5615 '_IOLBF' { '1' }
5616 '_IONBF' { '2' }
5617 // File flags
5618 'O_NONBLOCK' { '4' }
5619 'O_CLOEXEC' { '16777216' }
5620 else { '' }
5621 }
5622
5623 if c_const_val.len > 0 {
5624 return b.mod.get_or_add_const(b.mod.type_store.get_int(32), c_const_val)
5625 }
5626 // _wyp: wyhash secret array from wyhash.h. Our wyhash stub uses
5627 // hardcoded constants, so this just needs to be a valid pointer.
5628 if c_name == '_wyp' {
5629 i64_t := b.mod.type_store.get_int(64)
5630 return b.mod.get_or_add_const(i64_t, '0')
5631 }
5632 // macOS errno: (*__error()) — call __error() which returns int*
5633 if c_name == 'errno' {
5634 i32_t := b.mod.type_store.get_int(32)
5635 ptr_i32 := b.mod.type_store.get_ptr(i32_t)
5636 err_fn := b.get_or_create_fn_ref('__error', ptr_i32)
5637 call_val := b.mod.add_instr(.call, b.cur_block, ptr_i32, [err_fn])
5638 return b.mod.add_instr(.load, b.cur_block, i32_t, [call_val])
5639 }
5640 // Map C standard I/O streams to macOS-specific symbol names
5641 macos_name := match c_name {
5642 'stdout' { '__stdoutp' }
5643 'stderr' { '__stderrp' }
5644 'stdin' { '__stdinp' }
5645 else { c_name }
5646 }
5647
5648 // Not a known constant — emit as a global reference (e.g. C.stdout, C.stderr)
5649 i8_t := b.mod.type_store.get_int(8)
5650 ptr_t := b.mod.type_store.get_ptr(i8_t)
5651 glob := b.mod.add_value_node(.global, ptr_t, macos_name, 0)
5652 b.global_refs[macos_name] = glob
5653 return glob
5654 }
5655
5656 // Module-qualified constant/global access: os.args, pref.Backend, etc.
5657 // When LHS is a module name, resolve module__field as a constant or global.
5658 if expr.lhs is ast.Ident {
5659 mod_name := expr.lhs.name.replace('.', '_')
5660 qualified := '${mod_name}__${expr.rhs.name}'
5661 // Try as float constant (inline as f64)
5662 if fval := b.float_const_values[qualified] {
5663 return b.mod.get_or_add_const(b.mod.type_store.get_float(64), fval)
5664 }
5665 // Try as compile-time constant
5666 if qualified in b.const_values {
5667 ct := if qualified in b.const_value_types {
5668 b.const_value_types[qualified]
5669 } else {
5670 b.mod.type_store.get_int(64)
5671 }
5672 return b.mod.get_or_add_const(ct, b.const_values[qualified].str())
5673 }
5674 // Try as string constant
5675 if qualified in b.string_const_values {
5676 return b.build_string_literal(ast.StringLiteral{
5677 kind: .v
5678 value: b.string_const_values[qualified]
5679 })
5680 }
5681 // Try as constant array global (return pointer directly for indexing)
5682 if qualified in b.const_array_globals {
5683 if glob_id := b.find_global(qualified) {
5684 return glob_id
5685 }
5686 }
5687 // Try as global variable (runtime-initialized constants like os.args)
5688 if glob_id := b.find_global(qualified) {
5689 glob_typ := b.mod.values[glob_id].typ
5690 elem_typ := b.mod.type_store.types[glob_typ].elem_type
5691 return b.mod.add_instr(.load, b.cur_block, elem_typ, [glob_id])
5692 }
5693 }
5694
5695 // Check for const array global .len access — return compile-time element count
5696 if expr.rhs.name == 'len' && expr.lhs is ast.Ident {
5697 lhs_name := expr.lhs.name
5698 if count := b.const_array_elem_count[lhs_name] {
5699 return b.mod.get_or_add_const(b.mod.type_store.get_int(64), count.str())
5700 }
5701 qualified := '${b.cur_module}__${lhs_name}'
5702 if count := b.const_array_elem_count[qualified] {
5703 return b.mod.get_or_add_const(b.mod.type_store.get_int(64), count.str())
5704 }
5705 builtin_name := 'builtin__${lhs_name}'
5706 if count := b.const_array_elem_count[builtin_name] {
5707 return b.mod.get_or_add_const(b.mod.type_store.get_int(64), count.str())
5708 }
5709 }
5710
5711 // Use extractvalue for struct field access
5712 mut base := b.build_expr(expr.lhs)
5713 // Check for fixed-size array .len access — return compile-time constant
5714 base_typ_raw := b.mod.values[base].typ
5715 if base_typ_raw < b.mod.type_store.types.len {
5716 mut check_typ := b.mod.type_store.types[base_typ_raw]
5717 // Also handle ptr(array_t) — e.g. when fixed array is behind a pointer
5718 if check_typ.kind == .ptr_t && check_typ.elem_type < b.mod.type_store.types.len {
5719 check_typ = b.mod.type_store.types[check_typ.elem_type]
5720 }
5721 if check_typ.kind == .array_t && expr.rhs.name == 'len' {
5722 return b.mod.get_or_add_const(b.mod.type_store.get_int(64), check_typ.len.str())
5723 }
5724 }
5725 // If base is a pointer to struct (mut param or heap alloc), auto-deref
5726 base_typ := b.mod.values[base].typ
5727 if base_typ < b.mod.type_store.types.len && b.mod.type_store.types[base_typ].kind == .ptr_t {
5728 pointee := b.mod.type_store.types[base_typ].elem_type
5729 if pointee < b.mod.type_store.types.len && b.mod.type_store.types[pointee].kind == .struct_t {
5730 base = b.mod.add_instr(.load, b.cur_block, pointee, [base])
5731 }
5732 }
5733 field_idx := b.field_index(expr, base)
5734 // Determine result type: prefer SSA struct field type for struct bases,
5735 // fall back to type environment.
5736 // The type environment may have the smartcast variant type (e.g., int) for a
5737 // sumtype field access, while the SSA struct has the correct field type.
5738 mut result_type := TypeID(0)
5739 actual_base_type := b.mod.values[base].typ
5740 if actual_base_type < b.mod.type_store.types.len {
5741 typ := b.mod.type_store.types[actual_base_type]
5742 if typ.kind == .struct_t && field_idx < typ.fields.len {
5743 result_type = typ.fields[field_idx]
5744 }
5745 }
5746 if result_type == 0 {
5747 result_type = b.expr_type(ast.Expr(expr))
5748 }
5749 return b.mod.add_instr(.extractvalue, b.cur_block, result_type, [base,
5750 b.mod.get_or_add_const(b.mod.type_store.get_int(32), field_idx.str())])
5751}
5752
5753fn (mut b Builder) field_index(expr ast.SelectorExpr, base ValueID) int {
5754 rhs_name := expr.rhs.name
5755 // Use type environment to find field index
5756 if b.env != unsafe { nil } {
5757 pos := expr.lhs.pos()
5758 if pos.id != 0 {
5759 if typ := b.env.get_expr_type(pos.id) {
5760 st := b.unwrap_to_struct(typ)
5761 if st.name != '' {
5762 for i, f in st.fields {
5763 if f.name == rhs_name {
5764 return i
5765 }
5766 }
5767 }
5768 }
5769 }
5770 }
5771 // Fallback: look up field name in the SSA struct type of the base value
5772 base_type_id := b.mod.values[base].typ
5773 if base_type_id < b.mod.type_store.types.len {
5774 mut ssa_typ := b.mod.type_store.types[base_type_id]
5775 // Dereference pointer(s) to get to the struct type
5776 for ssa_typ.kind == .ptr_t && ssa_typ.elem_type < b.mod.type_store.types.len {
5777 ssa_typ = b.mod.type_store.types[ssa_typ.elem_type]
5778 }
5779 if ssa_typ.kind == .struct_t {
5780 for i, name in ssa_typ.field_names {
5781 if name == rhs_name {
5782 return i
5783 }
5784 }
5785 }
5786 }
5787 // Tuple field access: the transformer generates `_tuple_tN.arg0`, `.arg1`, etc.
5788 // Tuples have no named fields in the SSA type, so parse the index from `argN`.
5789 if rhs_name.starts_with('arg') {
5790 idx_str := rhs_name[3..]
5791 if idx_str.len > 0 && idx_str[0] >= `0` && idx_str[0] <= `9` {
5792 return int(parse_const_int_literal(idx_str))
5793 }
5794 }
5795 return 0
5796}
5797
5798// is_struct_field checks whether a field name exists as a struct field of the
5799// receiver expression's type. Used to distinguish function pointer field calls
5800// (e.g., m.hash_fn(pkey)) from method calls.
5801fn (mut b Builder) is_struct_field(receiver_expr ast.Expr, field_name string) bool {
5802 // Try type environment first
5803 if b.env != unsafe { nil } {
5804 pos := receiver_expr.pos()
5805 if pos.id != 0 {
5806 if typ := b.env.get_expr_type(pos.id) {
5807 st := b.unwrap_to_struct(typ)
5808 if st.name != '' {
5809 for f in st.fields {
5810 if f.name == field_name {
5811 return true
5812 }
5813 }
5814 }
5815 }
5816 }
5817 }
5818 return false
5819}
5820
5821fn (mut b Builder) unwrap_to_struct(t types.Type) types.Struct {
5822 match t {
5823 types.Struct {
5824 return t
5825 }
5826 types.Pointer {
5827 return b.unwrap_to_struct(t.base_type)
5828 }
5829 types.Alias {
5830 return b.unwrap_to_struct(t.base_type)
5831 }
5832 types.Map {
5833 // Map is a builtin struct type. Look up the 'map' struct from
5834 // the type checker's scope to get its fields (hash_fn, key_eq_fn, etc).
5835 if b.env != unsafe { nil } {
5836 if scope := b.env.get_scope('builtin') {
5837 if obj := scope.lookup_parent('map', 0) {
5838 obj_type := obj.typ()
5839 if obj_type is types.Struct {
5840 return obj_type
5841 }
5842 }
5843 }
5844 }
5845 return types.Struct{}
5846 }
5847 else {
5848 return types.Struct{}
5849 }
5850 }
5851}
5852
5853// unwrap_to_array_elem_ssa unwraps Pointer and Alias types to find an Array type,
5854// converts its elem_type to SSA TypeID, and returns it. Returns 0 if not found.
5855fn (mut b Builder) unwrap_to_array_elem_ssa(t types.Type) TypeID {
5856 match t {
5857 types.Array {
5858 return b.type_to_ssa(t.elem_type)
5859 }
5860 types.Pointer {
5861 return b.unwrap_to_array_elem_ssa(t.base_type)
5862 }
5863 types.Alias {
5864 return b.unwrap_to_array_elem_ssa(t.base_type)
5865 }
5866 else {
5867 return 0
5868 }
5869 }
5870}
5871
5872fn (mut b Builder) build_index(expr ast.IndexExpr) ValueID {
5873 mut base_val := b.build_expr(expr.lhs)
5874 index := b.build_expr(expr.expr)
5875 mut result_type := b.expr_type(ast.Expr(expr))
5876
5877 base_type_id := b.mod.values[base_val].typ
5878 array_type := b.get_array_type()
5879
5880 // If base is a pointer to a dynamic array (mut []T param), deref first
5881 if array_type != 0 && base_type_id != array_type {
5882 if base_type_id < b.mod.type_store.types.len {
5883 base_typ := b.mod.type_store.types[base_type_id]
5884 if base_typ.kind == .ptr_t && base_typ.elem_type == array_type {
5885 base_val = b.mod.add_instr(.load, b.cur_block, array_type, [base_val])
5886 }
5887 }
5888 }
5889
5890 // Re-check base type after potential deref
5891 base_type_id2 := b.mod.values[base_val].typ
5892
5893 // Check if base is a dynamic array (array struct) — need to access .data field
5894 if array_type != 0 && base_type_id2 == array_type {
5895 // If result_type is i64 (fallback), try to infer the actual element type
5896 // from the array expression's checker type. This is needed for transformer-
5897 // generated IndexExprs (e.g., for-in-array lowering) that have no position ID.
5898 i64_t := b.mod.type_store.get_int(64)
5899 if result_type == i64_t {
5900 // Also try the index expression's own position for type inference
5901 idx_pos := expr.pos
5902 if b.env != unsafe { nil } && idx_pos.id != 0 {
5903 if idx_typ := b.env.get_expr_type(idx_pos.id) {
5904 inferred2 := b.type_to_ssa(idx_typ)
5905 if inferred2 != 0 && inferred2 != i64_t {
5906 result_type = inferred2
5907 }
5908 }
5909 }
5910 }
5911 if result_type == i64_t {
5912 if b.env != unsafe { nil } {
5913 lhs_pos := expr.lhs.pos()
5914 if lhs_pos.id != 0 {
5915 if arr_typ := b.env.get_expr_type(lhs_pos.id) {
5916 // Unwrap pointer/alias types to get to the array type
5917 inferred := b.unwrap_to_array_elem_ssa(arr_typ)
5918 if inferred != 0 {
5919 result_type = inferred
5920 }
5921 }
5922 }
5923 }
5924 }
5925 // If LHS is an array__slice call (transformer-lowered slice), trace
5926 // back to the original array argument to infer element type.
5927 if result_type == i64_t {
5928 if expr.lhs is ast.CallExpr {
5929 call_lhs := expr.lhs as ast.CallExpr
5930 if call_lhs.lhs is ast.Ident {
5931 call_name := (call_lhs.lhs as ast.Ident).name
5932 if call_name == 'array__slice' && call_lhs.args.len >= 1 {
5933 if b.env != unsafe { nil } {
5934 arr_pos := call_lhs.args[0].pos()
5935 if arr_pos.id != 0 {
5936 if arr_typ := b.env.get_expr_type(arr_pos.id) {
5937 inferred := b.unwrap_to_array_elem_ssa(arr_typ)
5938 if inferred != 0 {
5939 result_type = inferred
5940 }
5941 }
5942 }
5943 }
5944 }
5945 }
5946 }
5947 }
5948 // Fallback: use array element type tracked from function parameter declarations.
5949 // This handles transformer-generated functions (e.g., Array_int_str) where
5950 // the checker has no position info for the generated IndexExpr.
5951 if result_type == i64_t {
5952 if expr.lhs is ast.Ident {
5953 if elem_t := b.array_elem_types[expr.lhs.name] {
5954 result_type = elem_t
5955 }
5956 }
5957 }
5958 // Extract .data field (index 0), cast to element pointer, then GEP
5959 i8_t := b.mod.type_store.get_int(8)
5960 void_ptr := b.mod.type_store.get_ptr(i8_t)
5961 data_ptr := b.mod.add_instr(.extractvalue, b.cur_block, void_ptr, [base_val,
5962 b.mod.get_or_add_const(b.mod.type_store.get_int(32), '0')])
5963 // Cast void* to element*
5964 elem_ptr_type := b.mod.type_store.get_ptr(result_type)
5965 typed_ptr := b.mod.add_instr(.bitcast, b.cur_block, elem_ptr_type, [data_ptr])
5966 // GEP to the element
5967 elem_addr := b.mod.add_instr(.get_element_ptr, b.cur_block, elem_ptr_type, [
5968 typed_ptr,
5969 index,
5970 ])
5971 // Load the element
5972 return b.mod.add_instr(.load, b.cur_block, result_type, [elem_addr])
5973 }
5974
5975 // Check if base is a string struct — index into .str (field 0) data pointer
5976 str_type := b.get_string_type()
5977 if str_type != 0 && base_type_id2 == str_type {
5978 // Extract .str field (field 0) — pointer to u8 data
5979 // Use unsigned u8 type so byte comparisons (>= 0x80) work correctly
5980 u8_t := b.mod.type_store.get_uint(8)
5981 u8_ptr := b.mod.type_store.get_ptr(u8_t)
5982 data_ptr := b.mod.add_instr(.extractvalue, b.cur_block, u8_ptr, [base_val,
5983 b.mod.get_or_add_const(b.mod.type_store.get_int(32), '0')])
5984 // GEP to the byte at index (scale = 1 for u8)
5985 elem_addr := b.mod.add_instr(.get_element_ptr, b.cur_block, u8_ptr, [
5986 data_ptr,
5987 index,
5988 ])
5989 // Load the byte as unsigned u8
5990 return b.mod.add_instr(.load, b.cur_block, u8_t, [elem_addr])
5991 }
5992
5993 // Handle fixed-size array values (from [1,2,3]! load): base is array_t, not ptr_t.
5994 // Trace back through the load instruction to find the original alloca pointer,
5995 // then use the pointer-based GEP+load path.
5996 if base_type_id > 0 && base_type_id < b.mod.type_store.types.len {
5997 base_typ := b.mod.type_store.types[base_type_id]
5998 if base_typ.kind == .array_t && base_typ.elem_type != 0 {
5999 base_value := b.mod.values[base_val]
6000 if base_value.kind == .instruction {
6001 instr := b.mod.instrs[base_value.index]
6002 if instr.op == .load && instr.operands.len > 0 {
6003 // Found the original alloca pointer — use it for GEP+load
6004 alloca_ptr := instr.operands[0]
6005 elem_type := base_typ.elem_type
6006 elem_ptr_type := b.mod.type_store.get_ptr(elem_type)
6007 elem_addr := b.mod.add_instr(.get_element_ptr, b.cur_block, elem_ptr_type, [
6008 alloca_ptr,
6009 index,
6010 ])
6011 return b.mod.add_instr(.load, b.cur_block, elem_type, [elem_addr])
6012 }
6013 if instr.op == .extractvalue && instr.operands.len >= 2 {
6014 // Base is extractvalue(struct_val, field_idx) producing an array_t.
6015 // This happens for union/struct field access like u.b[i] where b is [4]u8.
6016 // The extractvalue produces a VALUE, not a pointer, so GEP can't work on it.
6017 // Fix: trace back to the struct's alloca, GEP to the field, then index.
6018 struct_val := instr.operands[0]
6019 field_idx_val := instr.operands[1]
6020 struct_value := b.mod.values[struct_val]
6021 if struct_value.kind == .instruction {
6022 struct_instr := b.mod.instrs[struct_value.index]
6023 if struct_instr.op == .load && struct_instr.operands.len > 0 {
6024 // struct_val came from load(alloca_ptr)
6025 struct_ptr := struct_instr.operands[0]
6026 // GEP to the array field within the struct
6027 arr_ptr_type := b.mod.type_store.get_ptr(base_type_id)
6028 field_addr := b.mod.add_instr(.get_element_ptr, b.cur_block,
6029 arr_ptr_type, [struct_ptr, field_idx_val])
6030 // GEP to the element within the array
6031 elem_type := base_typ.elem_type
6032 elem_ptr_type := b.mod.type_store.get_ptr(elem_type)
6033 elem_addr := b.mod.add_instr(.get_element_ptr, b.cur_block,
6034 elem_ptr_type, [field_addr, index])
6035 return b.mod.add_instr(.load, b.cur_block, elem_type, [
6036 elem_addr,
6037 ])
6038 }
6039 }
6040 }
6041 }
6042 }
6043 }
6044
6045 // Check if base is a pointer (e.g., from alloca for fixed-size array literal)
6046 // For ptr(T), element type is T — use that instead of expr_type fallback
6047 if base_type_id < b.mod.type_store.types.len {
6048 base_typ := b.mod.type_store.types[base_type_id]
6049 if base_typ.kind == .ptr_t && base_typ.elem_type != 0 {
6050 mut elem_type := base_typ.elem_type
6051 // If pointed-to type is a fixed-size array (array_t), extract its element type.
6052 // ptr(array(i32, 5))[i] should load an i32, not an array(i32, 5).
6053 if elem_type < b.mod.type_store.types.len {
6054 inner_typ := b.mod.type_store.types[elem_type]
6055 if inner_typ.kind == .array_t && inner_typ.elem_type != 0 {
6056 elem_type = inner_typ.elem_type
6057 }
6058 }
6059 // GEP to the element address, then load
6060 elem_ptr_type := b.mod.type_store.get_ptr(elem_type)
6061 elem_addr := b.mod.add_instr(.get_element_ptr, b.cur_block, elem_ptr_type, [
6062 base_val,
6063 index,
6064 ])
6065 return b.mod.add_instr(.load, b.cur_block, elem_type, [elem_addr])
6066 }
6067 }
6068
6069 return b.mod.add_instr(.get_element_ptr, b.cur_block, result_type, [base_val, index])
6070}
6071
6072// infer_if_expr_type tries to infer the result type of a transformer-generated
6073// IfExpr that has no position annotation (so expr_type returns i64 fallback).
6074// Checks both then and else branches for type information.
6075fn (mut b Builder) infer_if_expr_type(node ast.IfExpr, i64_t TypeID) TypeID {
6076 // Try to infer from branch expressions — recursively walk the entire
6077 // if-else-if chain so we check ALL branches (not just the first two).
6078 // This is essential for match expressions lowered to if-else-if chains
6079 // where the type-bearing expression may be in a deeply nested branch.
6080 mut branches := [][]ast.Stmt{cap: 8}
6081 branches << node.stmts
6082 mut cur_else := node.else_expr
6083 for {
6084 if cur_else is ast.IfExpr {
6085 else_if := cur_else as ast.IfExpr
6086 branches << else_if.stmts
6087 cur_else = else_if.else_expr
6088 } else {
6089 break
6090 }
6091 }
6092 for branch_stmts in branches {
6093 if branch_stmts.len == 0 {
6094 continue
6095 }
6096 last := branch_stmts[branch_stmts.len - 1]
6097 if last !is ast.ExprStmt {
6098 continue
6099 }
6100 // Unwrap PrefixExpr (e.g., -f has the same type as f)
6101 mut expr := (last as ast.ExprStmt).expr
6102 for expr is ast.PrefixExpr {
6103 expr = (expr as ast.PrefixExpr).expr
6104 }
6105 if expr is ast.StringLiteral || expr is ast.StringInterLiteral {
6106 str_type := b.get_string_type()
6107 if str_type != 0 {
6108 return str_type
6109 }
6110 } else if expr is ast.Ident {
6111 ident_name := (expr as ast.Ident).name
6112 if alloca_id := b.vars[ident_name] {
6113 alloca_val := b.mod.values[alloca_id]
6114 if alloca_val.typ > 0 && alloca_val.typ < b.mod.type_store.types.len {
6115 alloca_typ := b.mod.type_store.types[alloca_val.typ]
6116 if alloca_typ.kind == .ptr_t && alloca_typ.elem_type > 0
6117 && alloca_typ.elem_type < b.mod.type_store.types.len {
6118 elem := b.mod.type_store.types[alloca_typ.elem_type]
6119 if elem.kind == .struct_t || elem.kind == .float_t {
6120 return alloca_typ.elem_type
6121 }
6122 }
6123 }
6124 }
6125 } else if expr is ast.BasicLiteral {
6126 bl := expr as ast.BasicLiteral
6127 if bl.kind == .number && bl.value.contains('.') {
6128 return b.mod.type_store.get_float(64)
6129 }
6130 } else if expr is ast.InitExpr {
6131 // Sum type init: check the struct type of the InitExpr
6132 inferred := b.expr_type(expr)
6133 if inferred != i64_t && inferred != 0 {
6134 return inferred
6135 }
6136 // Try to find the sum type from the field names (_tag, _data)
6137 init := expr as ast.InitExpr
6138 if init.fields.len >= 2 {
6139 for fi in init.fields {
6140 if fi.name == '_tag' || fi.name == '_data' {
6141 // This is a sum type init — look up the type from the typ expr
6142 // (e.g., Ident{name: 'Expr'} → 'ast__Expr' in struct_types)
6143 type_name := init.typ.name()
6144 if type_name.len > 0 {
6145 if tid := b.struct_types[type_name] {
6146 return tid
6147 }
6148 qualified2 := '${b.cur_module}__${type_name}'
6149 if tid := b.struct_types[qualified2] {
6150 return tid
6151 }
6152 for sname, sid in b.struct_types {
6153 if sname.ends_with('__${type_name}') {
6154 return sid
6155 }
6156 }
6157 }
6158 break
6159 }
6160 }
6161 }
6162 } else if expr is ast.CallExpr {
6163 // Check call return type from registered functions
6164 call_expr := expr as ast.CallExpr
6165 fn_name := b.resolve_call_name(call_expr)
6166 if fn_name in b.fn_index {
6167 fn_idx := b.fn_index[fn_name]
6168 fn_ret := b.mod.funcs[fn_idx].typ
6169 if fn_ret != 0 && fn_ret != i64_t {
6170 return fn_ret
6171 }
6172 }
6173 // Also try expr_type
6174 inferred := b.expr_type(expr)
6175 if inferred != i64_t && inferred != 0 {
6176 return inferred
6177 }
6178 } else {
6179 inferred := b.expr_type(expr)
6180 if inferred != i64_t && inferred != 0 {
6181 return inferred
6182 }
6183 }
6184 }
6185 return i64_t
6186}
6187
6188fn (mut b Builder) build_if_expr(node ast.IfExpr) ValueID {
6189 // If used as expression, returns a value
6190 mut result_type := b.expr_type(ast.Expr(node))
6191 // If result_type is i64 (fallback), try to infer from branch contents.
6192 // This handles match-on-string expressions where the transformer creates
6193 // IfExpr chains without position IDs, so expr_type returns i64 fallback.
6194 i64_t := b.mod.type_store.get_int(64)
6195 if result_type == i64_t {
6196 result_type = b.infer_if_expr_type(node, i64_t)
6197 }
6198
6199 then_block := b.mod.add_block(b.cur_func, 'ifx_then')
6200 merge_block := b.mod.add_block(b.cur_func, 'ifx_merge')
6201 has_else := node.else_expr !is ast.EmptyExpr
6202 else_block := if has_else {
6203 b.mod.add_block(b.cur_func, 'ifx_else')
6204 } else {
6205 merge_block
6206 }
6207
6208 cond := b.build_expr(node.cond)
6209 b.mod.add_instr(.br, b.cur_block, 0,
6210 [cond, b.mod.blocks[then_block].val_id, b.mod.blocks[else_block].val_id])
6211 b.add_edge(b.cur_block, then_block)
6212 b.add_edge(b.cur_block, else_block)
6213
6214 // Then
6215 b.cur_block = then_block
6216 mut then_val := ValueID(0)
6217 if node.stmts.len > 0 {
6218 for i := 0; i < node.stmts.len - 1; i++ {
6219 b.build_stmt(node.stmts[i])
6220 }
6221 last := node.stmts[node.stmts.len - 1]
6222 if last is ast.ExprStmt {
6223 then_val = b.build_expr(last.expr)
6224 } else {
6225 b.build_stmt(last)
6226 }
6227 }
6228 then_end_block := b.cur_block
6229 if !b.block_has_terminator(b.cur_block) {
6230 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[merge_block].val_id])
6231 b.add_edge(b.cur_block, merge_block)
6232 }
6233
6234 // Else
6235 mut else_val := ValueID(0)
6236 mut else_end_block := b.cur_block
6237 if has_else {
6238 b.cur_block = else_block
6239 if node.else_expr is ast.IfExpr {
6240 else_if := node.else_expr as ast.IfExpr
6241 if else_if.cond is ast.EmptyExpr {
6242 // Pure else
6243 if else_if.stmts.len > 0 {
6244 for i := 0; i < else_if.stmts.len - 1; i++ {
6245 b.build_stmt(else_if.stmts[i])
6246 }
6247 last := else_if.stmts[else_if.stmts.len - 1]
6248 if last is ast.ExprStmt {
6249 else_val = b.build_expr(last.expr)
6250 } else {
6251 b.build_stmt(last)
6252 }
6253 }
6254 } else {
6255 else_val = b.build_if_expr(else_if)
6256 }
6257 } else {
6258 else_val = b.build_expr(node.else_expr)
6259 }
6260 else_end_block = b.cur_block
6261 if !b.block_has_terminator(b.cur_block) {
6262 b.mod.add_instr(.jmp, b.cur_block, 0, [b.mod.blocks[merge_block].val_id])
6263 b.add_edge(b.cur_block, merge_block)
6264 }
6265 }
6266
6267 // Merge block: use phi to select result (no alloca/store/load)
6268 b.cur_block = merge_block
6269 // If both branches produce values, use a phi node
6270 if then_val != 0 && else_val != 0 {
6271 return b.mod.add_instr(.phi, merge_block, result_type, [then_val, b.mod.blocks[then_end_block].val_id,
6272 else_val, b.mod.blocks[else_end_block].val_id])
6273 } else if then_val != 0 {
6274 // Only then branch has a value; use zero for the else side
6275 zero := b.mod.get_or_add_const(result_type, '0')
6276 return b.mod.add_instr(.phi, merge_block, result_type, [then_val, b.mod.blocks[then_end_block].val_id,
6277 zero, b.mod.blocks[else_end_block].val_id])
6278 } else if else_val != 0 {
6279 // Only else branch has a value
6280 zero := b.mod.get_or_add_const(result_type, '0')
6281 return b.mod.add_instr(.phi, merge_block, result_type, [zero, b.mod.blocks[then_end_block].val_id,
6282 else_val, b.mod.blocks[else_end_block].val_id])
6283 }
6284 // Neither branch produced a value - return zero constant
6285 return b.mod.get_or_add_const(result_type, '0')
6286}
6287
6288fn (mut b Builder) build_array_init_expr(expr ast.ArrayInitExpr) ValueID {
6289 // If the array init has elements, alloca a fixed-size array on the stack,
6290 // store each element, and return the pointer.
6291 if expr.exprs.len > 0 {
6292 mut elem_vals := []ValueID{cap: expr.exprs.len}
6293 for e in expr.exprs {
6294 elem_vals << b.build_expr(e)
6295 }
6296 mut elem_type := b.mod.type_store.get_int(32) // default int
6297 // Try to get the declared element type from the array type annotation.
6298 // This is important when the value type is narrower than the element type
6299 // (e.g., pushing u8 into []rune where rune is i32).
6300 mut has_declared_type := false
6301 if expr.typ is ast.Type {
6302 if expr.typ is ast.ArrayType {
6303 arr_typ := expr.typ as ast.ArrayType
6304 declared_elem := b.ast_type_to_ssa(arr_typ.elem_type)
6305 if declared_elem > 0 {
6306 elem_type = declared_elem
6307 has_declared_type = true
6308 }
6309 }
6310 }
6311 if !has_declared_type && elem_vals.len > 0 {
6312 elem_type = b.mod.values[elem_vals[0]].typ
6313 }
6314 // Convert element values to match the declared/inferred element type.
6315 // This handles: int→wider int (zext), int→float (sitofp), f64→f32 (trunc).
6316 {
6317 elem_kind := b.mod.type_store.types[elem_type].kind
6318 elem_width := b.mod.type_store.types[elem_type].width
6319 for i, val in elem_vals {
6320 val_type := b.mod.values[val].typ
6321 if val_type == elem_type {
6322 continue
6323 }
6324 val_kind := b.mod.type_store.types[val_type].kind
6325 val_width := b.mod.type_store.types[val_type].width
6326 if val_kind == .int_t && elem_kind == .float_t {
6327 // int → float (e.g., 15 in []f32 → f32(15.0))
6328 elem_vals[i] = b.mod.add_instr(.sitofp, b.cur_block, elem_type, [
6329 val,
6330 ])
6331 } else if val_kind == .float_t && elem_kind == .float_t && val_width > elem_width {
6332 // f64 → f32 narrowing
6333 elem_vals[i] = b.mod.add_instr(.trunc, b.cur_block, elem_type, [
6334 val,
6335 ])
6336 } else if val_kind == .float_t && elem_kind == .float_t && val_width < elem_width {
6337 // f32 → f64 widening
6338 elem_vals[i] = b.mod.add_instr(.zext, b.cur_block, elem_type, [
6339 val,
6340 ])
6341 } else if val_kind == .int_t && elem_kind == .int_t && val_width > 0
6342 && val_width < elem_width {
6343 // int → wider int (e.g., u8 in []rune)
6344 elem_vals[i] = b.mod.add_instr(.zext, b.cur_block, elem_type, [
6345 val,
6346 ])
6347 }
6348 }
6349 }
6350 // Allocate fixed-size array on stack and store each element.
6351 // Use array_t so that GEP uses element-size scaling (not struct-field offsets).
6352 arr_fixed_type := b.mod.type_store.get_array(elem_type, elem_vals.len)
6353 ptr_type := b.mod.type_store.get_ptr(arr_fixed_type)
6354 alloca := b.mod.add_instr(.alloca, b.cur_block, ptr_type, []ValueID{})
6355 // Store each element via GEP + store.
6356 // GEP result type is ptr(elem_type) - a pointer to one element, not the whole array.
6357 elem_ptr_type := b.mod.type_store.get_ptr(elem_type)
6358 i32_t := b.mod.type_store.get_int(32)
6359 for i, val in elem_vals {
6360 idx := b.mod.get_or_add_const(i32_t, i.str())
6361 gep := b.mod.add_instr(.get_element_ptr, b.cur_block, elem_ptr_type, [
6362 alloca,
6363 idx,
6364 ])
6365 b.mod.add_instr(.store, b.cur_block, 0, [val, gep])
6366 }
6367 // For fixed arrays ([1, 2]!), return the array VALUE (not the pointer).
6368 // This ensures variables store actual array data so that:
6369 // 1. &a gives the address of the data (the variable alloca), enabling memcmp
6370 // 2. a == b compares values, not pointer addresses
6371 // The ARM64 backend handles array_t values > 8 bytes via memcpy-style
6372 // load/store, similar to struct_t handling.
6373 // build_index handles indexing into array_t values by tracing back to
6374 // the original alloca pointer.
6375 mut is_fixed := false
6376 if expr.len is ast.PostfixExpr {
6377 postfix := expr.len as ast.PostfixExpr
6378 if postfix.op == .not {
6379 is_fixed = true
6380 }
6381 }
6382 if !is_fixed && expr.typ is ast.Type && expr.typ is ast.ArrayFixedType {
6383 is_fixed = true
6384 }
6385 if is_fixed {
6386 return b.mod.add_instr(.load, b.cur_block, arr_fixed_type, [alloca])
6387 }
6388 return alloca
6389 }
6390
6391 // Check if this is a fixed-size array type (e.g., [5]u8{}).
6392 // These need stack allocation via alloca, not a dynamic array struct.
6393 if expr.typ is ast.Type {
6394 if expr.typ is ast.ArrayFixedType {
6395 fixed_typ := expr.typ as ast.ArrayFixedType
6396 elem_type := b.ast_type_to_ssa(fixed_typ.elem_type)
6397 arr_len := if fixed_typ.len is ast.BasicLiteral {
6398 int(parse_const_int_literal(fixed_typ.len.value))
6399 } else if fixed_typ.len is ast.Ident {
6400 b.resolve_const_int(fixed_typ.len.name)
6401 } else {
6402 0
6403 }
6404 if arr_len > 0 {
6405 arr_fixed_type := b.mod.type_store.get_array(elem_type, arr_len)
6406 ptr_type := b.mod.type_store.get_ptr(arr_fixed_type)
6407 alloca := b.mod.add_instr(.alloca, b.cur_block, ptr_type, []ValueID{})
6408 // For small arrays (<=16 elements), zero-initialize element by element.
6409 // For larger arrays, the codegen will bulk-zero the alloca slot.
6410 if arr_len <= 16 {
6411 zero := b.mod.get_or_add_const(elem_type, '0')
6412 elem_ptr_type := b.mod.type_store.get_ptr(elem_type)
6413 i32_t := b.mod.type_store.get_int(32)
6414 for i in 0 .. arr_len {
6415 idx := b.mod.get_or_add_const(i32_t, i.str())
6416 gep := b.mod.add_instr(.get_element_ptr, b.cur_block, elem_ptr_type, [
6417 alloca,
6418 idx,
6419 ])
6420 b.mod.add_instr(.store, b.cur_block, 0, [zero, gep])
6421 }
6422 }
6423 return alloca
6424 }
6425 }
6426 }
6427
6428 // Empty dynamic array with len/cap - these should have been
6429 // transformed to __new_array_with_default_noscan calls by the transformer.
6430 // Return zero-initialized array struct as fallback.
6431 arr_type := b.get_array_type()
6432 return b.mod.get_or_add_const(arr_type, '0')
6433}
6434
6435fn (mut b Builder) collect_init_expr_values(expr ast.InitExpr) (TypeID, []ValueID) {
6436 // Resolve the struct type
6437 mut struct_type := b.ast_type_to_ssa(expr.typ)
6438 if struct_type == b.mod.type_store.get_int(64) {
6439 env_type := b.expr_type(ast.Expr(expr))
6440 if env_type != b.mod.type_store.get_int(64) {
6441 struct_type = env_type
6442 }
6443 }
6444
6445 // Get the type info for field name lookup
6446 typ_info := b.mod.type_store.types[struct_type]
6447 num_fields := typ_info.field_names.len
6448
6449 if num_fields == 0 {
6450 return struct_type, []ValueID{}
6451 }
6452
6453 // Build field values in declaration order
6454 mut field_vals := []ValueID{cap: num_fields}
6455 mut initialized_fields := map[string]int{} // field name -> index in expr.fields
6456
6457 // Check if this is positional initialization (all field names empty)
6458 mut is_positional := expr.fields.len > 0
6459 for field in expr.fields {
6460 if field.name.len > 0 {
6461 is_positional = false
6462 break
6463 }
6464 }
6465
6466 // Map explicit field inits by name
6467 // Handle sumtype _data._variant fields by mapping to _data
6468 if is_positional {
6469 // Positional init: match by index using struct field names
6470 for fi, field in expr.fields {
6471 if fi < num_fields {
6472 initialized_fields[typ_info.field_names[fi]] = fi
6473 }
6474 _ = field
6475 }
6476 } else {
6477 for fi, field in expr.fields {
6478 fname := if field.name.starts_with('_data.') {
6479 '_data'
6480 } else {
6481 field.name
6482 }
6483 initialized_fields[fname] = fi
6484 }
6485 }