@@ -100,9 +100,19 @@ pub fn (mut g Gen) gen() {
100100 g.macho.data_data << bytes
101101 }
102102 else {
103- // Non-scalar constants are emitted as zero-initialized storage for now.
104- for _ in 0 .. size {
105- g.macho.data_data << 0
103+ // For struct constants (e.g., sum types), emit initial_value as first 8 bytes
104+ // (the tag for sum types), then zeros for the rest.
105+ if gvar.initial_value != 0 && size > = 8 {
106+ mut bytes := []u8 {len: 8 }
107+ binary.little_endian_put_u64 (mut bytes, u64 (gvar.initial_value))
108+ g.macho.data_data << bytes
109+ for _ in 0 .. size - 8 {
110+ g.macho.data_data << 0
111+ }
112+ } else {
113+ for _ in 0 .. size {
114+ g.macho.data_data << 0
115+ }
106116 }
107117 }
108118 }
@@ -980,6 +990,29 @@ fn (mut g Gen) gen_instr(val_id int) {
980990 g.emit_add_fp_imm (8 , data_off)
981991 g.store_reg_to_val (8 , val_id)
982992 }
993+ .heap_alloc {
994+ // Heap-allocate memory for a struct type.
995+ // Result type is ptr(T), compute sizeof(T) and call calloc(1, size).
996+ mut alloc_size := 8
997+ ha_val := g.mod.values[val_id]
998+ if ha_val.typ > 0 && ha_val.typ < g.mod.type_store.types.len {
999+ ptr_typ := g.mod.type_store.types[ha_val.typ]
1000+ if ptr_typ.kind == .ptr_t && ptr_typ.elem_type > 0 {
1001+ alloc_size = g.type_size (ptr_typ.elem_type)
1002+ if alloc_size < = 0 {
1003+ alloc_size = 8
1004+ }
1005+ }
1006+ }
1007+ // calloc(1, size) → x0 = 1, x1 = size
1008+ g.emit_mov_imm (0 , 1 )
1009+ g.emit_mov_imm (1 , u64 (alloc_size))
1010+ sym_idx := g.macho.add_undefined ('_calloc' )
1011+ g.macho.add_reloc (g.macho.text_data.len, sym_idx, arm64_ reloc_branch26 , true )
1012+ g.emit (asm_bl_reloc ())
1013+ // calloc returns heap pointer in x0
1014+ g.store_reg_to_val (0 , val_id)
1015+ }
9831016 .get_element_ptr {
9841017 // GEP: Base + scaled index (or struct field offset for aggregate pointers)
9851018 base_reg := g.get_operand_reg (instr.operands[0 ], 8 )
@@ -1448,6 +1481,12 @@ fn (mut g Gen) gen_instr(val_id int) {
14481481 // Small struct (≤ 16 bytes) - return in registers x0, x1
14491482 actual_struct_typ := if is_indirect_struct_return { fn_ret_typ } else { ret_typ }
14501483
1484+ // Ensure string literals are materialized on the stack
1485+ // before we try to load their fields into return registers.
1486+ if ret_val.kind == .string_literal {
1487+ g.load_val_to_reg (9 , ret_val_id)
1488+ }
1489+
14511490 if is_indirect_struct_return {
14521491 // Return value is a pointer to struct - load each field via the pointer
14531492 g.load_val_to_reg (8 , ret_val_id)
@@ -1636,6 +1675,24 @@ fn (mut g Gen) gen_instr(val_id int) {
16361675 dest_id := instr.operands[0 ]
16371676 src_id := instr.operands[1 ]
16381677 mut handled_aggregate_copy := false
1678+ // Diagnostic: check if assign involves multi-word dest
1679+ if dest_id > 0 && dest_id < g.mod.values.len {
1680+ diag_dt := g.mod.values[dest_id].typ
1681+ if diag_dt > 0 && diag_dt < g.mod.type_store.types.len {
1682+ diag_dsz := g.type_size (diag_dt)
1683+ if diag_dsz > 8 {
1684+ src_kind_str := if src_id > 0 && src_id < g.mod.values.len {
1685+ 'kind=${g .mod .values [src_id ].kind } typ_kind=${g .mod .type_store .types [g .mod .values [src_id ].typ ].kind } name="${g .mod .values [src_id ].name }"'
1686+ } else {
1687+ 'invalid_src'
1688+ }
1689+ dest_kind_str := 'kind=${g .mod .values [dest_id ].kind } typ_kind=${g .mod .type_store .types [diag_dt ].kind } name="${g .mod .values [dest_id ].name }"'
1690+ has_src_stack := src_id in g.stack_map
1691+ has_dest_stack := dest_id in g.stack_map
1692+ eprintln ('DIAG ASSIGN multi: dest[${dest_id }]={${dest_kind_str } sz=${diag_dsz } stk=${has_dest_stack }} src[${src_id }]={${src_kind_str } stk=${has_src_stack }} fn=${g .cur_func_name }' )
1693+ }
1694+ }
1695+ }
16391696 if dest_id > 0 && dest_id < g.mod.values.len {
16401697 dest_typ_id := g.mod.values[dest_id].typ
16411698 if dest_typ_id > 0 && dest_typ_id < g.mod.type_store.types.len {
@@ -1685,10 +1742,25 @@ fn (mut g Gen) gen_instr(val_id int) {
16851742 }
16861743 }
16871744 if can_copy {
1688- for i in 0 .. num_chunks {
1745+ // Determine how many chunks the source actually has
1746+ mut src_chunks := num_chunks
1747+ if src_id > 0 && src_id < g.mod.values.len {
1748+ src_sz := g.type_size (g.mod.values[src_id].typ)
1749+ if src_sz > 0 && src_sz < dest_size {
1750+ src_chunks = (src_sz + 7 ) / 8
1751+ }
1752+ }
1753+ for i in 0 .. src_chunks {
16891754 g.emit (asm_ldr_imm (Reg (10 ), Reg (src_ptr_reg), u32 (i)))
16901755 g.emit_str_reg_offset (10 , 29 , dest_off + i * 8 )
16911756 }
1757+ // Zero-fill remaining chunks if source is smaller
1758+ if src_chunks < num_chunks {
1759+ g.emit_mov_reg (10 , 31 ) // xzr
1760+ for i in src_chunks .. num_chunks {
1761+ g.emit_str_reg_offset (10 , 29 , dest_off + i * 8 )
1762+ }
1763+ }
16921764 handled_aggregate_copy = true
16931765 }
16941766 }
@@ -1698,6 +1770,38 @@ fn (mut g Gen) gen_instr(val_id int) {
16981770 if handled_aggregate_copy {
16991771 return
17001772 }
1773+ // For multi-word struct destinations with constant sources (undef/0),
1774+ // zero-fill all chunks instead of storing a single register.
1775+ if dest_id > 0 && dest_id < g.mod.values.len {
1776+ d_typ_id := g.mod.values[dest_id].typ
1777+ if d_typ_id > 0 && d_typ_id < g.mod.type_store.types.len {
1778+ d_sz := g.type_size (d_typ_id)
1779+ if d_sz > 8 {
1780+ is_const_src := src_id > 0 && src_id < g.mod.values.len
1781+ && g.mod.values[src_id].kind == .constant
1782+ if is_const_src {
1783+ if d_off := g.stack_map[dest_id] {
1784+ num_chunks := (d_sz + 7 ) / 8
1785+ g.emit_mov_reg (10 , 31 ) // xzr
1786+ for ci in 0 .. num_chunks {
1787+ g.emit_str_reg_offset (10 , 29 , d_off + ci * 8 )
1788+ }
1789+ return
1790+ }
1791+ }
1792+ }
1793+ }
1794+ }
1795+ // Check if this single-reg fallback is for a multi-word dest
1796+ if dest_id > 0 && dest_id < g.mod.values.len {
1797+ fb_dt := g.mod.values[dest_id].typ
1798+ if fb_dt > 0 && fb_dt < g.mod.type_store.types.len {
1799+ fb_dsz := g.type_size (fb_dt)
1800+ if fb_dsz > 8 {
1801+ eprintln ('WARN ASSIGN single-reg fallback for multi-word dest! dest_sz=${fb_dsz } fn=${g .cur_func_name }' )
1802+ }
1803+ }
1804+ }
17011805 g.load_val_to_reg (8 , src_id)
17021806 g.store_reg_to_val (8 , dest_id)
17031807 }
@@ -1758,6 +1862,8 @@ fn (mut g Gen) gen_instr(val_id int) {
17581862 }
17591863 }
17601864 }
1865+ } else {
1866+ // typ out of range — use default field_byte_off and field_elem_size
17611867 }
17621868
17631869 // If the tuple source is a string_literal (e.g. after mem2reg
@@ -1877,6 +1983,19 @@ fn (mut g Gen) gen_instr(val_id int) {
18771983 struct_size := g.type_size (instr.typ)
18781984 num_chunks := if struct_size > 0 { (struct_size + 7 ) / 8 } else { 1 }
18791985
1986+ // Diagnostic: check sum type struct_init for zero _data
1987+ if struct_typ.field_names.len == 2 && struct_typ.field_names[0 ] == '_tag'
1988+ && struct_typ.field_names[1 ] == '_data' && instr.operands.len > = 2 {
1989+ tag_id := instr.operands[0 ]
1990+ data_id := instr.operands[1 ]
1991+ tag_val := g.mod.values[tag_id]
1992+ data_val := g.mod.values[data_id]
1993+ if tag_val.kind == .constant && tag_val.name != '0' && data_val.kind == .constant
1994+ && data_val.name == '0' {
1995+ eprintln ('DIAG: struct_init sum type with _tag=${tag_val .name } but _data=0 (const zero)! fn=${g .cur_func_name } val_id=${val_id } data_id=${data_id }' )
1996+ }
1997+ }
1998+
18801999 // Zero-initialize the entire struct first
18812000 g.emit_mov_reg (9 , 31 ) // xzr
18822001 for i in 0 .. num_chunks {
@@ -1934,7 +2053,9 @@ fn (mut g Gen) gen_instr(val_id int) {
19342053 g.emit_str_reg_offset (10 , 29 , result_offset + field_off + w * 8 )
19352054 }
19362055 } else {
1937- // Fallback: store first word
2056+ // Fallback: store first word only
2057+ in_reg := field_id in g.reg_map
2058+ eprintln ('WARN: struct_init multi-word field fallback: field_id=${field_id } field_size=${field_size } field_chunks=${field_chunks } in_reg=${in_reg } val_kind=${field_val .kind } val_name="${field_val .name }" fn=${g .cur_func_name }' )
19382059 g.load_val_to_reg (8 , field_id)
19392060 g.emit_str_reg_offset (8 , 29 , result_offset + field_off)
19402061 }
@@ -3073,7 +3194,7 @@ fn (mut g Gen) allocate_registers(func mir.Function) {
30733194 }
30743195
30753196 instr := g.mod.instrs[val.index]
3076- if instr.op in [.call, .call_indirect, .call_sret] {
3197+ if instr.op in [.call, .call_indirect, .call_sret, .heap_alloc ] {
30773198 call_indices << instr_idx
30783199 }
30793200
0 commit comments