vxx2 / vlib / v3 / gen / arm64 / gen.v
1929 lines · 1823 sloc · 52.15 KB · 1f8a06056a77c1daa7ffb9e0a39d1da81d08183e
Raw
1module arm64
2
3import v3.ssa
4
5// Gen stores state for ARM64 code generation.
6pub struct Gen {
7mut:
8 m &ssa.Module = unsafe { nil }
9 macho &MachOObject = unsafe { nil }
10 stack_offsets []int
11 alloca_offsets []int
12 alloca_sizes []int
13 stack_size int
14 block_offsets []int
15 pending_jmps []PendingJmp
16 fn_offsets map[string]int
17 string_cache map[string]int
18 cur_func_ret_type ssa.TypeID
19 cur_func_sret_offset int
20}
21
22// PendingJmp represents pending jmp data used by arm64.
23struct PendingJmp {
24 text_pos int
25 block_id int
26}
27
28// new creates a Gen value for arm64.
29pub fn Gen.new(m &ssa.Module) &Gen {
30 return &Gen{
31 m: m
32 macho: MachOObject.new()
33 stack_offsets: []int{}
34 alloca_offsets: []int{}
35 alloca_sizes: []int{}
36 block_offsets: []int{}
37 pending_jmps: []PendingJmp{}
38 fn_offsets: map[string]int{}
39 string_cache: map[string]int{}
40 }
41}
42
43// gen supports gen handling for Gen.
44pub fn (mut g Gen) gen() {
45 g.gen_pre_pass()
46 for fi in 0 .. g.m.funcs.len {
47 g.gen_func(fi)
48 }
49 g.gen_post_pass()
50}
51
52// write_and_link writes and link output for arm64.
53pub fn (mut g Gen) write_and_link(output string) {
54 mut l := Linker.new(g.macho)
55 l.link(output, '_main')
56}
57
58// reset_value_slots updates reset value slots state for arm64.
59fn (mut g Gen) reset_value_slots() {
60 n := g.m.values.len
61 g.stack_offsets = []int{len: n}
62 g.alloca_offsets = []int{len: n}
63 g.alloca_sizes = []int{len: n}
64}
65
66// set_stack_slot updates set stack slot state for arm64.
67fn (mut g Gen) set_stack_slot(val_id int, off int) {
68 if val_id > 0 && val_id < g.stack_offsets.len {
69 g.stack_offsets[val_id] = off
70 }
71}
72
73// stack_slot supports stack slot handling for Gen.
74fn (g &Gen) stack_slot(val_id int) ?int {
75 if val_id > 0 && val_id < g.stack_offsets.len {
76 off := g.stack_offsets[val_id]
77 if off != 0 {
78 return off
79 }
80 }
81 return none
82}
83
84// set_alloca_slot updates set alloca slot state for arm64.
85fn (mut g Gen) set_alloca_slot(val_id int, off int, size int) {
86 if val_id > 0 && val_id < g.alloca_offsets.len {
87 g.alloca_offsets[val_id] = off
88 g.alloca_sizes[val_id] = size
89 }
90}
91
92// alloca_slot supports alloca slot handling for Gen.
93fn (g &Gen) alloca_slot(val_id int) ?int {
94 if val_id > 0 && val_id < g.alloca_offsets.len {
95 off := g.alloca_offsets[val_id]
96 if off != 0 {
97 return off
98 }
99 }
100 return none
101}
102
103// alloca_byte_size supports alloca byte size handling for Gen.
104fn (g &Gen) alloca_byte_size(val_id int) ?int {
105 if val_id > 0 && val_id < g.alloca_sizes.len {
106 size := g.alloca_sizes[val_id]
107 if size != 0 {
108 return size
109 }
110 }
111 return none
112}
113
114// gen_pre_pass emits pre pass output for arm64.
115fn (mut g Gen) gen_pre_pass() {
116 mut data_offset := u64(0)
117 for gi in 0 .. g.m.globals.len {
118 data_offset = (data_offset + 7) & ~u64(7)
119 g.macho.add_symbol('_' + g.m.globals[gi].name, data_offset, true, 3)
120 size := g.m.type_size(g.m.globals[gi].typ)
121 data_offset += u64(if size > 0 { size } else { 8 })
122 }
123}
124
125// gen_post_pass emits post pass output for arm64.
126fn (mut g Gen) gen_post_pass() {
127 for gi in 0 .. g.m.globals.len {
128 for g.macho.data_data.len % 8 != 0 {
129 g.macho.data_data << 0
130 }
131 size := g.m.type_size(g.m.globals[gi].typ)
132 actual := if size > 0 { size } else { 8 }
133 for _ in 0 .. actual {
134 g.macho.data_data << 0
135 }
136 }
137
138 cstring_base := u64(g.macho.text_data.len)
139 data_base := (cstring_base + u64(g.macho.str_data.len) + 7) & ~u64(7)
140 for i in 0 .. g.macho.symbols.len {
141 if g.macho.symbols[i].sect == 2 {
142 g.macho.symbols[i].value += cstring_base
143 } else if g.macho.symbols[i].sect == 3 {
144 g.macho.symbols[i].value += data_base
145 }
146 }
147}
148
149// gen_func emits func output for arm64.
150fn (mut g Gen) gen_func(func_idx int) {
151 func := g.m.funcs[func_idx]
152 if func.is_c_extern {
153 return
154 }
155 if func.blocks.len == 0 {
156 fn_start := g.macho.text_data.len
157 sym_name := '_' + func.name
158 g.macho.add_symbol(sym_name, u64(fn_start), false, 1)
159 g.emit32(asm_ret())
160 g.fn_offsets[func.name] = fn_start
161 return
162 }
163
164 g.reset_value_slots()
165 g.pending_jmps.clear()
166
167 n_blks := g.m.blocks.len
168 g.block_offsets = []int{len: n_blks, init: -1}
169
170 // Frame layout (all at negative offsets from fp):
171 // fp + 0: saved fp
172 // fp + 8: saved lr
173 // fp - 8: first local slot
174 // fp - 16: second local slot ...
175 mut slot_offset := 0
176 g.cur_func_ret_type = func.typ
177 g.cur_func_sret_offset = 0
178 if g.is_large_struct_type(func.typ) {
179 slot_offset += 8
180 g.cur_func_sret_offset = -slot_offset
181 }
182
183 for _, pid in func.params {
184 param_val := g.m.values[pid]
185 param_size := g.m.type_size(param_val.typ)
186 alloc_size := if param_size > 8 { (param_size + 7) & ~7 } else { 8 }
187 slot_offset += alloc_size
188 g.set_stack_slot(pid, -slot_offset)
189 }
190
191 for blk_id in func.blocks {
192 blk := g.m.blocks[blk_id]
193 for val_id in blk.instrs {
194 val := g.m.values[val_id]
195 if val.kind != .instruction {
196 continue
197 }
198 instr := g.m.instrs[val.index]
199 if instr.op == .alloca {
200 ptr_type := g.m.type_store.types[val.typ]
201 elem_size := g.m.type_size(ptr_type.elem_type)
202 mut count := 1
203 if instr.operands.len > 0 {
204 count_val := g.m.values[instr.operands[0]]
205 if count_val.kind == .constant {
206 n := parse_arm64_int(count_val.name)
207 if n > 1 {
208 count = int(n)
209 } else {
210 count = 1
211 }
212 } else {
213 count = 1
214 }
215 }
216 alloc_size := if elem_size > 0 { (elem_size * count + 7) & ~7 } else { 8 }
217 slot_offset = (slot_offset + 15) & ~0xF
218 slot_offset += alloc_size
219 g.set_alloca_slot(val_id, -slot_offset, alloc_size)
220 slot_offset += 8
221 } else if instr.op != .store && instr.op != .ret && instr.op != .br && instr.op != .jmp
222 && instr.op != .unreachable {
223 result_size := g.m.type_size(val.typ)
224 alloc_size := if result_size > 8 && val.typ > 0
225 && val.typ < g.m.type_store.types.len
226 && g.m.type_store.types[val.typ].kind == .struct_t {
227 (result_size + 7) & ~7
228 } else {
229 8
230 }
231 slot_offset += alloc_size
232 g.set_stack_slot(val_id, -slot_offset)
233 }
234 }
235 }
236
237 g.stack_size = (slot_offset + 15) & ~0xF
238
239 fn_start := g.macho.text_data.len
240 sym_name := '_' + func.name
241 g.macho.add_symbol(sym_name, u64(fn_start), false, 1)
242 g.fn_offsets[func.name] = fn_start
243
244 // Prologue: stp fp, lr, [sp, -16]! ; mov fp, sp ; sub sp, sp, #frame
245 g.emit32(asm_stp_fp_lr_pre())
246 g.emit32(asm_mov_fp_sp())
247 if g.stack_size > 0 {
248 g.emit_sub_sp(g.stack_size)
249 }
250 if g.cur_func_sret_offset != 0 {
251 g.emit_store_fp(8, g.cur_func_sret_offset)
252 }
253
254 if func.name == 'main' {
255 g.store_entry_arg_to_global(0, 'g_main_argc')
256 g.store_entry_arg_to_global(1, 'g_main_argv')
257 }
258
259 // Spill params from registers to stack. The AArch64 PCS allocates integer
260 // (x0-x7) and float (d0-d7) argument registers from independent counters.
261 mut reg_idx := 0
262 mut float_reg_idx := 0
263 mut stack_arg_off := 16
264 for _, pid in func.params {
265 param_val := g.m.values[pid]
266 param_size := g.m.type_size(param_val.typ)
267 if g.is_float_type(param_val.typ) {
268 off := g.stack_slot(pid) or { 0 }
269 if float_reg_idx < 8 {
270 // Float arg arrives in dN; move the bit pattern to x8 and spill.
271 g.emit32(asm_fmov_x_d(Reg(8), float_reg_idx))
272 g.emit_store_fp(8, off)
273 } else {
274 g.emit_load_fp(8, stack_arg_off)
275 g.emit_store_fp(8, off)
276 stack_arg_off += 8
277 }
278 float_reg_idx++
279 continue
280 }
281 if g.is_large_struct_type(param_val.typ) {
282 off := g.stack_slot(pid) or { 0 }
283 if reg_idx < 8 {
284 g.emit_copy_ptr_to_fp(reg_idx, off, param_size)
285 reg_idx++
286 } else {
287 g.emit_load_fp(8, stack_arg_off)
288 g.emit_copy_ptr_to_fp(8, off, param_size)
289 stack_arg_off += 8
290 }
291 continue
292 }
293 n_words := if param_size > 8 { (param_size + 7) / 8 } else { 1 }
294 off := g.stack_slot(pid) or { 0 }
295 if reg_idx + n_words <= 8 {
296 for wi in 0 .. n_words {
297 g.emit_store_fp(reg_idx, off + wi * 8)
298 reg_idx++
299 }
300 } else {
301 for wi in 0 .. n_words {
302 g.emit_load_fp(8, stack_arg_off + wi * 8)
303 g.emit_store_fp(8, off + wi * 8)
304 }
305 stack_arg_off += n_words * 8
306 }
307 }
308
309 // Generate blocks
310 for blk_id in func.blocks {
311 g.block_offsets[blk_id] = g.macho.text_data.len
312 g.resolve_pending_jmps(blk_id)
313 blk := g.m.blocks[blk_id]
314 for val_id in blk.instrs {
315 g.gen_instr(val_id)
316 }
317 }
318
319 g.resolve_all_pending()
320}
321
322// is_large_struct_type reports whether is large struct type applies in arm64.
323fn (g &Gen) is_large_struct_type(typ_id ssa.TypeID) bool {
324 if typ_id <= 0 || typ_id >= g.m.type_store.types.len {
325 return false
326 }
327 typ := g.m.type_store.types[typ_id]
328 return typ.kind == .struct_t && g.m.type_size(typ_id) > 16
329}
330
331// is_aggregate_type reports whether is aggregate type applies in arm64.
332fn (g &Gen) is_aggregate_type(typ_id ssa.TypeID) bool {
333 if typ_id <= 0 || typ_id >= g.m.type_store.types.len {
334 return false
335 }
336 typ := g.m.type_store.types[typ_id]
337 return typ.kind == .struct_t && g.m.type_size(typ_id) > 8
338}
339
340// is_zero_const reports whether is zero const applies in arm64.
341fn (g &Gen) is_zero_const(val_id int) bool {
342 if val_id <= 0 || val_id >= g.m.values.len {
343 return false
344 }
345 val := g.m.values[val_id]
346 return val.kind == .constant && parse_arm64_int(val.name) == 0
347}
348
349// emit_zero_aggregate emits emit zero aggregate output for arm64.
350fn (mut g Gen) emit_zero_aggregate(ptr_reg int, typ_id ssa.TypeID, max_size int) {
351 mut size := g.m.type_size(typ_id)
352 if max_size > 0 && max_size < size {
353 size = max_size
354 }
355 n_words := (size + 7) / 8
356 for wi in 0 .. n_words {
357 g.emit32(asm_str_imm(xzr, Reg(ptr_reg), u32(wi)))
358 }
359}
360
361// aggregate_store_size supports aggregate store size handling for Gen.
362fn (g &Gen) aggregate_store_size(ptr_id int, typ_id ssa.TypeID) int {
363 mut size := g.m.type_size(typ_id)
364 if slot_size := g.stack_slot_size(ptr_id) {
365 if slot_size > 0 && slot_size < size {
366 size = slot_size
367 }
368 return size
369 }
370 if remaining := g.stack_alloca_remaining(ptr_id) {
371 if remaining > 0 && remaining < size {
372 size = remaining
373 }
374 }
375 return size
376}
377
378// aggregate_load_size supports aggregate load size handling for Gen.
379fn (g &Gen) aggregate_load_size(ptr_id int, typ_id ssa.TypeID) int {
380 return g.aggregate_store_size(ptr_id, typ_id)
381}
382
383// stack_slot_size supports stack slot size handling for Gen.
384fn (g &Gen) stack_slot_size(ptr_id int) ?int {
385 mut cur := ptr_id
386 mut slot_size := 0
387 for _ in 0 .. 8 {
388 if cur <= 0 || cur >= g.m.values.len {
389 return none
390 }
391 val := g.m.values[cur]
392 if val.kind != .instruction {
393 return none
394 }
395 instr := g.m.instrs[val.index]
396 match instr.op {
397 .alloca {
398 if slot_size > 0 {
399 return slot_size
400 }
401 return g.alloca_byte_size(cur)
402 }
403 .get_element_ptr {
404 if instr.operands.len < 2 {
405 return none
406 }
407 if slot_size == 0 {
408 base_id := int(instr.operands[0])
409 base_type := g.ptr_elem_type(base_id)
410 if base_type > 0 && base_type < g.m.type_store.types.len {
411 base := g.m.type_store.types[base_type]
412 if base.kind == .struct_t {
413 off_id := instr.operands[1]
414 if off_id > 0 && off_id < g.m.values.len {
415 off_val := g.m.values[off_id]
416 if off_val.kind == .constant {
417 field_off := int(parse_arm64_int(off_val.name))
418 for fi in 0 .. base.fields.len {
419 if g.m.struct_field_offset(base_type, fi) == field_off {
420 field_size := g.m.struct_field_size(base_type, fi)
421 if field_size > 0 {
422 slot_size = field_size
423 }
424 break
425 }
426 }
427 }
428 }
429 }
430 }
431 }
432 cur = int(instr.operands[0])
433 }
434 .bitcast {
435 if instr.operands.len == 0 {
436 return none
437 }
438 cur = int(instr.operands[0])
439 }
440 else {
441 return none
442 }
443 }
444 }
445 return none
446}
447
448// stack_alloca_remaining supports stack alloca remaining handling for Gen.
449fn (g &Gen) stack_alloca_remaining(ptr_id int) ?int {
450 mut cur := ptr_id
451 mut total_offset := 0
452 for _ in 0 .. 8 {
453 if cur <= 0 || cur >= g.m.values.len {
454 return none
455 }
456 val := g.m.values[cur]
457 if val.kind != .instruction {
458 return none
459 }
460 instr := g.m.instrs[val.index]
461 match instr.op {
462 .alloca {
463 size := g.alloca_byte_size(cur) or { return none }
464 remaining := size - total_offset
465 if remaining > 0 {
466 return remaining
467 }
468 return none
469 }
470 .get_element_ptr {
471 if instr.operands.len < 2 {
472 return none
473 }
474 off_id := instr.operands[1]
475 if off_id > 0 && off_id < g.m.values.len {
476 off_val := g.m.values[off_id]
477 if off_val.kind == .constant {
478 total_offset += int(parse_arm64_int(off_val.name))
479 }
480 }
481 cur = int(instr.operands[0])
482 }
483 .bitcast {
484 if instr.operands.len == 0 {
485 return none
486 }
487 cur = int(instr.operands[0])
488 }
489 else {
490 return none
491 }
492 }
493 }
494 return none
495}
496
497// gen_instr emits instr output for arm64.
498fn (mut g Gen) gen_instr(val_id int) {
499 if val_id <= 0 || val_id >= g.m.values.len {
500 return
501 }
502 val := g.m.values[val_id]
503 if val.kind != .instruction {
504 return
505 }
506 instr := g.m.instrs[val.index]
507
508 match instr.op {
509 .alloca {
510 off := g.alloca_slot(val_id) or { return }
511 g.emit_lea_fp(8, off)
512 g.store_val(8, val_id)
513 }
514 .store {
515 if instr.operands.len < 2 {
516 return
517 }
518 src_id := instr.operands[0]
519 ptr_id := instr.operands[1]
520
521 src_val := g.m.values[src_id]
522 if src_val.kind == .string_literal {
523 g.materialize_string(src_id, 8)
524 ptr_reg := g.load_val(ptr_id, 9)
525 g.emit32(asm_str(Reg(8), Reg(ptr_reg)))
526 g.emit32(asm_str_imm(Reg(10), Reg(ptr_reg), 1))
527 } else {
528 src_size := g.m.type_size(src_val.typ)
529 if src_size > 8 && src_val.typ > 0 && src_val.typ < g.m.type_store.types.len
530 && g.m.type_store.types[src_val.typ].kind == .struct_t {
531 if src_off := g.stack_slot(src_id) {
532 ptr_reg := g.load_val(ptr_id, 9)
533 copy_size := g.aggregate_store_size(ptr_id, src_val.typ)
534 n_words := (copy_size + 7) / 8
535 for wi in 0 .. n_words {
536 g.emit_load_fp(8, src_off + wi * 8)
537 g.emit32(asm_str_imm(Reg(8), Reg(ptr_reg), u32(wi)))
538 }
539 } else {
540 src_reg := g.load_val(src_id, 8)
541 ptr_reg := g.load_val(ptr_id, 9)
542 g.emit32(asm_str(Reg(src_reg), Reg(ptr_reg)))
543 }
544 } else {
545 ptr_reg := g.load_val(ptr_id, 9)
546 dest_type := g.ptr_elem_type(ptr_id)
547 if g.is_zero_const(src_id) && g.is_aggregate_type(dest_type) {
548 g.emit_zero_aggregate(ptr_reg, dest_type, g.aggregate_store_size(ptr_id,
549 dest_type))
550 return
551 }
552 src_reg := g.load_val(src_id, 8)
553 store_typ := if int(dest_type) > 0 { dest_type } else { src_val.typ }
554 g.emit_store_typed(src_reg, ptr_reg, store_typ)
555 }
556 }
557 }
558 .load {
559 if instr.operands.len < 1 {
560 return
561 }
562 ptr_id := instr.operands[0]
563 ptr_val := g.m.values[ptr_id]
564
565 if ptr_val.kind == .global {
566 g.emit_global_addr(8, ptr_val.name)
567 g.emit32(asm_ldr(Reg(8), Reg(8)))
568 g.store_val(8, val_id)
569 } else if ptr_val.kind == .string_literal {
570 g.materialize_string(ptr_id, 8)
571 g.store_val(8, val_id)
572 if off := g.stack_slot(val_id) {
573 g.emit_store_fp(10, off + 8)
574 }
575 } else {
576 ptr_reg := g.load_val(ptr_id, 9)
577 result_size := g.m.type_size(val.typ)
578 if result_size > 8 && val.typ > 0 && val.typ < g.m.type_store.types.len {
579 typ := g.m.type_store.types[val.typ]
580 if typ.kind == .struct_t {
581 if off := g.stack_slot(val_id) {
582 if g.is_string_struct_type(val.typ) {
583 copy_size := g.aggregate_load_size(ptr_id, val.typ)
584 if copy_size > 0 {
585 g.emit32(asm_ldr(Reg(8), Reg(ptr_reg)))
586 g.emit_store_fp(8, off)
587 } else {
588 g.emit_mov_imm(8, 0)
589 g.emit_store_fp(8, off)
590 }
591 if copy_size > 8 {
592 g.emit32(asm_ldr_imm(Reg(10), Reg(ptr_reg), 1))
593 g.emit_store_fp(10, off + 8)
594 } else {
595 g.emit_mov_imm(10, 0)
596 g.emit_store_fp(10, off + 8)
597 }
598 } else {
599 copy_size := g.aggregate_load_size(ptr_id, val.typ)
600 copy_words := (copy_size + 7) / 8
601 total_words := (result_size + 7) / 8
602 for wi in 0 .. copy_words {
603 g.emit32(asm_ldr_imm(Reg(8), Reg(ptr_reg), u32(wi)))
604 g.emit_store_fp(8, off + wi * 8)
605 }
606 if copy_words < total_words {
607 g.emit_mov_imm(8, 0)
608 for wi in copy_words .. total_words {
609 g.emit_store_fp(8, off + wi * 8)
610 }
611 }
612 }
613 }
614 return
615 }
616 }
617 g.emit_load_typed(8, ptr_reg, val.typ)
618 g.store_val(8, val_id)
619 }
620 }
621 .get_element_ptr {
622 if instr.operands.len < 2 {
623 return
624 }
625 base_reg := g.load_val(instr.operands[0], 8)
626 off_reg := g.load_val(instr.operands[1], 9)
627 g.emit32(asm_add_reg(Reg(8), Reg(base_reg), Reg(off_reg)))
628 g.store_val(8, val_id)
629 }
630 .add, .sub, .mul, .sdiv, .srem, .udiv, .urem, .and_, .or_, .xor, .shl, .ashr, .lshr {
631 // Float-typed arithmetic reaches here only via the builder's unary
632 // minus lowering (`0 - x`), which emits an integer `.sub` over a
633 // float value. Redirect any float-typed arithmetic to the FP path so
634 // the bit pattern is negated/operated on as a real IEEE-754 value.
635 if g.is_float_type(val.typ)
636 && instr.op in [.add, .sub, .mul, .sdiv, .udiv, .srem, .urem] {
637 fop := match instr.op {
638 .add { ssa.OpCode.fadd }
639 .sub { ssa.OpCode.fsub }
640 .mul { ssa.OpCode.fmul }
641 .sdiv, .udiv { ssa.OpCode.fdiv }
642 else { ssa.OpCode.frem }
643 }
644
645 g.gen_float_binop(fop, instr.operands[0], instr.operands[1], val_id)
646 return
647 }
648 lhs_reg := g.load_val(instr.operands[0], 8)
649 rhs_reg := g.load_val(instr.operands[1], 9)
650 match instr.op {
651 .add {
652 g.emit32(asm_add_reg(Reg(8), Reg(lhs_reg), Reg(rhs_reg)))
653 }
654 .sub {
655 g.emit32(asm_sub_reg(Reg(8), Reg(lhs_reg), Reg(rhs_reg)))
656 }
657 .mul {
658 g.emit32(asm_mul(Reg(8), Reg(lhs_reg), Reg(rhs_reg)))
659 }
660 .sdiv {
661 g.emit32(asm_sdiv(Reg(8), Reg(lhs_reg), Reg(rhs_reg)))
662 }
663 .srem {
664 g.emit32(asm_sdiv(Reg(10), Reg(lhs_reg), Reg(rhs_reg)))
665 g.emit32(asm_msub(Reg(8), Reg(10), Reg(rhs_reg), Reg(lhs_reg)))
666 }
667 .udiv {
668 g.emit32(asm_udiv(Reg(8), Reg(lhs_reg), Reg(rhs_reg)))
669 }
670 .urem {
671 g.emit32(asm_udiv(Reg(10), Reg(lhs_reg), Reg(rhs_reg)))
672 g.emit32(asm_msub(Reg(8), Reg(10), Reg(rhs_reg), Reg(lhs_reg)))
673 }
674 .and_ {
675 g.emit32(asm_and(Reg(8), Reg(lhs_reg), Reg(rhs_reg)))
676 }
677 .or_ {
678 g.emit32(asm_orr(Reg(8), Reg(lhs_reg), Reg(rhs_reg)))
679 }
680 .xor {
681 g.emit32(asm_eor(Reg(8), Reg(lhs_reg), Reg(rhs_reg)))
682 }
683 .shl {
684 g.emit32(asm_lslv(Reg(8), Reg(lhs_reg), Reg(rhs_reg)))
685 }
686 .ashr {
687 g.emit32(asm_asrv(Reg(8), Reg(lhs_reg), Reg(rhs_reg)))
688 }
689 .lshr {
690 g.emit32(asm_lsrv(Reg(8), Reg(lhs_reg), Reg(rhs_reg)))
691 }
692 else {}
693 }
694
695 g.store_val(8, val_id)
696 }
697 .fadd, .fsub, .fmul, .fdiv, .frem {
698 g.gen_float_binop(instr.op, instr.operands[0], instr.operands[1], val_id)
699 }
700 .fptosi {
701 g.load_float_operand(instr.operands[0], 0)
702 g.emit32(asm_fcvtzs_x_d(Reg(8), 0))
703 g.store_val(8, val_id)
704 }
705 .fptoui {
706 g.load_float_operand(instr.operands[0], 0)
707 g.emit32(asm_fcvtzu_x_d(Reg(8), 0))
708 g.store_val(8, val_id)
709 }
710 .sitofp {
711 src_reg := g.load_val(instr.operands[0], 8)
712 g.emit32(asm_scvtf_d_x(0, Reg(src_reg)))
713 g.store_float_result(val_id)
714 }
715 .uitofp {
716 src_reg := g.load_val(instr.operands[0], 8)
717 g.emit32(asm_ucvtf_d_x(0, Reg(src_reg)))
718 g.store_float_result(val_id)
719 }
720 .eq, .ne, .lt, .gt, .le, .ge, .ult, .ugt, .ule, .uge {
721 lhs_typ := g.m.values[instr.operands[0]].typ
722 if g.is_float_type(lhs_typ) {
723 // Float comparison: FCMP sets NZCV; the same condition codes used
724 // for signed integers give the expected ordered results (matching
725 // the v2 backend).
726 g.load_float_operand(instr.operands[0], 0)
727 g.load_float_operand(instr.operands[1], 1)
728 g.emit32(asm_fcmp_d(Reg(0), Reg(1)))
729 } else {
730 lhs_reg := g.load_val(instr.operands[0], 8)
731 rhs_reg := g.load_val(instr.operands[1], 9)
732 g.emit32(asm_cmp_reg(Reg(lhs_reg), Reg(rhs_reg)))
733 }
734 match instr.op {
735 .eq { g.emit32(asm_cset_eq(Reg(8))) }
736 .ne { g.emit32(asm_cset_ne(Reg(8))) }
737 .lt { g.emit32(asm_cset_lt(Reg(8))) }
738 .gt { g.emit32(asm_cset_gt(Reg(8))) }
739 .le { g.emit32(asm_cset_le(Reg(8))) }
740 .ge { g.emit32(asm_cset_ge(Reg(8))) }
741 .ult { g.emit32(asm_cset_lo(Reg(8))) }
742 .ugt { g.emit32(asm_cset_hi(Reg(8))) }
743 .ule { g.emit32(asm_cset_ls(Reg(8))) }
744 .uge { g.emit32(asm_cset_hs(Reg(8))) }
745 else {}
746 }
747
748 g.store_val(8, val_id)
749 }
750 .neg {
751 src_reg := g.load_val(instr.operands[0], 8)
752 g.emit32(asm_sub_reg(Reg(8), xzr, Reg(src_reg)))
753 g.store_val(8, val_id)
754 }
755 .zext {
756 if instr.operands.len > 0 {
757 src_id := instr.operands[0]
758 src_reg := g.load_val(src_id, 8)
759 src_typ := g.m.values[src_id].typ
760 src_width := if src_typ > 0 && src_typ < g.m.type_store.types.len {
761 g.m.type_store.types[src_typ].width
762 } else {
763 64
764 }
765 if src_width > 0 && src_width < 64 {
766 g.emit32(asm_ubfx_lower(Reg(8), Reg(src_reg), u32(src_width)))
767 } else if src_reg != 8 {
768 g.emit32(asm_mov_reg(Reg(8), Reg(src_reg)))
769 }
770 g.store_val(8, val_id)
771 }
772 }
773 .sext {
774 if instr.operands.len > 0 {
775 src_id := instr.operands[0]
776 src_reg := g.load_val(src_id, 8)
777 src_typ := g.m.values[src_id].typ
778 src_width := if src_typ > 0 && src_typ < g.m.type_store.types.len {
779 g.m.type_store.types[src_typ].width
780 } else {
781 64
782 }
783 match src_width {
784 8 {
785 g.emit32(asm_sxtb(Reg(8), Reg(src_reg)))
786 }
787 16 {
788 g.emit32(asm_sxth(Reg(8), Reg(src_reg)))
789 }
790 32 {
791 g.emit32(asm_sxtw(Reg(8), Reg(src_reg)))
792 }
793 else {
794 if src_reg != 8 {
795 g.emit32(asm_mov_reg(Reg(8), Reg(src_reg)))
796 }
797 }
798 }
799
800 g.store_val(8, val_id)
801 }
802 }
803 .trunc {
804 if instr.operands.len > 0 {
805 src_reg := g.load_val(instr.operands[0], 8)
806 dst_width := if val.typ > 0 && val.typ < g.m.type_store.types.len {
807 g.m.type_store.types[val.typ].width
808 } else {
809 64
810 }
811 if dst_width > 0 && dst_width < 64 {
812 if g.is_signed_int_type(val.typ) {
813 match dst_width {
814 8 {
815 g.emit32(asm_sxtb(Reg(8), Reg(src_reg)))
816 }
817 16 {
818 g.emit32(asm_sxth(Reg(8), Reg(src_reg)))
819 }
820 32 {
821 g.emit32(asm_sxtw(Reg(8), Reg(src_reg)))
822 }
823 else {
824 g.emit32(asm_ubfx_lower(Reg(8), Reg(src_reg), u32(dst_width)))
825 }
826 }
827 } else {
828 g.emit32(asm_ubfx_lower(Reg(8), Reg(src_reg), u32(dst_width)))
829 }
830 } else if src_reg != 8 {
831 g.emit32(asm_mov_reg(Reg(8), Reg(src_reg)))
832 }
833 g.store_val(8, val_id)
834 }
835 }
836 .bitcast {
837 if instr.operands.len > 0 {
838 src_id := instr.operands[0]
839 src_typ := g.m.values[src_id].typ
840 // The builder lowers f32<->f64 casts as a bitcast. Those need a
841 // real representation conversion, not a bit copy: widen the source
842 // into a `d` register and narrow back per the result type.
843 if g.is_float_type(src_typ) && g.is_float_type(val.typ)
844 && g.is_f32_type(src_typ) != g.is_f32_type(val.typ) {
845 g.load_float_operand(src_id, 0)
846 g.store_float_result(val_id)
847 return
848 }
849 src_reg := g.load_val(src_id, 8)
850 if src_reg != 8 {
851 g.emit32(asm_mov_reg(Reg(8), Reg(src_reg)))
852 }
853 g.store_val(8, val_id)
854 }
855 }
856 .call {
857 g.gen_call(val_id, instr)
858 }
859 .call_indirect {
860 g.gen_call(val_id, instr)
861 }
862 .ret {
863 if g.cur_func_sret_offset != 0 {
864 ret_size := g.m.type_size(g.cur_func_ret_type)
865 n_words := (ret_size + 7) / 8
866 g.emit_load_fp(9, g.cur_func_sret_offset)
867 if instr.operands.len > 0 && instr.operands[0] > 0 {
868 ret_id := instr.operands[0]
869 if off := g.stack_slot(ret_id) {
870 for wi in 0 .. n_words {
871 g.emit_load_fp(8, off + wi * 8)
872 g.emit32(asm_str_imm(Reg(8), Reg(9), u32(wi)))
873 }
874 } else {
875 g.emit_mov_imm(8, 0)
876 for wi in 0 .. n_words {
877 g.emit32(asm_str_imm(Reg(8), Reg(9), u32(wi)))
878 }
879 }
880 } else {
881 g.emit_mov_imm(8, 0)
882 for wi in 0 .. n_words {
883 g.emit32(asm_str_imm(Reg(8), Reg(9), u32(wi)))
884 }
885 }
886 if g.stack_size > 0 {
887 g.emit_add_sp(g.stack_size)
888 }
889 g.emit32(asm_ldp_fp_lr_post())
890 g.emit32(asm_ret())
891 return
892 }
893 // Float returns go in d0 (s0 for f32). Load the return value as a
894 // double — load_float_operand widens an f32 source automatically, so
895 // an implicit f32->f64 widening on `return` is handled here — then
896 // narrow to s0 when the function itself returns f32.
897 if g.is_float_type(g.cur_func_ret_type) && instr.operands.len > 0
898 && instr.operands[0] > 0 {
899 g.load_float_operand(instr.operands[0], 0)
900 if g.is_f32_type(g.cur_func_ret_type) {
901 g.emit32(asm_fcvt_s_d(0, 0))
902 }
903 if g.stack_size > 0 {
904 g.emit_add_sp(g.stack_size)
905 }
906 g.emit32(asm_ldp_fp_lr_post())
907 g.emit32(asm_ret())
908 return
909 }
910 if instr.operands.len > 0 && instr.operands[0] > 0 {
911 ret_id := instr.operands[0]
912 ret_val := g.m.values[ret_id]
913 if ret_val.kind == .string_literal {
914 g.materialize_string(ret_id, 0)
915 g.emit32(asm_mov_reg(Reg(1), Reg(10)))
916 } else {
917 ret_size := g.m.type_size(ret_val.typ)
918 if ret_size > 8 && ret_val.typ > 0 && ret_val.typ < g.m.type_store.types.len
919 && g.m.type_store.types[ret_val.typ].kind == .struct_t {
920 if off := g.stack_slot(ret_id) {
921 if g.is_string_struct_type(ret_val.typ) {
922 g.emit_load_string_regs_from_fp(off, 0, 1, ret_val.typ)
923 } else {
924 n_words := (ret_size + 7) / 8
925 for wi in 0 .. n_words {
926 if wi < 8 {
927 g.emit_load_fp(wi, off + wi * 8)
928 }
929 }
930 }
931 }
932 } else {
933 src_reg := g.load_val(ret_id, 0)
934 if src_reg != 0 {
935 g.emit32(asm_mov_reg(Reg(0), Reg(src_reg)))
936 }
937 }
938 }
939 } else {
940 g.emit_mov_imm(0, 0)
941 }
942 // Epilogue
943 if g.stack_size > 0 {
944 g.emit_add_sp(g.stack_size)
945 }
946 g.emit32(asm_ldp_fp_lr_post())
947 g.emit32(asm_ret())
948 }
949 .br {
950 if instr.operands.len < 3 {
951 return
952 }
953 cond_reg := g.load_val(instr.operands[0], 8)
954 then_blk := int(instr.operands[1])
955 else_blk := int(instr.operands[2])
956
957 g.emit32(asm_cbnz(Reg(cond_reg), 2))
958 g.emit_branch_to_block(else_blk)
959 g.emit_branch_to_block(then_blk)
960 }
961 .jmp {
962 if instr.operands.len < 1 {
963 return
964 }
965 target_blk := int(instr.operands[0])
966 g.emit_phi_edge_copies(instr.block, target_blk)
967 g.emit_branch_to_block(target_blk)
968 }
969 .unreachable {
970 g.emit32(asm_udf())
971 }
972 .phi {}
973 .assign {
974 // Phi-elimination copy: assign dest, src -> dest_slot = src.
975 if instr.operands.len >= 2 {
976 g.emit_phi_copy_value(instr.operands[1], instr.operands[0])
977 }
978 }
979 .struct_init {}
980 else {}
981 }
982}
983
984// gen_call emits call output for arm64.
985fn (mut g Gen) gen_call(val_id int, instr ssa.Instruction) {
986 if instr.operands.len < 1 {
987 return
988 }
989 fn_ref_id := instr.operands[0]
990 is_indirect := instr.op == .call_indirect
991 fn_ref := g.m.values[fn_ref_id]
992 mut fn_name := ''
993 if !is_indirect {
994 fn_name = fn_ref.name
995 }
996 ret_indirect := g.is_large_struct_type(instr.typ)
997
998 out_stack_size := g.call_stack_arg_size(instr)
999 if out_stack_size > 0 {
1000 g.emit_sub_sp(out_stack_size)
1001 }
1002
1003 mut arg_reg := 0
1004 mut float_reg := 0
1005 mut stack_off := 0
1006 for ai in 1 .. instr.operands.len {
1007 arg_id := instr.operands[ai]
1008 arg_val := g.m.values[arg_id]
1009
1010 if arg_val.kind == .string_literal {
1011 if arg_reg + 2 <= 8 {
1012 g.materialize_string(arg_id, arg_reg)
1013 g.emit32(asm_mov_reg(Reg(arg_reg + 1), Reg(10)))
1014 arg_reg += 2
1015 } else {
1016 g.materialize_string(arg_id, 8)
1017 g.emit_store_sp(8, stack_off)
1018 g.emit_store_sp(10, stack_off + 8)
1019 stack_off += 16
1020 }
1021 } else if g.is_float_type(arg_val.typ) {
1022 // Float args go in d0-d7 (separate from x0-x7). Move the bit pattern
1023 // into an x register then fmov into the float arg register.
1024 if float_reg < 8 {
1025 g.load_float_bits_to_reg(arg_id, 9)
1026 g.emit32(asm_fmov_d_x(float_reg, Reg(9)))
1027 float_reg++
1028 } else {
1029 g.load_float_bits_to_reg(arg_id, 8)
1030 g.emit_store_sp(8, stack_off)
1031 stack_off += 8
1032 }
1033 } else {
1034 arg_type_id := arg_val.typ
1035 arg_size := g.m.type_size(arg_type_id)
1036 if arg_size > 8 && arg_type_id > 0 && arg_type_id < g.m.type_store.types.len {
1037 typ := g.m.type_store.types[arg_type_id]
1038 if typ.kind == .struct_t {
1039 if g.is_large_struct_type(arg_type_id) {
1040 if arg_reg < 8 {
1041 if !g.emit_value_address(arg_id, arg_reg) {
1042 g.emit_mov_imm(arg_reg, 0)
1043 }
1044 arg_reg += 1
1045 } else {
1046 if !g.emit_value_address(arg_id, 8) {
1047 g.emit_mov_imm(8, 0)
1048 }
1049 g.emit_store_sp(8, stack_off)
1050 stack_off += 8
1051 }
1052 continue
1053 }
1054 if g.is_string_struct_type(arg_type_id) {
1055 if arg_reg + 2 <= 8 {
1056 if off := g.stack_slot(arg_id) {
1057 g.emit_load_string_regs_from_fp(off, arg_reg, arg_reg + 1,
1058 arg_type_id)
1059 } else {
1060 src_reg := g.load_val(arg_id, arg_reg)
1061 if src_reg != arg_reg {
1062 g.emit32(asm_mov_reg(Reg(arg_reg), Reg(src_reg)))
1063 }
1064 g.emit_mov_imm(arg_reg + 1, 0)
1065 }
1066 arg_reg += 2
1067 } else {
1068 if off := g.stack_slot(arg_id) {
1069 g.emit_load_string_regs_from_fp(off, 8, 10, arg_type_id)
1070 g.emit_store_sp(8, stack_off)
1071 g.emit_store_sp(10, stack_off + 8)
1072 } else {
1073 src_reg := g.load_val(arg_id, 8)
1074 g.emit_store_sp(src_reg, stack_off)
1075 g.emit_mov_imm(10, 0)
1076 g.emit_store_sp(10, stack_off + 8)
1077 }
1078 stack_off += 16
1079 }
1080 continue
1081 }
1082 n_words := (arg_size + 7) / 8
1083 if arg_reg + n_words <= 8 {
1084 if off := g.stack_slot(arg_id) {
1085 for wi in 0 .. n_words {
1086 g.emit_load_fp(arg_reg + wi, off + wi * 8)
1087 }
1088 } else {
1089 src_reg := g.load_val(arg_id, arg_reg)
1090 if src_reg != arg_reg {
1091 g.emit32(asm_mov_reg(Reg(arg_reg), Reg(src_reg)))
1092 }
1093 }
1094 arg_reg += n_words
1095 } else {
1096 if off := g.stack_slot(arg_id) {
1097 for wi in 0 .. n_words {
1098 g.emit_load_fp(8, off + wi * 8)
1099 g.emit_store_sp(8, stack_off + wi * 8)
1100 }
1101 } else {
1102 src_reg := g.load_val(arg_id, 8)
1103 g.emit_store_sp(src_reg, stack_off)
1104 }
1105 stack_off += n_words * 8
1106 }
1107 continue
1108 }
1109 }
1110
1111 if arg_val.kind == .instruction {
1112 arg_instr := g.m.instrs[arg_val.index]
1113 if arg_instr.op == .alloca {
1114 if alloca_off := g.alloca_slot(arg_id) {
1115 if arg_reg < 8 {
1116 g.emit_lea_fp(arg_reg, alloca_off)
1117 arg_reg += 1
1118 } else {
1119 g.emit_lea_fp(8, alloca_off)
1120 g.emit_store_sp(8, stack_off)
1121 stack_off += 8
1122 }
1123 continue
1124 }
1125 }
1126 }
1127
1128 if arg_reg < 8 {
1129 src_reg := g.load_val(arg_id, arg_reg)
1130 if src_reg != arg_reg {
1131 g.emit32(asm_mov_reg(Reg(arg_reg), Reg(src_reg)))
1132 }
1133 arg_reg += 1
1134 } else {
1135 src_reg := g.load_val(arg_id, 8)
1136 g.emit_store_sp(src_reg, stack_off)
1137 stack_off += 8
1138 }
1139 }
1140 }
1141
1142 if ret_indirect {
1143 if off := g.stack_slot(val_id) {
1144 g.emit_lea_fp(8, off)
1145 }
1146 }
1147
1148 is_c_extern := fn_ref.kind == .func_ref && fn_ref.index >= 0 && fn_ref.index < g.m.funcs.len
1149 && g.m.funcs[fn_ref.index].is_c_extern
1150 if is_indirect {
1151 target_reg := g.load_val(fn_ref_id, 16)
1152 g.emit32(asm_blr(Reg(target_reg)))
1153 } else if !is_c_extern && fn_name in g.fn_offsets {
1154 target := g.fn_offsets[fn_name]
1155 offset := (target - g.macho.text_data.len) / 4
1156 g.emit32(asm_bl(i32(offset)))
1157 } else {
1158 sym_idx := g.macho.add_undefined('_' + fn_name)
1159 g.macho.add_reloc(g.macho.text_data.len, sym_idx, arm64_reloc_branch26, true)
1160 g.emit32(asm_bl(0))
1161 }
1162
1163 if out_stack_size > 0 {
1164 g.emit_add_sp(out_stack_size)
1165 }
1166
1167 if ret_indirect {
1168 return
1169 }
1170
1171 if instr.typ != 0 {
1172 ret_size := g.m.type_size(instr.typ)
1173 if ret_size > 8 && instr.typ > 0 && instr.typ < g.m.type_store.types.len {
1174 typ := g.m.type_store.types[instr.typ]
1175 if typ.kind == .struct_t {
1176 if off := g.stack_slot(val_id) {
1177 if g.is_string_struct_type(instr.typ) {
1178 g.emit_store_fp(0, off)
1179 g.emit_store_fp(1, off + 8)
1180 return
1181 }
1182 n_words := (ret_size + 7) / 8
1183 for wi in 0 .. n_words {
1184 if wi < 8 {
1185 g.emit_store_fp(wi, off + wi * 8)
1186 }
1187 }
1188 }
1189 return
1190 }
1191 }
1192 // Float results are returned in d0: move the bit pattern into x0.
1193 if g.is_float_type(instr.typ) || g.call_returns_float(fn_ref) {
1194 g.emit32(asm_fmov_x_d(Reg(0), 0))
1195 }
1196 g.store_val(0, val_id)
1197 }
1198}
1199
1200// call_returns_float updates call returns float state for Gen.
1201fn (g &Gen) call_returns_float(fn_ref ssa.Value) bool {
1202 if fn_ref.kind == .func_ref && fn_ref.index >= 0 && fn_ref.index < g.m.funcs.len {
1203 return g.is_float_type(g.m.funcs[fn_ref.index].typ)
1204 }
1205 return false
1206}
1207
1208// load_float_bits_to_reg loads the raw IEEE-754 bit pattern of a float value
1209// into integer register `reg` (handling float constants, which `load_val`
1210// cannot parse).
1211fn (mut g Gen) load_float_bits_to_reg(val_id int, reg int) {
1212 if val_id > 0 && val_id < g.m.values.len {
1213 val := g.m.values[val_id]
1214 if val.kind == .constant {
1215 if g.is_f32_type(val.typ) {
1216 f_val := f32(val.name.f64())
1217 bits := *unsafe { &u32(&f_val) }
1218 g.emit_mov_imm(reg, i64(bits))
1219 } else {
1220 f_val := val.name.f64()
1221 bits := *unsafe { &u64(&f_val) }
1222 g.emit_mov_imm(reg, i64(bits))
1223 }
1224 return
1225 }
1226 }
1227 g.load_val(val_id, reg)
1228}
1229
1230// call_stack_arg_size updates call stack arg size state for Gen.
1231fn (g &Gen) call_stack_arg_size(instr ssa.Instruction) int {
1232 mut arg_reg := 0
1233 mut float_reg := 0
1234 mut stack_words := 0
1235 for ai in 1 .. instr.operands.len {
1236 arg_id := instr.operands[ai]
1237 if arg_id <= 0 || arg_id >= g.m.values.len {
1238 continue
1239 }
1240 arg_val := g.m.values[arg_id]
1241 if g.is_float_type(arg_val.typ) {
1242 if float_reg < 8 {
1243 float_reg++
1244 } else {
1245 stack_words += 1
1246 }
1247 continue
1248 }
1249 mut n_words := 1
1250 if arg_val.kind == .string_literal {
1251 n_words = 2
1252 } else {
1253 arg_size := g.m.type_size(arg_val.typ)
1254 if arg_size > 8 && arg_val.typ > 0 && arg_val.typ < g.m.type_store.types.len
1255 && g.m.type_store.types[arg_val.typ].kind == .struct_t {
1256 n_words = if g.is_large_struct_type(arg_val.typ) { 1 } else { (arg_size + 7) / 8 }
1257 }
1258 }
1259 if arg_reg + n_words <= 8 {
1260 arg_reg += n_words
1261 } else {
1262 stack_words += n_words
1263 }
1264 }
1265 if stack_words == 0 {
1266 return 0
1267 }
1268 return (stack_words * 8 + 15) & ~0xF
1269}
1270
1271// emit_value_address emits emit value address output for arm64.
1272fn (mut g Gen) emit_value_address(val_id int, reg int) bool {
1273 if val_id <= 0 || val_id >= g.m.values.len {
1274 return false
1275 }
1276 val := g.m.values[val_id]
1277 match val.kind {
1278 .global {
1279 g.emit_global_addr(reg, val.name)
1280 return true
1281 }
1282 .instruction {
1283 instr := g.m.instrs[val.index]
1284 if instr.op == .alloca {
1285 if off := g.alloca_slot(val_id) {
1286 g.emit_lea_fp(reg, off)
1287 return true
1288 }
1289 }
1290 if off := g.stack_slot(val_id) {
1291 g.emit_lea_fp(reg, off)
1292 return true
1293 }
1294 }
1295 .argument {
1296 if off := g.stack_slot(val_id) {
1297 g.emit_lea_fp(reg, off)
1298 return true
1299 }
1300 }
1301 else {}
1302 }
1303
1304 return false
1305}
1306
1307// emit_copy_ptr_to_fp converts emit copy ptr to fp data for arm64.
1308fn (mut g Gen) emit_copy_ptr_to_fp(src_ptr_reg int, dst_off int, size int) {
1309 n_words := (size + 7) / 8
1310 tmp_reg := if src_ptr_reg == 8 { 10 } else { 8 }
1311 for wi in 0 .. n_words {
1312 g.emit32(asm_ldr_imm(Reg(tmp_reg), Reg(src_ptr_reg), u32(wi)))
1313 g.emit_store_fp(tmp_reg, dst_off + wi * 8)
1314 }
1315}
1316
1317// ==================== Value loading/storing ====================
1318
1319// load_val reads load val input for arm64.
1320fn (mut g Gen) load_val(val_id int, reg int) int {
1321 if val_id <= 0 || val_id >= g.m.values.len {
1322 g.emit_mov_imm(reg, 0)
1323 return reg
1324 }
1325 val := g.m.values[val_id]
1326 match val.kind {
1327 .constant {
1328 // A float constant's register value is its IEEE-754 bit pattern, not
1329 // the integer parse of its textual name.
1330 if g.is_float_type(val.typ) {
1331 g.load_float_bits_to_reg(val_id, reg)
1332 return reg
1333 }
1334 n := parse_arm64_int(val.name)
1335 g.emit_mov_imm(reg, n)
1336 return reg
1337 }
1338 .string_literal {
1339 g.materialize_string(val_id, reg)
1340 return reg
1341 }
1342 .global {
1343 g.emit_global_addr(reg, val.name)
1344 return reg
1345 }
1346 .func_ref {
1347 g.emit_global_addr(reg, val.name)
1348 return reg
1349 }
1350 .instruction {
1351 instr := g.m.instrs[val.index]
1352 if instr.op == .alloca {
1353 if off := g.alloca_slot(val_id) {
1354 g.emit_lea_fp(reg, off)
1355 return reg
1356 }
1357 }
1358 if off := g.stack_slot(val_id) {
1359 g.emit_load_fp(reg, off)
1360 return reg
1361 }
1362 g.emit_mov_imm(reg, 0)
1363 return reg
1364 }
1365 .argument {
1366 if off := g.stack_slot(val_id) {
1367 g.emit_load_fp(reg, off)
1368 return reg
1369 }
1370 g.emit_mov_imm(reg, 0)
1371 return reg
1372 }
1373 else {
1374 g.emit_mov_imm(reg, 0)
1375 return reg
1376 }
1377 }
1378}
1379
1380// store_val supports store val handling for Gen.
1381fn (mut g Gen) store_val(reg int, val_id int) {
1382 if off := g.stack_slot(val_id) {
1383 g.emit_store_fp(reg, off)
1384 }
1385}
1386
1387// ==================== String materialization ====================
1388
1389// materialize_string supports materialize string handling for Gen.
1390fn (mut g Gen) materialize_string(val_id int, reg int) {
1391 val := g.m.values[val_id]
1392 str_content := val.name
1393 str_len := str_content.len
1394
1395 mut str_offset := 0
1396 if cached := g.string_cache[str_content] {
1397 str_offset = cached
1398 } else {
1399 str_offset = g.macho.str_data.len
1400 g.string_cache[str_content] = str_offset
1401 g.macho.str_data << str_content.bytes()
1402 g.macho.str_data << 0
1403 }
1404
1405 str_sym_name := 'L_str_${str_offset}'
1406 str_sym_idx := g.macho.add_symbol(str_sym_name, u64(str_offset), false, 2)
1407
1408 // ADRP + ADD to load address
1409 g.macho.add_reloc(g.macho.text_data.len, str_sym_idx, arm64_reloc_page21, true)
1410 g.emit32(asm_adrp(Reg(reg)))
1411 g.macho.add_reloc(g.macho.text_data.len, str_sym_idx, arm64_reloc_pageoff12, false)
1412 g.emit32(asm_add_pageoff(Reg(reg)))
1413
1414 // x10 holds the string struct's second 8-byte word: `len` in the low 32 bits and
1415 // `is_lit` in the high 32 bits (matching `string{ str, len int, is_lit int }`).
1416 // Every caller stores/moves x10 as that whole word, so set is_lit=1 here: string
1417 // literals point at static data and must NOT be passed to free() (`string.free`
1418 // returns early only when is_lit==1). Without this, freeing a literal (e.g. the
1419 // `mut res := ''` in os.real_path) aborts with a libmalloc "pointer not allocated".
1420 g.emit_mov_imm(10, i64(str_len) | i64(u64(1) << 32))
1421}
1422
1423// ==================== Global access ====================
1424
1425// emit_global_addr emits emit global addr output for arm64.
1426fn (mut g Gen) emit_global_addr(reg int, name string) {
1427 sym_name := '_' + name
1428 mut sym_idx := 0
1429 if existing := g.macho.sym_by_name[sym_name] {
1430 sym_idx = existing
1431 } else {
1432 sym_idx = g.macho.add_undefined(sym_name)
1433 }
1434 g.macho.add_reloc(g.macho.text_data.len, sym_idx, arm64_reloc_page21, true)
1435 g.emit32(asm_adrp(Reg(reg)))
1436 g.macho.add_reloc(g.macho.text_data.len, sym_idx, arm64_reloc_pageoff12, false)
1437 g.emit32(asm_add_pageoff(Reg(reg)))
1438}
1439
1440// find_global_idx_by_name resolves find global idx by name information for arm64.
1441fn (g &Gen) find_global_idx_by_name(name string) int {
1442 for i, global in g.m.globals {
1443 if global.name == name {
1444 return i
1445 }
1446 }
1447 return -1
1448}
1449
1450// store_entry_arg_to_global converts store entry arg to global data for arm64.
1451fn (mut g Gen) store_entry_arg_to_global(reg int, global_name string) {
1452 global_idx := g.find_global_idx_by_name(global_name)
1453 if global_idx < 0 {
1454 return
1455 }
1456 g.emit_global_addr(9, global_name)
1457 g.emit_store_typed(reg, 9, g.m.globals[global_idx].typ)
1458}
1459
1460// ==================== Branch handling ====================
1461
1462// emit_branch_to_block converts emit branch to block data for arm64.
1463fn (mut g Gen) emit_branch_to_block(blk_id int) {
1464 if blk_id >= 0 && blk_id < g.block_offsets.len && g.block_offsets[blk_id] >= 0 {
1465 target := g.block_offsets[blk_id]
1466 offset := (target - g.macho.text_data.len) / 4
1467 g.emit32(asm_b(i32(offset)))
1468 } else {
1469 g.pending_jmps << PendingJmp{
1470 text_pos: g.macho.text_data.len
1471 block_id: blk_id
1472 }
1473 g.emit32(asm_b(0))
1474 }
1475}
1476
1477// emit_phi_edge_copies emits emit phi edge copies output for arm64.
1478fn (mut g Gen) emit_phi_edge_copies(from_blk int, to_blk int) {
1479 if to_blk < 0 || to_blk >= g.m.blocks.len {
1480 return
1481 }
1482 for val_id in g.m.blocks[to_blk].instrs {
1483 if val_id <= 0 || val_id >= g.m.values.len {
1484 continue
1485 }
1486 val := g.m.values[val_id]
1487 if val.kind != .instruction {
1488 continue
1489 }
1490 instr := g.m.instrs[val.index]
1491 if instr.op != .phi {
1492 break
1493 }
1494 for oi := 0; oi + 1 < instr.operands.len; oi += 2 {
1495 if int(instr.operands[oi + 1]) != from_blk {
1496 continue
1497 }
1498 src_id := instr.operands[oi]
1499 g.emit_phi_copy_value(src_id, val_id)
1500 break
1501 }
1502 }
1503}
1504
1505// emit_phi_copy_value emits emit phi copy value output for arm64.
1506fn (mut g Gen) emit_phi_copy_value(src_id int, dst_id int) {
1507 if dst_id <= 0 || dst_id >= g.m.values.len {
1508 return
1509 }
1510 dst := g.m.values[dst_id]
1511 if dst.typ > 0 && dst.typ < g.m.type_store.types.len && g.is_aggregate_type(dst.typ) {
1512 dst_off := g.stack_slot(dst_id) or { return }
1513 size := g.m.type_size(dst.typ)
1514 n_words := (size + 7) / 8
1515 src := g.m.values[src_id]
1516 if src.kind == .string_literal {
1517 g.materialize_string(src_id, 8)
1518 g.emit_store_fp(8, dst_off)
1519 if n_words > 1 {
1520 g.emit_store_fp(10, dst_off + 8)
1521 }
1522 return
1523 }
1524 if src_off := g.stack_slot(src_id) {
1525 for wi in 0 .. n_words {
1526 g.emit_load_fp(8, src_off + wi * 8)
1527 g.emit_store_fp(8, dst_off + wi * 8)
1528 }
1529 return
1530 }
1531 src_reg := g.load_val(src_id, 8)
1532 if src_reg != 8 {
1533 g.emit32(asm_mov_reg(Reg(8), Reg(src_reg)))
1534 }
1535 g.emit_store_fp(8, dst_off)
1536 g.emit_mov_imm(8, 0)
1537 for wi in 1 .. n_words {
1538 g.emit_store_fp(8, dst_off + wi * 8)
1539 }
1540 return
1541 }
1542 src_reg := g.load_val(src_id, 8)
1543 if src_reg != 8 {
1544 g.emit32(asm_mov_reg(Reg(8), Reg(src_reg)))
1545 }
1546 g.store_val(8, dst_id)
1547}
1548
1549// resolve_pending_jmps resolves resolve pending jmps information for arm64.
1550fn (mut g Gen) resolve_pending_jmps(blk_id int) {
1551 target := g.macho.text_data.len
1552 mut remaining := []PendingJmp{}
1553 for pj in g.pending_jmps {
1554 if pj.block_id == blk_id {
1555 offset := (target - pj.text_pos) / 4
1556 g.patch_branch(pj.text_pos, offset)
1557 } else {
1558 remaining << pj
1559 }
1560 }
1561 g.pending_jmps = remaining
1562}
1563
1564// resolve_all_pending resolves resolve all pending information for arm64.
1565fn (mut g Gen) resolve_all_pending() {
1566 for pj in g.pending_jmps {
1567 if pj.block_id >= 0 && pj.block_id < g.block_offsets.len
1568 && g.block_offsets[pj.block_id] >= 0 {
1569 offset := (g.block_offsets[pj.block_id] - pj.text_pos) / 4
1570 g.patch_branch(pj.text_pos, offset)
1571 }
1572 }
1573 g.pending_jmps.clear()
1574}
1575
1576// patch_branch supports patch branch handling for Gen.
1577fn (mut g Gen) patch_branch(text_pos int, offset int) {
1578 existing := read_u32_le(g.macho.text_data, text_pos)
1579 opcode := existing & 0xFC000000
1580 imm26 := u32(offset) & 0x03FFFFFF
1581 patched := opcode | imm26
1582 write_u32_le_at_arr(mut g.macho.text_data, text_pos, patched)
1583}
1584
1585// ==================== Low-level emission helpers ====================
1586
1587// emit32 supports emit32 handling for Gen.
1588fn (mut g Gen) emit32(instr u32) {
1589 g.macho.text_data << u8(instr)
1590 g.macho.text_data << u8(instr >> 8)
1591 g.macho.text_data << u8(instr >> 16)
1592 g.macho.text_data << u8(instr >> 24)
1593}
1594
1595// emit_mov_imm emits emit mov imm output for arm64.
1596fn (mut g Gen) emit_mov_imm(reg int, val i64) {
1597 if val >= 0 && val < 65536 {
1598 g.emit32(asm_movz(Reg(reg), u32(val)))
1599 } else if val >= 0 && val < i64(0xFFFFFFFF) {
1600 lo := u32(val) & 0xFFFF
1601 hi := (u32(val) >> 16) & 0xFFFF
1602 g.emit32(asm_movz(Reg(reg), lo))
1603 if hi != 0 {
1604 g.emit32(asm_movk(Reg(reg), hi, 1))
1605 }
1606 } else if val < 0 && val >= -65536 {
1607 g.emit32(asm_movn(Reg(reg), u32(~val)))
1608 } else {
1609 uval := u64(val)
1610 g.emit32(asm_movz(Reg(reg), u32(uval & 0xFFFF)))
1611 if (uval >> 16) & 0xFFFF != 0 {
1612 g.emit32(asm_movk(Reg(reg), u32((uval >> 16) & 0xFFFF), 1))
1613 }
1614 if (uval >> 32) & 0xFFFF != 0 {
1615 g.emit32(asm_movk(Reg(reg), u32((uval >> 32) & 0xFFFF), 2))
1616 }
1617 if (uval >> 48) & 0xFFFF != 0 {
1618 g.emit32(asm_movk(Reg(reg), u32((uval >> 48) & 0xFFFF), 3))
1619 }
1620 }
1621}
1622
1623// emit_store_fp emits emit store fp output for arm64.
1624fn (mut g Gen) emit_store_fp(reg int, offset int) {
1625 if offset >= -255 && offset < 0 {
1626 g.emit32(asm_stur(Reg(reg), fp, i32(offset)))
1627 } else if offset < -255 {
1628 g.emit_mov_imm(11, i64(offset))
1629 g.emit32(asm_add_reg(Reg(11), fp, Reg(11)))
1630 g.emit32(asm_str(Reg(reg), Reg(11)))
1631 } else {
1632 g.emit32(asm_str_imm(Reg(reg), fp, u32(offset / 8)))
1633 }
1634}
1635
1636// emit_store_sp emits emit store sp output for arm64.
1637fn (mut g Gen) emit_store_sp(reg int, offset int) {
1638 if offset >= 0 && offset < 32768 && offset % 8 == 0 {
1639 g.emit32(asm_str_imm(Reg(reg), sp, u32(offset / 8)))
1640 } else {
1641 g.emit_mov_imm(11, i64(offset))
1642 g.emit32(asm_add_reg(Reg(11), sp, Reg(11)))
1643 g.emit32(asm_str(Reg(reg), Reg(11)))
1644 }
1645}
1646
1647// emit_load_fp emits emit load fp output for arm64.
1648fn (mut g Gen) emit_load_fp(reg int, offset int) {
1649 if offset >= -255 && offset < 0 {
1650 g.emit32(asm_ldur(Reg(reg), fp, i32(offset)))
1651 } else if offset < -255 {
1652 g.emit_mov_imm(11, i64(offset))
1653 g.emit32(asm_add_reg(Reg(11), fp, Reg(11)))
1654 g.emit32(asm_ldr(Reg(reg), Reg(11)))
1655 } else {
1656 g.emit32(asm_ldr_imm(Reg(reg), fp, u32(offset / 8)))
1657 }
1658}
1659
1660// emit_lea_fp emits emit lea fp output for arm64.
1661fn (mut g Gen) emit_lea_fp(reg int, offset int) {
1662 if offset >= 0 && offset < 4096 {
1663 g.emit32(asm_add_imm(Reg(reg), fp, u32(offset)))
1664 } else if offset < 0 && -offset < 4096 {
1665 g.emit32(asm_sub_imm(Reg(reg), fp, u32(-offset)))
1666 } else {
1667 g.emit_mov_imm(reg, i64(offset))
1668 g.emit32(asm_add_reg(Reg(reg), fp, Reg(reg)))
1669 }
1670}
1671
1672// ptr_elem_type supports ptr elem type handling for Gen.
1673fn (g &Gen) ptr_elem_type(val_id int) ssa.TypeID {
1674 if val_id <= 0 || val_id >= g.m.values.len {
1675 return 0
1676 }
1677 typ_id := g.m.values[val_id].typ
1678 if typ_id > 0 && typ_id < g.m.type_store.types.len {
1679 typ := g.m.type_store.types[typ_id]
1680 if typ.kind == .ptr_t {
1681 return typ.elem_type
1682 }
1683 }
1684 return 0
1685}
1686
1687// is_string_struct_type reports whether is string struct type applies in arm64.
1688fn (g &Gen) is_string_struct_type(typ_id ssa.TypeID) bool {
1689 if typ_id <= 0 || typ_id >= g.m.type_store.types.len {
1690 return false
1691 }
1692 typ := g.m.type_store.types[typ_id]
1693 if typ.kind != .struct_t || typ.fields.len != 2 || g.m.type_size(typ_id) != 16 {
1694 return false
1695 }
1696 first := g.m.type_store.types[typ.fields[0]]
1697 second := g.m.type_store.types[typ.fields[1]]
1698 return first.kind == .ptr_t && second.kind == .int_t && second.width == 32
1699}
1700
1701// emit_load_string_regs_from_ptr converts emit load string regs from ptr data for arm64.
1702fn (mut g Gen) emit_load_string_regs_from_ptr(ptr_reg int, data_reg int, len_reg int, typ_id ssa.TypeID) {
1703 typ := g.m.type_store.types[typ_id]
1704 g.emit_load_typed(data_reg, ptr_reg, typ.fields[0])
1705 g.emit32(asm_add_imm(Reg(11), Reg(ptr_reg), 8))
1706 g.emit_load_typed(len_reg, 11, typ.fields[1])
1707}
1708
1709// emit_load_string_regs_from_fp converts emit load string regs from fp data for arm64.
1710fn (mut g Gen) emit_load_string_regs_from_fp(off int, data_reg int, len_reg int, typ_id ssa.TypeID) {
1711 typ := g.m.type_store.types[typ_id]
1712 g.emit_load_fp(data_reg, off)
1713 g.emit_lea_fp(11, off + 8)
1714 g.emit_load_typed(len_reg, 11, typ.fields[1])
1715}
1716
1717// emit_store_typed emits emit store typed output for arm64.
1718fn (mut g Gen) emit_store_typed(src_reg int, ptr_reg int, typ ssa.TypeID) {
1719 size := g.m.type_size(typ)
1720 match size {
1721 1 { g.emit32(asm_str_b(Reg(src_reg), Reg(ptr_reg))) }
1722 2 { g.emit32(asm_str_h(Reg(src_reg), Reg(ptr_reg))) }
1723 4 { g.emit32(asm_str_w(Reg(src_reg), Reg(ptr_reg))) }
1724 else { g.emit32(asm_str(Reg(src_reg), Reg(ptr_reg))) }
1725 }
1726}
1727
1728// emit_load_typed emits emit load typed output for arm64.
1729fn (mut g Gen) emit_load_typed(dst_reg int, ptr_reg int, typ ssa.TypeID) {
1730 size := g.m.type_size(typ)
1731 match size {
1732 1 {
1733 g.emit32(asm_ldr_b(Reg(dst_reg), Reg(ptr_reg)))
1734 if g.is_signed_int_type(typ) {
1735 g.emit32(asm_sxtb(Reg(dst_reg), Reg(dst_reg)))
1736 }
1737 }
1738 2 {
1739 g.emit32(asm_ldr_h(Reg(dst_reg), Reg(ptr_reg)))
1740 if g.is_signed_int_type(typ) {
1741 g.emit32(asm_sxth(Reg(dst_reg), Reg(dst_reg)))
1742 }
1743 }
1744 4 {
1745 if g.is_signed_int_type(typ) {
1746 g.emit32(asm_ldrsw(Reg(dst_reg), Reg(ptr_reg)))
1747 } else {
1748 g.emit32(asm_ldr_w(Reg(dst_reg), Reg(ptr_reg)))
1749 }
1750 }
1751 else {
1752 g.emit32(asm_ldr(Reg(dst_reg), Reg(ptr_reg)))
1753 }
1754 }
1755}
1756
1757// is_signed_int_type reports whether is signed int type applies in arm64.
1758fn (g &Gen) is_signed_int_type(typ_id ssa.TypeID) bool {
1759 if typ_id <= 0 || typ_id >= g.m.type_store.types.len {
1760 return false
1761 }
1762 typ := g.m.type_store.types[typ_id]
1763 return typ.kind == .int_t && !typ.is_unsigned
1764}
1765
1766// is_float_type reports whether is float type applies in arm64.
1767fn (g &Gen) is_float_type(typ_id ssa.TypeID) bool {
1768 if typ_id <= 0 || typ_id >= g.m.type_store.types.len {
1769 return false
1770 }
1771 return g.m.type_store.types[typ_id].kind == .float_t
1772}
1773
1774// is_f32_type reports whether is f32 type applies in arm64.
1775fn (g &Gen) is_f32_type(typ_id ssa.TypeID) bool {
1776 if typ_id <= 0 || typ_id >= g.m.type_store.types.len {
1777 return false
1778 }
1779 typ := g.m.type_store.types[typ_id]
1780 return typ.kind == .float_t && typ.width == 32
1781}
1782
1783// ==================== Floating point ====================
1784//
1785// Float values live as their raw IEEE-754 bit pattern in ordinary integer
1786// stack slots / x-registers, exactly like the v2 backend. They are only moved
1787// into the scalar SIMD `d` registers transiently for arithmetic, comparison and
1788// conversion. An f32 is stored as its 32-bit pattern; it is widened to f64 in
1789// the `d` register for computation and narrowed back before storing.
1790
1791// load_float_operand materializes value `val_id` into the scalar float register
1792// `dreg` (as a double), using x8 as a scratch integer register.
1793fn (mut g Gen) load_float_operand(val_id int, dreg int) {
1794 if val_id <= 0 || val_id >= g.m.values.len {
1795 g.emit_mov_imm(8, 0)
1796 g.emit32(asm_fmov_d_x(dreg, Reg(8)))
1797 return
1798 }
1799 val := g.m.values[val_id]
1800 is_f32 := g.is_f32_type(val.typ)
1801 if val.kind == .constant {
1802 if is_f32 {
1803 f_val := f32(val.name.f64())
1804 bits := *unsafe { &u32(&f_val) }
1805 g.emit_mov_imm(8, i64(bits))
1806 g.emit32(asm_fmov_s_w(dreg, Reg(8)))
1807 g.emit32(asm_fcvt_d_s(dreg, dreg))
1808 } else {
1809 f_val := val.name.f64()
1810 bits := *unsafe { &u64(&f_val) }
1811 g.emit_mov_imm(8, i64(bits))
1812 g.emit32(asm_fmov_d_x(dreg, Reg(8)))
1813 }
1814 } else {
1815 reg := g.load_val(val_id, 8)
1816 if is_f32 {
1817 g.emit32(asm_fmov_s_w(dreg, Reg(reg)))
1818 g.emit32(asm_fcvt_d_s(dreg, dreg))
1819 } else {
1820 g.emit32(asm_fmov_d_x(dreg, Reg(reg)))
1821 }
1822 }
1823}
1824
1825// store_float_result writes the double in d0 back to `val_id`'s slot as the
1826// appropriate bit pattern (narrowing to f32 first when the result is an f32).
1827fn (mut g Gen) store_float_result(val_id int) {
1828 if g.is_f32_type(g.m.values[val_id].typ) {
1829 g.emit32(asm_fcvt_s_d(0, 0))
1830 g.emit32(asm_fmov_w_s(Reg(8), 0))
1831 } else {
1832 g.emit32(asm_fmov_x_d(Reg(8), 0))
1833 }
1834 g.store_val(8, val_id)
1835}
1836
1837// gen_float_binop emits a binary float op with both operands loaded into d0/d1
1838// and the result left in d0, then stored to `val_id`.
1839fn (mut g Gen) gen_float_binop(fop ssa.OpCode, lhs_id int, rhs_id int, val_id int) {
1840 g.load_float_operand(lhs_id, 0) // d0
1841 g.load_float_operand(rhs_id, 1) // d1
1842 match fop {
1843 .fadd {
1844 g.emit32(asm_fadd_d0_d0_d1())
1845 }
1846 .fsub {
1847 g.emit32(asm_fsub_d0_d0_d1())
1848 }
1849 .fmul {
1850 g.emit32(asm_fmul_d0_d0_d1())
1851 }
1852 .fdiv {
1853 g.emit32(asm_fdiv_d0_d0_d1())
1854 }
1855 .frem {
1856 // No single frem instruction: d0 = d0 - trunc(d0/d1) * d1
1857 g.emit32(asm_fdiv_d2_d0_d1())
1858 g.emit32(asm_frintz_d2())
1859 g.emit32(asm_fmsub_d0_d2_d1_d0())
1860 }
1861 else {}
1862 }
1863
1864 g.store_float_result(val_id)
1865}
1866
1867// emit_sub_sp emits emit sub sp output for arm64.
1868fn (mut g Gen) emit_sub_sp(size int) {
1869 if size > 0 && size < 4096 {
1870 g.emit32(asm_sub_imm(sp, sp, u32(size)))
1871 } else if size >= 4096 {
1872 g.emit_mov_imm(11, i64(size))
1873 g.emit32(asm_sub_sp_reg(Reg(11)))
1874 }
1875}
1876
1877// emit_add_sp emits emit add sp output for arm64.
1878fn (mut g Gen) emit_add_sp(size int) {
1879 if size > 0 && size < 4096 {
1880 g.emit32(asm_add_imm(sp, sp, u32(size)))
1881 } else if size >= 4096 {
1882 g.emit_mov_imm(11, i64(size))
1883 g.emit32(asm_add_sp_reg(Reg(11)))
1884 }
1885}
1886
1887// parse_arm64_int reads parse arm64 int input for arm64.
1888fn parse_arm64_int(s string) i64 {
1889 if s.len == 0 {
1890 return 0
1891 }
1892 mut neg := false
1893 mut start := 0
1894 if s[0] == `-` {
1895 neg = true
1896 start = 1
1897 }
1898 mut base := u64(10)
1899 mut digits_start := start
1900 if s.len > start + 2 && s[start] == `0` {
1901 if s[start + 1] == `x` || s[start + 1] == `X` {
1902 base = 16
1903 digits_start = start + 2
1904 } else if s[start + 1] == `b` || s[start + 1] == `B` {
1905 base = 2
1906 digits_start = start + 2
1907 } else if s[start + 1] == `o` || s[start + 1] == `O` {
1908 base = 8
1909 digits_start = start + 2
1910 }
1911 }
1912 mut n := u64(0)
1913 for i in digits_start .. s.len {
1914 c := s[i]
1915 mut digit := u64(base)
1916 if c >= `0` && c <= `9` {
1917 digit = u64(c - `0`)
1918 } else if c >= `a` && c <= `f` {
1919 digit = u64(c - `a` + 10)
1920 } else if c >= `A` && c <= `F` {
1921 digit = u64(c - `A` + 10)
1922 }
1923 if digit < base {
1924 n = n * base + digit
1925 }
1926 }
1927 wrapped := i64(n)
1928 return if neg { -wrapped } else { wrapped }
1929}
1930