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])