zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

blob 5ed7b63d (173616B) - Raw


      1 const std = @import("std");
      2 const builtin = @import("builtin");
      3 const mem = std.mem;
      4 const math = std.math;
      5 const assert = std.debug.assert;
      6 const Air = @import("../../Air.zig");
      7 const Mir = @import("Mir.zig");
      8 const Emit = @import("Emit.zig");
      9 const Liveness = @import("../../Liveness.zig");
     10 const Type = @import("../../type.zig").Type;
     11 const Value = @import("../../value.zig").Value;
     12 const TypedValue = @import("../../TypedValue.zig");
     13 const link = @import("../../link.zig");
     14 const Module = @import("../../Module.zig");
     15 const Compilation = @import("../../Compilation.zig");
     16 const ErrorMsg = Module.ErrorMsg;
     17 const Target = std.Target;
     18 const Allocator = mem.Allocator;
     19 const trace = @import("../../tracy.zig").trace;
     20 const DW = std.dwarf;
     21 const leb128 = std.leb;
     22 const log = std.log.scoped(.codegen);
     23 const build_options = @import("build_options");
     24 const RegisterManagerFn = @import("../../register_manager.zig").RegisterManager;
     25 const RegisterManager = RegisterManagerFn(Self, Register, &callee_preserved_regs);
     26 
     27 const GenerateSymbolError = @import("../../codegen.zig").GenerateSymbolError;
     28 const FnResult = @import("../../codegen.zig").FnResult;
     29 const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
     30 
     31 const bits = @import("bits.zig");
     32 const abi = @import("abi.zig");
     33 const Register = bits.Register;
     34 const Instruction = bits.Instruction;
     35 const callee_preserved_regs = abi.callee_preserved_regs;
     36 const c_abi_int_param_regs = abi.c_abi_int_param_regs;
     37 const c_abi_int_return_regs = abi.c_abi_int_return_regs;
     38 
     39 const InnerError = error{
     40     OutOfMemory,
     41     CodegenFail,
     42     OutOfRegisters,
     43 };
     44 
     45 gpa: Allocator,
     46 air: Air,
     47 liveness: Liveness,
     48 bin_file: *link.File,
     49 target: *const std.Target,
     50 mod_fn: *const Module.Fn,
     51 err_msg: ?*ErrorMsg,
     52 args: []MCValue,
     53 ret_mcv: MCValue,
     54 fn_type: Type,
     55 arg_index: usize,
     56 src_loc: Module.SrcLoc,
     57 stack_align: u32,
     58 
     59 /// MIR Instructions
     60 mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
     61 /// MIR extra data
     62 mir_extra: std.ArrayListUnmanaged(u32) = .{},
     63 
     64 /// Byte offset within the source file of the ending curly.
     65 end_di_line: u32,
     66 end_di_column: u32,
     67 
     68 /// The value is an offset into the `Function` `code` from the beginning.
     69 /// To perform the reloc, write 32-bit signed little-endian integer
     70 /// which is a relative jump, based on the address following the reloc.
     71 exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{},
     72 
     73 /// Whenever there is a runtime branch, we push a Branch onto this stack,
     74 /// and pop it off when the runtime branch joins. This provides an "overlay"
     75 /// of the table of mappings from instructions to `MCValue` from within the branch.
     76 /// This way we can modify the `MCValue` for an instruction in different ways
     77 /// within different branches. Special consideration is needed when a branch
     78 /// joins with its parent, to make sure all instructions have the same MCValue
     79 /// across each runtime branch upon joining.
     80 branch_stack: *std.ArrayList(Branch),
     81 
     82 // Key is the block instruction
     83 blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .{},
     84 
     85 register_manager: RegisterManager = .{},
     86 /// Maps offset to what is stored there.
     87 stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{},
     88 /// Tracks the current instruction allocated to the compare flags
     89 compare_flags_inst: ?Air.Inst.Index = null,
     90 
     91 /// Offset from the stack base, representing the end of the stack frame.
     92 max_end_stack: u32 = 0,
     93 /// Represents the current end stack offset. If there is no existing slot
     94 /// to place a new stack allocation, it goes here, and then bumps `max_end_stack`.
     95 next_stack_offset: u32 = 0,
     96 
     97 saved_regs_stack_space: u32 = 0,
     98 
     99 /// Debug field, used to find bugs in the compiler.
    100 air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init,
    101 
    102 const air_bookkeeping_init = if (std.debug.runtime_safety) @as(usize, 0) else {};
    103 
    104 const MCValue = union(enum) {
    105     /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc.
    106     /// TODO Look into deleting this tag and using `dead` instead, since every use
    107     /// of MCValue.none should be instead looking at the type and noticing it is 0 bits.
    108     none,
    109     /// Control flow will not allow this value to be observed.
    110     unreach,
    111     /// No more references to this value remain.
    112     dead,
    113     /// The value is undefined.
    114     undef,
    115     /// A pointer-sized integer that fits in a register.
    116     /// If the type is a pointer, this is the pointer address in virtual address space.
    117     immediate: u64,
    118     /// The value is in a target-specific register.
    119     register: Register,
    120     /// The value is in memory at a hard-coded address.
    121     /// If the type is a pointer, it means the pointer address is at this memory location.
    122     memory: u64,
    123     /// The value is in memory referenced indirectly via a GOT entry index.
    124     /// If the type is a pointer, it means the pointer is referenced indirectly via GOT.
    125     /// When lowered, linker will emit relocations of type ARM64_RELOC_GOT_LOAD_PAGE21 and ARM64_RELOC_GOT_LOAD_PAGEOFF12.
    126     got_load: u32,
    127     /// The value is in memory referenced directly via symbol index.
    128     /// If the type is a pointer, it means the pointer is referenced directly via symbol index.
    129     /// When lowered, linker will emit a relocation of type ARM64_RELOC_PAGE21 and ARM64_RELOC_PAGEOFF12.
    130     direct_load: u32,
    131     /// The value is one of the stack variables.
    132     /// If the type is a pointer, it means the pointer address is in the stack at this offset.
    133     stack_offset: u32,
    134     /// The value is a pointer to one of the stack variables (payload is stack offset).
    135     ptr_stack_offset: u32,
    136     /// The value is in the compare flags assuming an unsigned operation,
    137     /// with this operator applied on top of it.
    138     compare_flags_unsigned: math.CompareOperator,
    139     /// The value is in the compare flags assuming a signed operation,
    140     /// with this operator applied on top of it.
    141     compare_flags_signed: math.CompareOperator,
    142 
    143     fn isMemory(mcv: MCValue) bool {
    144         return switch (mcv) {
    145             .memory, .stack_offset => true,
    146             else => false,
    147         };
    148     }
    149 
    150     fn isImmediate(mcv: MCValue) bool {
    151         return switch (mcv) {
    152             .immediate => true,
    153             else => false,
    154         };
    155     }
    156 
    157     fn isMutable(mcv: MCValue) bool {
    158         return switch (mcv) {
    159             .none => unreachable,
    160             .unreach => unreachable,
    161             .dead => unreachable,
    162 
    163             .immediate,
    164             .memory,
    165             .compare_flags_unsigned,
    166             .compare_flags_signed,
    167             .ptr_stack_offset,
    168             .undef,
    169             => false,
    170 
    171             .register,
    172             .stack_offset,
    173             => true,
    174         };
    175     }
    176 };
    177 
    178 const Branch = struct {
    179     inst_table: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, MCValue) = .{},
    180 
    181     fn deinit(self: *Branch, gpa: Allocator) void {
    182         self.inst_table.deinit(gpa);
    183         self.* = undefined;
    184     }
    185 };
    186 
    187 const StackAllocation = struct {
    188     inst: Air.Inst.Index,
    189     /// TODO do we need size? should be determined by inst.ty.abiSize()
    190     size: u32,
    191 };
    192 
    193 const BlockData = struct {
    194     relocs: std.ArrayListUnmanaged(Mir.Inst.Index),
    195     /// The first break instruction encounters `null` here and chooses a
    196     /// machine code value for the block result, populating this field.
    197     /// Following break instructions encounter that value and use it for
    198     /// the location to store their block results.
    199     mcv: MCValue,
    200 };
    201 
    202 const BigTomb = struct {
    203     function: *Self,
    204     inst: Air.Inst.Index,
    205     lbt: Liveness.BigTomb,
    206 
    207     fn feed(bt: *BigTomb, op_ref: Air.Inst.Ref) void {
    208         const dies = bt.lbt.feed();
    209         const op_index = Air.refToIndex(op_ref) orelse return;
    210         if (!dies) return;
    211         bt.function.processDeath(op_index);
    212     }
    213 
    214     fn finishAir(bt: *BigTomb, result: MCValue) void {
    215         const is_used = !bt.function.liveness.isUnused(bt.inst);
    216         if (is_used) {
    217             log.debug("%{d} => {}", .{ bt.inst, result });
    218             const branch = &bt.function.branch_stack.items[bt.function.branch_stack.items.len - 1];
    219             branch.inst_table.putAssumeCapacityNoClobber(bt.inst, result);
    220         }
    221         bt.function.finishAirBookkeeping();
    222     }
    223 };
    224 
    225 const Self = @This();
    226 
    227 pub fn generate(
    228     bin_file: *link.File,
    229     src_loc: Module.SrcLoc,
    230     module_fn: *Module.Fn,
    231     air: Air,
    232     liveness: Liveness,
    233     code: *std.ArrayList(u8),
    234     debug_output: DebugInfoOutput,
    235 ) GenerateSymbolError!FnResult {
    236     if (build_options.skip_non_native and builtin.cpu.arch != bin_file.options.target.cpu.arch) {
    237         @panic("Attempted to compile for architecture that was disabled by build configuration");
    238     }
    239 
    240     const mod = bin_file.options.module.?;
    241     const fn_owner_decl = mod.declPtr(module_fn.owner_decl);
    242     assert(fn_owner_decl.has_tv);
    243     const fn_type = fn_owner_decl.ty;
    244 
    245     var branch_stack = std.ArrayList(Branch).init(bin_file.allocator);
    246     defer {
    247         assert(branch_stack.items.len == 1);
    248         branch_stack.items[0].deinit(bin_file.allocator);
    249         branch_stack.deinit();
    250     }
    251     try branch_stack.append(.{});
    252 
    253     var function = Self{
    254         .gpa = bin_file.allocator,
    255         .air = air,
    256         .liveness = liveness,
    257         .target = &bin_file.options.target,
    258         .bin_file = bin_file,
    259         .mod_fn = module_fn,
    260         .err_msg = null,
    261         .args = undefined, // populated after `resolveCallingConventionValues`
    262         .ret_mcv = undefined, // populated after `resolveCallingConventionValues`
    263         .fn_type = fn_type,
    264         .arg_index = 0,
    265         .branch_stack = &branch_stack,
    266         .src_loc = src_loc,
    267         .stack_align = undefined,
    268         .end_di_line = module_fn.rbrace_line,
    269         .end_di_column = module_fn.rbrace_column,
    270     };
    271     defer function.stack.deinit(bin_file.allocator);
    272     defer function.blocks.deinit(bin_file.allocator);
    273     defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
    274 
    275     var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) {
    276         error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
    277         error.OutOfRegisters => return FnResult{
    278             .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
    279         },
    280         else => |e| return e,
    281     };
    282     defer call_info.deinit(&function);
    283 
    284     function.args = call_info.args;
    285     function.ret_mcv = call_info.return_value;
    286     function.stack_align = call_info.stack_align;
    287     function.max_end_stack = call_info.stack_byte_count;
    288 
    289     function.gen() catch |err| switch (err) {
    290         error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
    291         error.OutOfRegisters => return FnResult{
    292             .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
    293         },
    294         else => |e| return e,
    295     };
    296 
    297     var mir = Mir{
    298         .instructions = function.mir_instructions.toOwnedSlice(),
    299         .extra = function.mir_extra.toOwnedSlice(bin_file.allocator),
    300     };
    301     defer mir.deinit(bin_file.allocator);
    302 
    303     var emit = Emit{
    304         .mir = mir,
    305         .bin_file = bin_file,
    306         .debug_output = debug_output,
    307         .target = &bin_file.options.target,
    308         .src_loc = src_loc,
    309         .code = code,
    310         .prev_di_pc = 0,
    311         .prev_di_line = module_fn.lbrace_line,
    312         .prev_di_column = module_fn.lbrace_column,
    313         .stack_size = mem.alignForwardGeneric(u32, function.max_end_stack, function.stack_align),
    314     };
    315     defer emit.deinit();
    316 
    317     emit.emitMir() catch |err| switch (err) {
    318         error.EmitFail => return FnResult{ .fail = emit.err_msg.? },
    319         else => |e| return e,
    320     };
    321 
    322     if (function.err_msg) |em| {
    323         return FnResult{ .fail = em };
    324     } else {
    325         return FnResult{ .appended = {} };
    326     }
    327 }
    328 
    329 fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index {
    330     const gpa = self.gpa;
    331 
    332     try self.mir_instructions.ensureUnusedCapacity(gpa, 1);
    333 
    334     const result_index = @intCast(Air.Inst.Index, self.mir_instructions.len);
    335     self.mir_instructions.appendAssumeCapacity(inst);
    336     return result_index;
    337 }
    338 
    339 pub fn addExtra(self: *Self, extra: anytype) Allocator.Error!u32 {
    340     const fields = std.meta.fields(@TypeOf(extra));
    341     try self.mir_extra.ensureUnusedCapacity(self.gpa, fields.len);
    342     return self.addExtraAssumeCapacity(extra);
    343 }
    344 
    345 pub fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 {
    346     const fields = std.meta.fields(@TypeOf(extra));
    347     const result = @intCast(u32, self.mir_extra.items.len);
    348     inline for (fields) |field| {
    349         self.mir_extra.appendAssumeCapacity(switch (field.field_type) {
    350             u32 => @field(extra, field.name),
    351             i32 => @bitCast(u32, @field(extra, field.name)),
    352             else => @compileError("bad field type"),
    353         });
    354     }
    355     return result;
    356 }
    357 
    358 fn gen(self: *Self) !void {
    359     const cc = self.fn_type.fnCallingConvention();
    360     if (cc != .Naked) {
    361         // stp fp, lr, [sp, #-16]!
    362         _ = try self.addInst(.{
    363             .tag = .stp,
    364             .data = .{ .load_store_register_pair = .{
    365                 .rt = .x29,
    366                 .rt2 = .x30,
    367                 .rn = .sp,
    368                 .offset = Instruction.LoadStorePairOffset.pre_index(-16),
    369             } },
    370         });
    371 
    372         // <store other registers>
    373         const backpatch_save_registers = try self.addInst(.{
    374             .tag = .nop,
    375             .data = .{ .nop = {} },
    376         });
    377 
    378         // mov fp, sp
    379         _ = try self.addInst(.{
    380             .tag = .mov_to_from_sp,
    381             .data = .{ .rr = .{ .rd = .x29, .rn = .sp } },
    382         });
    383 
    384         // sub sp, sp, #reloc
    385         const backpatch_reloc = try self.addInst(.{
    386             .tag = .nop,
    387             .data = .{ .nop = {} },
    388         });
    389 
    390         _ = try self.addInst(.{
    391             .tag = .dbg_prologue_end,
    392             .data = .{ .nop = {} },
    393         });
    394 
    395         try self.genBody(self.air.getMainBody());
    396 
    397         // Backpatch push callee saved regs
    398         var saved_regs: u32 = 0;
    399         self.saved_regs_stack_space = 16;
    400         inline for (callee_preserved_regs) |reg| {
    401             if (self.register_manager.isRegAllocated(reg)) {
    402                 saved_regs |= @as(u32, 1) << @intCast(u5, reg.id());
    403                 self.saved_regs_stack_space += 8;
    404             }
    405         }
    406 
    407         // Emit.mirPopPushRegs automatically adds extra empty space so
    408         // that sp is always aligned to 16
    409         if (!std.mem.isAlignedGeneric(u32, self.saved_regs_stack_space, 16)) {
    410             self.saved_regs_stack_space += 8;
    411         }
    412         assert(std.mem.isAlignedGeneric(u32, self.saved_regs_stack_space, 16));
    413 
    414         self.mir_instructions.set(backpatch_save_registers, .{
    415             .tag = .push_regs,
    416             .data = .{ .reg_list = saved_regs },
    417         });
    418 
    419         // Backpatch stack offset
    420         const total_stack_size = self.max_end_stack + self.saved_regs_stack_space;
    421         const aligned_total_stack_end = mem.alignForwardGeneric(u32, total_stack_size, self.stack_align);
    422         const stack_size = aligned_total_stack_end - self.saved_regs_stack_space;
    423         if (math.cast(u12, stack_size)) |size| {
    424             self.mir_instructions.set(backpatch_reloc, .{
    425                 .tag = .sub_immediate,
    426                 .data = .{ .rr_imm12_sh = .{ .rd = .sp, .rn = .sp, .imm12 = size } },
    427             });
    428         } else |_| {
    429             return self.failSymbol("TODO AArch64: allow larger stacks", .{});
    430         }
    431 
    432         _ = try self.addInst(.{
    433             .tag = .dbg_epilogue_begin,
    434             .data = .{ .nop = {} },
    435         });
    436 
    437         // exitlude jumps
    438         if (self.exitlude_jump_relocs.items.len > 0 and
    439             self.exitlude_jump_relocs.items[self.exitlude_jump_relocs.items.len - 1] == self.mir_instructions.len - 2)
    440         {
    441             // If the last Mir instruction (apart from the
    442             // dbg_epilogue_begin) is the last exitlude jump
    443             // relocation (which would just jump one instruction
    444             // further), it can be safely removed
    445             self.mir_instructions.orderedRemove(self.exitlude_jump_relocs.pop());
    446         }
    447 
    448         for (self.exitlude_jump_relocs.items) |jmp_reloc| {
    449             self.mir_instructions.set(jmp_reloc, .{
    450                 .tag = .b,
    451                 .data = .{ .inst = @intCast(u32, self.mir_instructions.len) },
    452             });
    453         }
    454 
    455         // add sp, sp, #stack_size
    456         _ = try self.addInst(.{
    457             .tag = .add_immediate,
    458             .data = .{ .rr_imm12_sh = .{ .rd = .sp, .rn = .sp, .imm12 = @intCast(u12, stack_size) } },
    459         });
    460 
    461         // <load other registers>
    462         _ = try self.addInst(.{
    463             .tag = .pop_regs,
    464             .data = .{ .reg_list = saved_regs },
    465         });
    466 
    467         // ldp fp, lr, [sp], #16
    468         _ = try self.addInst(.{
    469             .tag = .ldp,
    470             .data = .{ .load_store_register_pair = .{
    471                 .rt = .x29,
    472                 .rt2 = .x30,
    473                 .rn = .sp,
    474                 .offset = Instruction.LoadStorePairOffset.post_index(16),
    475             } },
    476         });
    477 
    478         // ret lr
    479         _ = try self.addInst(.{
    480             .tag = .ret,
    481             .data = .{ .reg = .x30 },
    482         });
    483     } else {
    484         _ = try self.addInst(.{
    485             .tag = .dbg_prologue_end,
    486             .data = .{ .nop = {} },
    487         });
    488 
    489         try self.genBody(self.air.getMainBody());
    490 
    491         _ = try self.addInst(.{
    492             .tag = .dbg_epilogue_begin,
    493             .data = .{ .nop = {} },
    494         });
    495     }
    496 
    497     // Drop them off at the rbrace.
    498     _ = try self.addInst(.{
    499         .tag = .dbg_line,
    500         .data = .{ .dbg_line_column = .{
    501             .line = self.end_di_line,
    502             .column = self.end_di_column,
    503         } },
    504     });
    505 }
    506 
    507 fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
    508     const air_tags = self.air.instructions.items(.tag);
    509 
    510     for (body) |inst| {
    511         const old_air_bookkeeping = self.air_bookkeeping;
    512         try self.ensureProcessDeathCapacity(Liveness.bpi);
    513 
    514         switch (air_tags[inst]) {
    515             // zig fmt: off
    516             .add, .ptr_add   => try self.airBinOp(inst),
    517             .addwrap         => try self.airBinOp(inst),
    518             .add_sat         => try self.airAddSat(inst),
    519             .sub, .ptr_sub   => try self.airBinOp(inst),
    520             .subwrap         => try self.airBinOp(inst),
    521             .sub_sat         => try self.airSubSat(inst),
    522             .mul             => try self.airBinOp(inst),
    523             .mulwrap         => try self.airBinOp(inst),
    524             .mul_sat         => try self.airMulSat(inst),
    525             .rem             => try self.airRem(inst),
    526             .mod             => try self.airMod(inst),
    527             .shl, .shl_exact => try self.airBinOp(inst),
    528             .shl_sat         => try self.airShlSat(inst),
    529             .min             => try self.airMin(inst),
    530             .max             => try self.airMax(inst),
    531             .slice           => try self.airSlice(inst),
    532 
    533             .sqrt,
    534             .sin,
    535             .cos,
    536             .tan,
    537             .exp,
    538             .exp2,
    539             .log,
    540             .log2,
    541             .log10,
    542             .fabs,
    543             .floor,
    544             .ceil,
    545             .round,
    546             .trunc_float
    547             => try self.airUnaryMath(inst),
    548 
    549             .add_with_overflow => try self.airAddWithOverflow(inst),
    550             .sub_with_overflow => try self.airSubWithOverflow(inst),
    551             .mul_with_overflow => try self.airMulWithOverflow(inst),
    552             .shl_with_overflow => try self.airShlWithOverflow(inst),
    553 
    554             .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
    555 
    556             .cmp_lt  => try self.airCmp(inst, .lt),
    557             .cmp_lte => try self.airCmp(inst, .lte),
    558             .cmp_eq  => try self.airCmp(inst, .eq),
    559             .cmp_gte => try self.airCmp(inst, .gte),
    560             .cmp_gt  => try self.airCmp(inst, .gt),
    561             .cmp_neq => try self.airCmp(inst, .neq),
    562 
    563             .cmp_vector => try self.airCmpVector(inst),
    564             .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst),
    565 
    566             .bool_and        => try self.airBinOp(inst),
    567             .bool_or         => try self.airBinOp(inst),
    568             .bit_and         => try self.airBinOp(inst),
    569             .bit_or          => try self.airBinOp(inst),
    570             .xor             => try self.airBinOp(inst),
    571             .shr, .shr_exact => try self.airBinOp(inst),
    572 
    573             .alloc           => try self.airAlloc(inst),
    574             .ret_ptr         => try self.airRetPtr(inst),
    575             .arg             => try self.airArg(inst),
    576             .assembly        => try self.airAsm(inst),
    577             .bitcast         => try self.airBitCast(inst),
    578             .block           => try self.airBlock(inst),
    579             .br              => try self.airBr(inst),
    580             .breakpoint      => try self.airBreakpoint(),
    581             .ret_addr        => try self.airRetAddr(inst),
    582             .frame_addr      => try self.airFrameAddress(inst),
    583             .fence           => try self.airFence(),
    584             .cond_br         => try self.airCondBr(inst),
    585             .dbg_stmt        => try self.airDbgStmt(inst),
    586             .fptrunc         => try self.airFptrunc(inst),
    587             .fpext           => try self.airFpext(inst),
    588             .intcast         => try self.airIntCast(inst),
    589             .trunc           => try self.airTrunc(inst),
    590             .bool_to_int     => try self.airBoolToInt(inst),
    591             .is_non_null     => try self.airIsNonNull(inst),
    592             .is_non_null_ptr => try self.airIsNonNullPtr(inst),
    593             .is_null         => try self.airIsNull(inst),
    594             .is_null_ptr     => try self.airIsNullPtr(inst),
    595             .is_non_err      => try self.airIsNonErr(inst),
    596             .is_non_err_ptr  => try self.airIsNonErrPtr(inst),
    597             .is_err          => try self.airIsErr(inst),
    598             .is_err_ptr      => try self.airIsErrPtr(inst),
    599             .load            => try self.airLoad(inst),
    600             .loop            => try self.airLoop(inst),
    601             .not             => try self.airNot(inst),
    602             .ptrtoint        => try self.airPtrToInt(inst),
    603             .ret             => try self.airRet(inst),
    604             .ret_load        => try self.airRetLoad(inst),
    605             .store           => try self.airStore(inst),
    606             .struct_field_ptr=> try self.airStructFieldPtr(inst),
    607             .struct_field_val=> try self.airStructFieldVal(inst),
    608             .array_to_slice  => try self.airArrayToSlice(inst),
    609             .int_to_float    => try self.airIntToFloat(inst),
    610             .float_to_int    => try self.airFloatToInt(inst),
    611             .cmpxchg_strong  => try self.airCmpxchg(inst),
    612             .cmpxchg_weak    => try self.airCmpxchg(inst),
    613             .atomic_rmw      => try self.airAtomicRmw(inst),
    614             .atomic_load     => try self.airAtomicLoad(inst),
    615             .memcpy          => try self.airMemcpy(inst),
    616             .memset          => try self.airMemset(inst),
    617             .set_union_tag   => try self.airSetUnionTag(inst),
    618             .get_union_tag   => try self.airGetUnionTag(inst),
    619             .clz             => try self.airClz(inst),
    620             .ctz             => try self.airCtz(inst),
    621             .popcount        => try self.airPopcount(inst),
    622             .byte_swap       => try self.airByteSwap(inst),
    623             .bit_reverse     => try self.airBitReverse(inst),
    624             .tag_name        => try self.airTagName(inst),
    625             .error_name      => try self.airErrorName(inst),
    626             .splat           => try self.airSplat(inst),
    627             .select          => try self.airSelect(inst),
    628             .shuffle         => try self.airShuffle(inst),
    629             .reduce          => try self.airReduce(inst),
    630             .aggregate_init  => try self.airAggregateInit(inst),
    631             .union_init      => try self.airUnionInit(inst),
    632             .prefetch        => try self.airPrefetch(inst),
    633             .mul_add         => try self.airMulAdd(inst),
    634 
    635             .dbg_var_ptr,
    636             .dbg_var_val,
    637             => try self.airDbgVar(inst),
    638 
    639             .dbg_inline_begin,
    640             .dbg_inline_end,
    641             => try self.airDbgInline(inst),
    642 
    643             .dbg_block_begin,
    644             .dbg_block_end,
    645             => try self.airDbgBlock(inst),
    646 
    647             .call              => try self.airCall(inst, .auto),
    648             .call_always_tail  => try self.airCall(inst, .always_tail),
    649             .call_never_tail   => try self.airCall(inst, .never_tail),
    650             .call_never_inline => try self.airCall(inst, .never_inline),
    651 
    652             .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
    653             .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
    654             .atomic_store_release   => try self.airAtomicStore(inst, .Release),
    655             .atomic_store_seq_cst   => try self.airAtomicStore(inst, .SeqCst),
    656 
    657             .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
    658             .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
    659             .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
    660             .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
    661 
    662             .field_parent_ptr => try self.airFieldParentPtr(inst),
    663 
    664             .switch_br       => try self.airSwitch(inst),
    665             .slice_ptr       => try self.airSlicePtr(inst),
    666             .slice_len       => try self.airSliceLen(inst),
    667 
    668             .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst),
    669             .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst),
    670 
    671             .array_elem_val      => try self.airArrayElemVal(inst),
    672             .slice_elem_val      => try self.airSliceElemVal(inst),
    673             .slice_elem_ptr      => try self.airSliceElemPtr(inst),
    674             .ptr_elem_val        => try self.airPtrElemVal(inst),
    675             .ptr_elem_ptr        => try self.airPtrElemPtr(inst),
    676 
    677             .constant => unreachable, // excluded from function bodies
    678             .const_ty => unreachable, // excluded from function bodies
    679             .unreach  => self.finishAirBookkeeping(),
    680 
    681             .optional_payload           => try self.airOptionalPayload(inst),
    682             .optional_payload_ptr       => try self.airOptionalPayloadPtr(inst),
    683             .optional_payload_ptr_set   => try self.airOptionalPayloadPtrSet(inst),
    684             .unwrap_errunion_err        => try self.airUnwrapErrErr(inst),
    685             .unwrap_errunion_payload    => try self.airUnwrapErrPayload(inst),
    686             .unwrap_errunion_err_ptr    => try self.airUnwrapErrErrPtr(inst),
    687             .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
    688             .errunion_payload_ptr_set   => try self.airErrUnionPayloadPtrSet(inst),
    689 
    690             .wrap_optional         => try self.airWrapOptional(inst),
    691             .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
    692             .wrap_errunion_err     => try self.airWrapErrUnionErr(inst),
    693 
    694             .wasm_memory_size => unreachable,
    695             .wasm_memory_grow => unreachable,
    696             // zig fmt: on
    697         }
    698 
    699         assert(!self.register_manager.frozenRegsExist());
    700 
    701         if (std.debug.runtime_safety) {
    702             if (self.air_bookkeeping < old_air_bookkeeping + 1) {
    703                 std.debug.panic("in codegen.zig, handling of AIR instruction %{d} ('{}') did not do proper bookkeeping. Look for a missing call to finishAir.", .{ inst, air_tags[inst] });
    704             }
    705         }
    706     }
    707 }
    708 
    709 /// Asserts there is already capacity to insert into top branch inst_table.
    710 fn processDeath(self: *Self, inst: Air.Inst.Index) void {
    711     const air_tags = self.air.instructions.items(.tag);
    712     if (air_tags[inst] == .constant) return; // Constants are immortal.
    713     // When editing this function, note that the logic must synchronize with `reuseOperand`.
    714     const prev_value = self.getResolvedInstValue(inst);
    715     const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
    716     branch.inst_table.putAssumeCapacity(inst, .dead);
    717     switch (prev_value) {
    718         .register => |reg| {
    719             const canon_reg = toCanonicalReg(reg);
    720             self.register_manager.freeReg(canon_reg);
    721         },
    722         .compare_flags_signed, .compare_flags_unsigned => {
    723             self.compare_flags_inst = null;
    724         },
    725         else => {}, // TODO process stack allocation death
    726     }
    727 }
    728 
    729 /// Called when there are no operands, and the instruction is always unreferenced.
    730 fn finishAirBookkeeping(self: *Self) void {
    731     if (std.debug.runtime_safety) {
    732         self.air_bookkeeping += 1;
    733     }
    734 }
    735 
    736 fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Liveness.bpi - 1]Air.Inst.Ref) void {
    737     var tomb_bits = self.liveness.getTombBits(inst);
    738     for (operands) |op| {
    739         const dies = @truncate(u1, tomb_bits) != 0;
    740         tomb_bits >>= 1;
    741         if (!dies) continue;
    742         const op_int = @enumToInt(op);
    743         if (op_int < Air.Inst.Ref.typed_value_map.len) continue;
    744         const op_index = @intCast(Air.Inst.Index, op_int - Air.Inst.Ref.typed_value_map.len);
    745         self.processDeath(op_index);
    746     }
    747     const is_used = @truncate(u1, tomb_bits) == 0;
    748     if (is_used) {
    749         log.debug("%{d} => {}", .{ inst, result });
    750         const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
    751         branch.inst_table.putAssumeCapacityNoClobber(inst, result);
    752 
    753         switch (result) {
    754             .register => |reg| {
    755                 // In some cases (such as bitcast), an operand
    756                 // may be the same MCValue as the result. If
    757                 // that operand died and was a register, it
    758                 // was freed by processDeath. We have to
    759                 // "re-allocate" the register.
    760                 if (self.register_manager.isRegFree(reg)) {
    761                     self.register_manager.getRegAssumeFree(reg, inst);
    762                 }
    763             },
    764             else => {},
    765         }
    766     }
    767     self.finishAirBookkeeping();
    768 }
    769 
    770 fn ensureProcessDeathCapacity(self: *Self, additional_count: usize) !void {
    771     const table = &self.branch_stack.items[self.branch_stack.items.len - 1].inst_table;
    772     try table.ensureUnusedCapacity(self.gpa, additional_count);
    773 }
    774 
    775 /// Adds a Type to the .debug_info at the current position. The bytes will be populated later,
    776 /// after codegen for this symbol is done.
    777 fn addDbgInfoTypeReloc(self: *Self, ty: Type) !void {
    778     switch (self.debug_output) {
    779         .dwarf => |dbg_out| {
    780             assert(ty.hasRuntimeBits());
    781             const index = dbg_out.dbg_info.items.len;
    782             try dbg_out.dbg_info.resize(index + 4); // DW.AT.type,  DW.FORM.ref4
    783 
    784             const gop = try dbg_out.dbg_info_type_relocs.getOrPutContext(self.gpa, ty, .{
    785                 .target = self.target.*,
    786             });
    787             if (!gop.found_existing) {
    788                 gop.value_ptr.* = .{
    789                     .off = undefined,
    790                     .relocs = .{},
    791                 };
    792             }
    793             try gop.value_ptr.relocs.append(self.gpa, @intCast(u32, index));
    794         },
    795         .plan9 => {},
    796         .none => {},
    797     }
    798 }
    799 
    800 fn allocMem(self: *Self, inst: Air.Inst.Index, abi_size: u32, abi_align: u32) !u32 {
    801     if (abi_align > self.stack_align)
    802         self.stack_align = abi_align;
    803     // TODO find a free slot instead of always appending
    804     const offset = mem.alignForwardGeneric(u32, self.next_stack_offset, abi_align) + abi_size;
    805     self.next_stack_offset = offset;
    806     self.max_end_stack = @maximum(self.max_end_stack, self.next_stack_offset);
    807     try self.stack.putNoClobber(self.gpa, offset, .{
    808         .inst = inst,
    809         .size = abi_size,
    810     });
    811     return offset;
    812 }
    813 
    814 /// Use a pointer instruction as the basis for allocating stack memory.
    815 fn allocMemPtr(self: *Self, inst: Air.Inst.Index) !u32 {
    816     const elem_ty = self.air.typeOfIndex(inst).elemType();
    817 
    818     if (!elem_ty.hasRuntimeBits()) {
    819         // return the stack offset 0. Stack offset 0 will be where all
    820         // zero-sized stack allocations live as non-zero-sized
    821         // allocations will always have an offset > 0.
    822         return @as(u32, 0);
    823     }
    824 
    825     const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch {
    826         const mod = self.bin_file.options.module.?;
    827         return self.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(mod)});
    828     };
    829     // TODO swap this for inst.ty.ptrAlign
    830     const abi_align = elem_ty.abiAlignment(self.target.*);
    831     return self.allocMem(inst, abi_size, abi_align);
    832 }
    833 
    834 fn allocRegOrMem(self: *Self, inst: Air.Inst.Index, reg_ok: bool) !MCValue {
    835     const elem_ty = self.air.typeOfIndex(inst);
    836     const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch {
    837         const mod = self.bin_file.options.module.?;
    838         return self.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(mod)});
    839     };
    840     const abi_align = elem_ty.abiAlignment(self.target.*);
    841     if (abi_align > self.stack_align)
    842         self.stack_align = abi_align;
    843 
    844     if (reg_ok) {
    845         // Make sure the type can fit in a register before we try to allocate one.
    846         if (abi_size <= 8) {
    847             if (self.register_manager.tryAllocReg(inst)) |reg| {
    848                 return MCValue{ .register = registerAlias(reg, abi_size) };
    849             }
    850         }
    851     }
    852     const stack_offset = try self.allocMem(inst, abi_size, abi_align);
    853     return MCValue{ .stack_offset = stack_offset };
    854 }
    855 
    856 pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void {
    857     const stack_mcv = try self.allocRegOrMem(inst, false);
    858     log.debug("spilling {d} to stack mcv {any}", .{ inst, stack_mcv });
    859     const reg_mcv = self.getResolvedInstValue(inst);
    860     assert(reg == toCanonicalReg(reg_mcv.register));
    861     const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
    862     try branch.inst_table.put(self.gpa, inst, stack_mcv);
    863     try self.genSetStack(self.air.typeOfIndex(inst), stack_mcv.stack_offset, reg_mcv);
    864 }
    865 
    866 /// Save the current instruction stored in the compare flags if
    867 /// occupied
    868 fn spillCompareFlagsIfOccupied(self: *Self) !void {
    869     if (self.compare_flags_inst) |inst_to_save| {
    870         const mcv = self.getResolvedInstValue(inst_to_save);
    871         assert(mcv == .compare_flags_signed or mcv == .compare_flags_unsigned);
    872 
    873         const new_mcv = try self.allocRegOrMem(inst_to_save, true);
    874         try self.setRegOrMem(self.air.typeOfIndex(inst_to_save), new_mcv, mcv);
    875         log.debug("spilling {d} to mcv {any}", .{ inst_to_save, new_mcv });
    876 
    877         const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
    878         try branch.inst_table.put(self.gpa, inst_to_save, new_mcv);
    879 
    880         self.compare_flags_inst = null;
    881     }
    882 }
    883 
    884 /// Copies a value to a register without tracking the register. The register is not considered
    885 /// allocated. A second call to `copyToTmpRegister` may return the same register.
    886 /// This can have a side effect of spilling instructions to the stack to free up a register.
    887 fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register {
    888     const raw_reg = try self.register_manager.allocReg(null);
    889     const reg = registerAlias(raw_reg, ty.abiSize(self.target.*));
    890     try self.genSetReg(ty, reg, mcv);
    891     return reg;
    892 }
    893 
    894 /// Allocates a new register and copies `mcv` into it.
    895 /// `reg_owner` is the instruction that gets associated with the register in the register table.
    896 /// This can have a side effect of spilling instructions to the stack to free up a register.
    897 fn copyToNewRegister(self: *Self, reg_owner: Air.Inst.Index, mcv: MCValue) !MCValue {
    898     const raw_reg = try self.register_manager.allocReg(reg_owner);
    899     const ty = self.air.typeOfIndex(reg_owner);
    900     const reg = registerAlias(raw_reg, ty.abiSize(self.target.*));
    901     try self.genSetReg(self.air.typeOfIndex(reg_owner), reg, mcv);
    902     return MCValue{ .register = reg };
    903 }
    904 
    905 fn airAlloc(self: *Self, inst: Air.Inst.Index) !void {
    906     const stack_offset = try self.allocMemPtr(inst);
    907     return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none });
    908 }
    909 
    910 fn airRetPtr(self: *Self, inst: Air.Inst.Index) !void {
    911     const stack_offset = try self.allocMemPtr(inst);
    912     return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none });
    913 }
    914 
    915 fn airFptrunc(self: *Self, inst: Air.Inst.Index) !void {
    916     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
    917     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFptrunc for {}", .{self.target.cpu.arch});
    918     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
    919 }
    920 
    921 fn airFpext(self: *Self, inst: Air.Inst.Index) !void {
    922     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
    923     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFpext for {}", .{self.target.cpu.arch});
    924     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
    925 }
    926 
    927 fn airIntCast(self: *Self, inst: Air.Inst.Index) !void {
    928     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
    929     if (self.liveness.isUnused(inst))
    930         return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none });
    931 
    932     const operand_ty = self.air.typeOf(ty_op.operand);
    933     const operand = try self.resolveInst(ty_op.operand);
    934     const info_a = operand_ty.intInfo(self.target.*);
    935     const info_b = self.air.typeOfIndex(inst).intInfo(self.target.*);
    936     if (info_a.signedness != info_b.signedness)
    937         return self.fail("TODO gen intcast sign safety in semantic analysis", .{});
    938 
    939     if (info_a.bits == info_b.bits)
    940         return self.finishAir(inst, operand, .{ ty_op.operand, .none, .none });
    941 
    942     return self.fail("TODO implement intCast for {}", .{self.target.cpu.arch});
    943 }
    944 
    945 fn truncRegister(
    946     self: *Self,
    947     operand_reg: Register,
    948     dest_reg: Register,
    949     int_signedness: std.builtin.Signedness,
    950     int_bits: u16,
    951 ) !void {
    952     switch (int_bits) {
    953         1...31, 33...63 => {
    954             _ = try self.addInst(.{
    955                 .tag = switch (int_signedness) {
    956                     .signed => .sbfx,
    957                     .unsigned => .ubfx,
    958                 },
    959                 .data = .{ .rr_lsb_width = .{
    960                     .rd = dest_reg,
    961                     .rn = operand_reg,
    962                     .lsb = 0,
    963                     .width = @intCast(u6, int_bits),
    964                 } },
    965             });
    966         },
    967         32, 64 => {
    968             _ = try self.addInst(.{
    969                 .tag = .mov_register,
    970                 .data = .{ .rr = .{
    971                     .rd = dest_reg,
    972                     .rn = operand_reg,
    973                 } },
    974             });
    975         },
    976         else => unreachable,
    977     }
    978 }
    979 
    980 fn trunc(
    981     self: *Self,
    982     maybe_inst: ?Air.Inst.Index,
    983     operand: MCValue,
    984     operand_ty: Type,
    985     dest_ty: Type,
    986 ) !MCValue {
    987     const info_a = operand_ty.intInfo(self.target.*);
    988     const info_b = dest_ty.intInfo(self.target.*);
    989 
    990     if (info_b.bits <= 64) {
    991         const operand_reg = switch (operand) {
    992             .register => |r| r,
    993             else => operand_reg: {
    994                 if (info_a.bits <= 64) {
    995                     const raw_reg = try self.copyToTmpRegister(operand_ty, operand);
    996                     break :operand_reg registerAlias(raw_reg, operand_ty.abiSize(self.target.*));
    997                 } else {
    998                     return self.fail("TODO load least significant word into register", .{});
    999                 }
   1000             },
   1001         };
   1002         self.register_manager.freezeRegs(&.{operand_reg});
   1003         defer self.register_manager.unfreezeRegs(&.{operand_reg});
   1004 
   1005         const dest_reg = if (maybe_inst) |inst| blk: {
   1006             const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1007 
   1008             if (operand == .register and self.reuseOperand(inst, ty_op.operand, 0, operand)) {
   1009                 break :blk registerAlias(operand_reg, dest_ty.abiSize(self.target.*));
   1010             } else {
   1011                 const raw_reg = try self.register_manager.allocReg(inst);
   1012                 break :blk registerAlias(raw_reg, dest_ty.abiSize(self.target.*));
   1013             }
   1014         } else blk: {
   1015             const raw_reg = try self.register_manager.allocReg(null);
   1016             break :blk registerAlias(raw_reg, dest_ty.abiSize(self.target.*));
   1017         };
   1018 
   1019         try self.truncRegister(operand_reg, dest_reg, info_b.signedness, info_b.bits);
   1020 
   1021         return MCValue{ .register = dest_reg };
   1022     } else {
   1023         return self.fail("TODO: truncate to ints > 32 bits", .{});
   1024     }
   1025 }
   1026 
   1027 fn airTrunc(self: *Self, inst: Air.Inst.Index) !void {
   1028     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1029     const operand = try self.resolveInst(ty_op.operand);
   1030     const operand_ty = self.air.typeOf(ty_op.operand);
   1031     const dest_ty = self.air.typeOfIndex(inst);
   1032 
   1033     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else blk: {
   1034         break :blk try self.trunc(inst, operand, operand_ty, dest_ty);
   1035     };
   1036 
   1037     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1038 }
   1039 
   1040 fn airBoolToInt(self: *Self, inst: Air.Inst.Index) !void {
   1041     const un_op = self.air.instructions.items(.data)[inst].un_op;
   1042     const operand = try self.resolveInst(un_op);
   1043     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else operand;
   1044     return self.finishAir(inst, result, .{ un_op, .none, .none });
   1045 }
   1046 
   1047 fn airNot(self: *Self, inst: Air.Inst.Index) !void {
   1048     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1049     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   1050         const operand = try self.resolveInst(ty_op.operand);
   1051         const operand_ty = self.air.typeOf(ty_op.operand);
   1052         switch (operand) {
   1053             .dead => unreachable,
   1054             .unreach => unreachable,
   1055             .compare_flags_unsigned => |op| {
   1056                 const r = MCValue{
   1057                     .compare_flags_unsigned = switch (op) {
   1058                         .gte => .lt,
   1059                         .gt => .lte,
   1060                         .neq => .eq,
   1061                         .lt => .gte,
   1062                         .lte => .gt,
   1063                         .eq => .neq,
   1064                     },
   1065                 };
   1066                 break :result r;
   1067             },
   1068             .compare_flags_signed => |op| {
   1069                 const r = MCValue{
   1070                     .compare_flags_signed = switch (op) {
   1071                         .gte => .lt,
   1072                         .gt => .lte,
   1073                         .neq => .eq,
   1074                         .lt => .gte,
   1075                         .lte => .gt,
   1076                         .eq => .neq,
   1077                     },
   1078                 };
   1079                 break :result r;
   1080             },
   1081             else => {
   1082                 switch (operand_ty.zigTypeTag()) {
   1083                     .Bool => {
   1084                         // TODO convert this to mvn + and
   1085                         const op_reg = switch (operand) {
   1086                             .register => |r| r,
   1087                             else => try self.copyToTmpRegister(operand_ty, operand),
   1088                         };
   1089                         self.register_manager.freezeRegs(&.{op_reg});
   1090                         defer self.register_manager.unfreezeRegs(&.{op_reg});
   1091 
   1092                         const dest_reg = blk: {
   1093                             if (operand == .register and self.reuseOperand(inst, ty_op.operand, 0, operand)) {
   1094                                 break :blk op_reg;
   1095                             }
   1096 
   1097                             const raw_reg = try self.register_manager.allocReg(null);
   1098                             break :blk raw_reg.to32();
   1099                         };
   1100 
   1101                         _ = try self.addInst(.{
   1102                             .tag = .eor_immediate,
   1103                             .data = .{ .rr_bitmask = .{
   1104                                 .rd = dest_reg,
   1105                                 .rn = op_reg,
   1106                                 .imms = 0b000000,
   1107                                 .immr = 0b000000,
   1108                                 .n = 0b0,
   1109                             } },
   1110                         });
   1111 
   1112                         break :result MCValue{ .register = dest_reg };
   1113                     },
   1114                     .Vector => return self.fail("TODO bitwise not for vectors", .{}),
   1115                     .Int => {
   1116                         const int_info = operand_ty.intInfo(self.target.*);
   1117                         if (int_info.bits <= 64) {
   1118                             const op_reg = switch (operand) {
   1119                                 .register => |r| r,
   1120                                 else => try self.copyToTmpRegister(operand_ty, operand),
   1121                             };
   1122                             self.register_manager.freezeRegs(&.{op_reg});
   1123                             defer self.register_manager.unfreezeRegs(&.{op_reg});
   1124 
   1125                             const dest_reg = blk: {
   1126                                 if (operand == .register and self.reuseOperand(inst, ty_op.operand, 0, operand)) {
   1127                                     break :blk op_reg;
   1128                                 }
   1129 
   1130                                 const raw_reg = try self.register_manager.allocReg(null);
   1131                                 break :blk registerAlias(raw_reg, operand_ty.abiSize(self.target.*));
   1132                             };
   1133 
   1134                             _ = try self.addInst(.{
   1135                                 .tag = .mvn,
   1136                                 .data = .{ .rr_imm6_logical_shift = .{
   1137                                     .rd = dest_reg,
   1138                                     .rm = op_reg,
   1139                                     .imm6 = 0,
   1140                                     .shift = .lsl,
   1141                                 } },
   1142                             });
   1143 
   1144                             try self.truncRegister(dest_reg, dest_reg, int_info.signedness, int_info.bits);
   1145 
   1146                             break :result MCValue{ .register = dest_reg };
   1147                         } else {
   1148                             return self.fail("TODO AArch64 not on integers > u64/i64", .{});
   1149                         }
   1150                     },
   1151                     else => unreachable,
   1152                 }
   1153             },
   1154         }
   1155     };
   1156     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1157 }
   1158 
   1159 fn airMin(self: *Self, inst: Air.Inst.Index) !void {
   1160     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1161     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement min for {}", .{self.target.cpu.arch});
   1162     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1163 }
   1164 
   1165 fn airMax(self: *Self, inst: Air.Inst.Index) !void {
   1166     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1167     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement max for {}", .{self.target.cpu.arch});
   1168     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1169 }
   1170 
   1171 fn airSlice(self: *Self, inst: Air.Inst.Index) !void {
   1172     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   1173     const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
   1174     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   1175         const ptr = try self.resolveInst(bin_op.lhs);
   1176         const ptr_ty = self.air.typeOf(bin_op.lhs);
   1177         const len = try self.resolveInst(bin_op.rhs);
   1178         const len_ty = self.air.typeOf(bin_op.rhs);
   1179 
   1180         const ptr_bits = self.target.cpu.arch.ptrBitWidth();
   1181         const ptr_bytes = @divExact(ptr_bits, 8);
   1182 
   1183         const stack_offset = try self.allocMem(inst, ptr_bytes * 2, ptr_bytes * 2);
   1184         try self.genSetStack(ptr_ty, stack_offset, ptr);
   1185         try self.genSetStack(len_ty, stack_offset - ptr_bytes, len);
   1186         break :result MCValue{ .stack_offset = stack_offset };
   1187     };
   1188     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1189 }
   1190 
   1191 /// Don't call this function directly. Use binOp instead.
   1192 ///
   1193 /// Calling this function signals an intention to generate a Mir
   1194 /// instruction of the form
   1195 ///
   1196 ///     op dest, lhs, rhs
   1197 ///
   1198 /// Asserts that generating an instruction of that form is possible.
   1199 fn binOpRegister(
   1200     self: *Self,
   1201     mir_tag: Mir.Inst.Tag,
   1202     maybe_inst: ?Air.Inst.Index,
   1203     lhs: MCValue,
   1204     rhs: MCValue,
   1205     lhs_ty: Type,
   1206     rhs_ty: Type,
   1207 ) !MCValue {
   1208     const lhs_is_register = lhs == .register;
   1209     const rhs_is_register = rhs == .register;
   1210 
   1211     if (lhs_is_register) self.register_manager.freezeRegs(&.{lhs.register});
   1212     if (rhs_is_register) self.register_manager.freezeRegs(&.{rhs.register});
   1213 
   1214     const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
   1215 
   1216     const lhs_reg = if (lhs_is_register) lhs.register else blk: {
   1217         const track_inst: ?Air.Inst.Index = if (maybe_inst) |inst| inst: {
   1218             const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1219             break :inst Air.refToIndex(bin_op.lhs).?;
   1220         } else null;
   1221 
   1222         const raw_reg = try self.register_manager.allocReg(track_inst);
   1223         const reg = registerAlias(raw_reg, lhs_ty.abiSize(self.target.*));
   1224         self.register_manager.freezeRegs(&.{reg});
   1225 
   1226         if (track_inst) |inst| branch.inst_table.putAssumeCapacity(inst, .{ .register = reg });
   1227 
   1228         break :blk reg;
   1229     };
   1230     defer self.register_manager.unfreezeRegs(&.{lhs_reg});
   1231 
   1232     const rhs_reg = if (rhs_is_register) rhs.register else blk: {
   1233         const track_inst: ?Air.Inst.Index = if (maybe_inst) |inst| inst: {
   1234             const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1235             break :inst Air.refToIndex(bin_op.rhs).?;
   1236         } else null;
   1237 
   1238         const raw_reg = try self.register_manager.allocReg(track_inst);
   1239         const reg = registerAlias(raw_reg, rhs_ty.abiAlignment(self.target.*));
   1240         self.register_manager.freezeRegs(&.{reg});
   1241 
   1242         if (track_inst) |inst| branch.inst_table.putAssumeCapacity(inst, .{ .register = reg });
   1243 
   1244         break :blk reg;
   1245     };
   1246     defer self.register_manager.unfreezeRegs(&.{rhs_reg});
   1247 
   1248     const dest_reg = if (maybe_inst) |inst| blk: {
   1249         const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1250 
   1251         if (lhs_is_register and self.reuseOperand(inst, bin_op.lhs, 0, lhs)) {
   1252             break :blk lhs_reg;
   1253         } else if (rhs_is_register and self.reuseOperand(inst, bin_op.rhs, 1, rhs)) {
   1254             break :blk rhs_reg;
   1255         } else {
   1256             const raw_reg = try self.register_manager.allocReg(inst);
   1257             break :blk registerAlias(raw_reg, lhs_ty.abiSize(self.target.*));
   1258         }
   1259     } else try self.register_manager.allocReg(null);
   1260 
   1261     if (!lhs_is_register) try self.genSetReg(lhs_ty, lhs_reg, lhs);
   1262     if (!rhs_is_register) try self.genSetReg(rhs_ty, rhs_reg, rhs);
   1263 
   1264     const mir_data: Mir.Inst.Data = switch (mir_tag) {
   1265         .add_shifted_register,
   1266         .sub_shifted_register,
   1267         => .{ .rrr_imm6_shift = .{
   1268             .rd = dest_reg,
   1269             .rn = lhs_reg,
   1270             .rm = rhs_reg,
   1271             .imm6 = 0,
   1272             .shift = .lsl,
   1273         } },
   1274         .cmp_shifted_register => .{ .rr_imm6_shift = .{
   1275             .rn = lhs_reg,
   1276             .rm = rhs_reg,
   1277             .imm6 = 0,
   1278             .shift = .lsl,
   1279         } },
   1280         .mul,
   1281         .lsl_register,
   1282         .asr_register,
   1283         .lsr_register,
   1284         => .{ .rrr = .{
   1285             .rd = dest_reg,
   1286             .rn = lhs_reg,
   1287             .rm = rhs_reg,
   1288         } },
   1289         .and_shifted_register,
   1290         .orr_shifted_register,
   1291         .eor_shifted_register,
   1292         => .{ .rrr_imm6_logical_shift = .{
   1293             .rd = dest_reg,
   1294             .rn = lhs_reg,
   1295             .rm = rhs_reg,
   1296             .imm6 = 0,
   1297             .shift = .lsl,
   1298         } },
   1299         else => unreachable,
   1300     };
   1301 
   1302     _ = try self.addInst(.{
   1303         .tag = mir_tag,
   1304         .data = mir_data,
   1305     });
   1306 
   1307     return MCValue{ .register = dest_reg };
   1308 }
   1309 
   1310 /// Don't call this function directly. Use binOp instead.
   1311 ///
   1312 /// Calling this function signals an intention to generate a Mir
   1313 /// instruction of the form
   1314 ///
   1315 ///     op dest, lhs, #rhs_imm
   1316 ///
   1317 /// Set lhs_and_rhs_swapped to true iff inst.bin_op.lhs corresponds to
   1318 /// rhs and vice versa. This parameter is only used when maybe_inst !=
   1319 /// null.
   1320 ///
   1321 /// Asserts that generating an instruction of that form is possible.
   1322 fn binOpImmediate(
   1323     self: *Self,
   1324     mir_tag: Mir.Inst.Tag,
   1325     maybe_inst: ?Air.Inst.Index,
   1326     lhs: MCValue,
   1327     rhs: MCValue,
   1328     lhs_ty: Type,
   1329     lhs_and_rhs_swapped: bool,
   1330 ) !MCValue {
   1331     const lhs_is_register = lhs == .register;
   1332 
   1333     if (lhs_is_register) self.register_manager.freezeRegs(&.{lhs.register});
   1334 
   1335     const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
   1336 
   1337     const lhs_reg = if (lhs_is_register) lhs.register else blk: {
   1338         const track_inst: ?Air.Inst.Index = if (maybe_inst) |inst| inst: {
   1339             const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1340             break :inst Air.refToIndex(
   1341                 if (lhs_and_rhs_swapped) bin_op.rhs else bin_op.lhs,
   1342             ).?;
   1343         } else null;
   1344 
   1345         const raw_reg = try self.register_manager.allocReg(track_inst);
   1346         const reg = registerAlias(raw_reg, lhs_ty.abiSize(self.target.*));
   1347         self.register_manager.freezeRegs(&.{reg});
   1348 
   1349         if (track_inst) |inst| branch.inst_table.putAssumeCapacity(inst, .{ .register = reg });
   1350 
   1351         break :blk reg;
   1352     };
   1353     defer self.register_manager.unfreezeRegs(&.{lhs_reg});
   1354 
   1355     const dest_reg = switch (mir_tag) {
   1356         .cmp_immediate => undefined, // cmp has no destination register
   1357         else => if (maybe_inst) |inst| blk: {
   1358             const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1359 
   1360             if (lhs_is_register and self.reuseOperand(
   1361                 inst,
   1362                 if (lhs_and_rhs_swapped) bin_op.rhs else bin_op.lhs,
   1363                 if (lhs_and_rhs_swapped) 1 else 0,
   1364                 lhs,
   1365             )) {
   1366                 break :blk lhs_reg;
   1367             } else {
   1368                 const raw_reg = try self.register_manager.allocReg(inst);
   1369                 break :blk registerAlias(raw_reg, lhs_ty.abiSize(self.target.*));
   1370             }
   1371         } else try self.register_manager.allocReg(null),
   1372     };
   1373 
   1374     if (!lhs_is_register) try self.genSetReg(lhs_ty, lhs_reg, lhs);
   1375 
   1376     const mir_data: Mir.Inst.Data = switch (mir_tag) {
   1377         .add_immediate,
   1378         .sub_immediate,
   1379         => .{ .rr_imm12_sh = .{
   1380             .rd = dest_reg,
   1381             .rn = lhs_reg,
   1382             .imm12 = @intCast(u12, rhs.immediate),
   1383         } },
   1384         .lsl_immediate,
   1385         .asr_immediate,
   1386         .lsr_immediate,
   1387         => .{ .rr_shift = .{
   1388             .rd = dest_reg,
   1389             .rn = lhs_reg,
   1390             .shift = @intCast(u6, rhs.immediate),
   1391         } },
   1392         .cmp_immediate => .{ .r_imm12_sh = .{
   1393             .rn = lhs_reg,
   1394             .imm12 = @intCast(u12, rhs.immediate),
   1395         } },
   1396         else => unreachable,
   1397     };
   1398 
   1399     _ = try self.addInst(.{
   1400         .tag = mir_tag,
   1401         .data = mir_data,
   1402     });
   1403 
   1404     return MCValue{ .register = dest_reg };
   1405 }
   1406 
   1407 /// For all your binary operation needs, this function will generate
   1408 /// the corresponding Mir instruction(s). Returns the location of the
   1409 /// result.
   1410 ///
   1411 /// If the binary operation itself happens to be an Air instruction,
   1412 /// pass the corresponding index in the inst parameter. That helps
   1413 /// this function do stuff like reusing operands.
   1414 ///
   1415 /// This function does not do any lowering to Mir itself, but instead
   1416 /// looks at the lhs and rhs and determines which kind of lowering
   1417 /// would be best suitable and then delegates the lowering to other
   1418 /// functions.
   1419 fn binOp(
   1420     self: *Self,
   1421     tag: Air.Inst.Tag,
   1422     maybe_inst: ?Air.Inst.Index,
   1423     lhs: MCValue,
   1424     rhs: MCValue,
   1425     lhs_ty: Type,
   1426     rhs_ty: Type,
   1427 ) InnerError!MCValue {
   1428     const mod = self.bin_file.options.module.?;
   1429     switch (tag) {
   1430         .add,
   1431         .sub,
   1432         .cmp_eq,
   1433         => {
   1434             switch (lhs_ty.zigTypeTag()) {
   1435                 .Float => return self.fail("TODO binary operations on floats", .{}),
   1436                 .Vector => return self.fail("TODO binary operations on vectors", .{}),
   1437                 .Int => {
   1438                     assert(lhs_ty.eql(rhs_ty, mod));
   1439                     const int_info = lhs_ty.intInfo(self.target.*);
   1440                     if (int_info.bits <= 64) {
   1441                         // Only say yes if the operation is
   1442                         // commutative, i.e. we can swap both of the
   1443                         // operands
   1444                         const lhs_immediate_ok = switch (tag) {
   1445                             .add => lhs == .immediate and lhs.immediate <= std.math.maxInt(u12),
   1446                             .sub, .cmp_eq => false,
   1447                             else => unreachable,
   1448                         };
   1449                         const rhs_immediate_ok = switch (tag) {
   1450                             .add,
   1451                             .sub,
   1452                             .cmp_eq,
   1453                             => rhs == .immediate and rhs.immediate <= std.math.maxInt(u12),
   1454                             else => unreachable,
   1455                         };
   1456 
   1457                         const mir_tag_register: Mir.Inst.Tag = switch (tag) {
   1458                             .add => .add_shifted_register,
   1459                             .sub => .sub_shifted_register,
   1460                             .cmp_eq => .cmp_shifted_register,
   1461                             else => unreachable,
   1462                         };
   1463                         const mir_tag_immediate: Mir.Inst.Tag = switch (tag) {
   1464                             .add => .add_immediate,
   1465                             .sub => .sub_immediate,
   1466                             .cmp_eq => .cmp_immediate,
   1467                             else => unreachable,
   1468                         };
   1469 
   1470                         if (rhs_immediate_ok) {
   1471                             return try self.binOpImmediate(mir_tag_immediate, maybe_inst, lhs, rhs, lhs_ty, false);
   1472                         } else if (lhs_immediate_ok) {
   1473                             // swap lhs and rhs
   1474                             return try self.binOpImmediate(mir_tag_immediate, maybe_inst, rhs, lhs, rhs_ty, true);
   1475                         } else {
   1476                             return try self.binOpRegister(mir_tag_register, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
   1477                         }
   1478                     } else {
   1479                         return self.fail("TODO binary operations on int with bits > 64", .{});
   1480                     }
   1481                 },
   1482                 else => unreachable,
   1483             }
   1484         },
   1485         .mul => {
   1486             switch (lhs_ty.zigTypeTag()) {
   1487                 .Vector => return self.fail("TODO binary operations on vectors", .{}),
   1488                 .Int => {
   1489                     assert(lhs_ty.eql(rhs_ty, mod));
   1490                     const int_info = lhs_ty.intInfo(self.target.*);
   1491                     if (int_info.bits <= 64) {
   1492                         // TODO add optimisations for multiplication
   1493                         // with immediates, for example a * 2 can be
   1494                         // lowered to a << 1
   1495                         return try self.binOpRegister(.mul, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
   1496                     } else {
   1497                         return self.fail("TODO binary operations on int with bits > 64", .{});
   1498                     }
   1499                 },
   1500                 else => unreachable,
   1501             }
   1502         },
   1503         .addwrap,
   1504         .subwrap,
   1505         .mulwrap,
   1506         => {
   1507             const base_tag: Air.Inst.Tag = switch (tag) {
   1508                 .addwrap => .add,
   1509                 .subwrap => .sub,
   1510                 .mulwrap => .mul,
   1511                 else => unreachable,
   1512             };
   1513 
   1514             // Generate an add/sub/mul
   1515             const result = try self.binOp(base_tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
   1516 
   1517             // Truncate if necessary
   1518             switch (lhs_ty.zigTypeTag()) {
   1519                 .Vector => return self.fail("TODO binary operations on vectors", .{}),
   1520                 .Int => {
   1521                     const int_info = lhs_ty.intInfo(self.target.*);
   1522                     if (int_info.bits <= 64) {
   1523                         const result_reg = result.register;
   1524                         try self.truncRegister(result_reg, result_reg, int_info.signedness, int_info.bits);
   1525                         return result;
   1526                     } else {
   1527                         return self.fail("TODO binary operations on integers > u64/i64", .{});
   1528                     }
   1529                 },
   1530                 else => unreachable,
   1531             }
   1532         },
   1533         .bit_and,
   1534         .bit_or,
   1535         .xor,
   1536         => {
   1537             switch (lhs_ty.zigTypeTag()) {
   1538                 .Vector => return self.fail("TODO binary operations on vectors", .{}),
   1539                 .Int => {
   1540                     assert(lhs_ty.eql(rhs_ty, mod));
   1541                     const int_info = lhs_ty.intInfo(self.target.*);
   1542                     if (int_info.bits <= 64) {
   1543                         // TODO implement bitwise operations with immediates
   1544                         const mir_tag: Mir.Inst.Tag = switch (tag) {
   1545                             .bit_and => .and_shifted_register,
   1546                             .bit_or => .orr_shifted_register,
   1547                             .xor => .eor_shifted_register,
   1548                             else => unreachable,
   1549                         };
   1550 
   1551                         return try self.binOpRegister(mir_tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
   1552                     } else {
   1553                         return self.fail("TODO binary operations on int with bits > 64", .{});
   1554                     }
   1555                 },
   1556                 else => unreachable,
   1557             }
   1558         },
   1559         .shl_exact,
   1560         .shr_exact,
   1561         => {
   1562             switch (lhs_ty.zigTypeTag()) {
   1563                 .Vector => return self.fail("TODO binary operations on vectors", .{}),
   1564                 .Int => {
   1565                     const int_info = lhs_ty.intInfo(self.target.*);
   1566                     if (int_info.bits <= 64) {
   1567                         const rhs_immediate_ok = rhs == .immediate;
   1568 
   1569                         const mir_tag_register: Mir.Inst.Tag = switch (tag) {
   1570                             .shl_exact => .lsl_register,
   1571                             .shr_exact => switch (int_info.signedness) {
   1572                                 .signed => Mir.Inst.Tag.asr_register,
   1573                                 .unsigned => Mir.Inst.Tag.lsr_register,
   1574                             },
   1575                             else => unreachable,
   1576                         };
   1577                         const mir_tag_immediate: Mir.Inst.Tag = switch (tag) {
   1578                             .shl_exact => .lsl_immediate,
   1579                             .shr_exact => switch (int_info.signedness) {
   1580                                 .signed => Mir.Inst.Tag.asr_immediate,
   1581                                 .unsigned => Mir.Inst.Tag.lsr_immediate,
   1582                             },
   1583                             else => unreachable,
   1584                         };
   1585 
   1586                         if (rhs_immediate_ok) {
   1587                             return try self.binOpImmediate(mir_tag_immediate, maybe_inst, lhs, rhs, lhs_ty, false);
   1588                         } else {
   1589                             return try self.binOpRegister(mir_tag_register, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
   1590                         }
   1591                     } else {
   1592                         return self.fail("TODO binary operations on int with bits > 64", .{});
   1593                     }
   1594                 },
   1595                 else => unreachable,
   1596             }
   1597         },
   1598         .shl,
   1599         .shr,
   1600         => {
   1601             const base_tag: Air.Inst.Tag = switch (tag) {
   1602                 .shl => .shl_exact,
   1603                 .shr => .shr_exact,
   1604                 else => unreachable,
   1605             };
   1606 
   1607             // Generate a shl_exact/shr_exact
   1608             const result = try self.binOp(base_tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
   1609 
   1610             // Truncate if necessary
   1611             switch (tag) {
   1612                 .shr => return result,
   1613                 .shl => switch (lhs_ty.zigTypeTag()) {
   1614                     .Vector => return self.fail("TODO binary operations on vectors", .{}),
   1615                     .Int => {
   1616                         const int_info = lhs_ty.intInfo(self.target.*);
   1617                         if (int_info.bits <= 64) {
   1618                             const result_reg = result.register;
   1619                             try self.truncRegister(result_reg, result_reg, int_info.signedness, int_info.bits);
   1620                             return result;
   1621                         } else {
   1622                             return self.fail("TODO binary operations on integers > u64/i64", .{});
   1623                         }
   1624                     },
   1625                     else => unreachable,
   1626                 },
   1627                 else => unreachable,
   1628             }
   1629         },
   1630         .bool_and,
   1631         .bool_or,
   1632         => {
   1633             switch (lhs_ty.zigTypeTag()) {
   1634                 .Bool => {
   1635                     assert(lhs != .immediate); // should have been handled by Sema
   1636                     assert(rhs != .immediate); // should have been handled by Sema
   1637 
   1638                     const mir_tag_register: Mir.Inst.Tag = switch (tag) {
   1639                         .bool_and => .and_shifted_register,
   1640                         .bool_or => .orr_shifted_register,
   1641                         else => unreachable,
   1642                     };
   1643 
   1644                     return try self.binOpRegister(mir_tag_register, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
   1645                 },
   1646                 else => unreachable,
   1647             }
   1648         },
   1649         .ptr_add,
   1650         .ptr_sub,
   1651         => {
   1652             switch (lhs_ty.zigTypeTag()) {
   1653                 .Pointer => {
   1654                     const ptr_ty = lhs_ty;
   1655                     const elem_ty = switch (ptr_ty.ptrSize()) {
   1656                         .One => ptr_ty.childType().childType(), // ptr to array, so get array element type
   1657                         else => ptr_ty.childType(),
   1658                     };
   1659                     const elem_size = elem_ty.abiSize(self.target.*);
   1660 
   1661                     if (elem_size == 1) {
   1662                         const base_tag: Mir.Inst.Tag = switch (tag) {
   1663                             .ptr_add => .add_shifted_register,
   1664                             .ptr_sub => .sub_shifted_register,
   1665                             else => unreachable,
   1666                         };
   1667 
   1668                         return try self.binOpRegister(base_tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
   1669                     } else {
   1670                         // convert the offset into a byte offset by
   1671                         // multiplying it with elem_size
   1672                         const offset = try self.binOp(.mul, null, rhs, .{ .immediate = elem_size }, Type.usize, Type.usize);
   1673                         const addr = try self.binOp(tag, null, lhs, offset, Type.initTag(.manyptr_u8), Type.usize);
   1674                         return addr;
   1675                     }
   1676                 },
   1677                 else => unreachable,
   1678             }
   1679         },
   1680         else => unreachable,
   1681     }
   1682 }
   1683 
   1684 fn airBinOp(self: *Self, inst: Air.Inst.Index) !void {
   1685     const tag = self.air.instructions.items(.tag)[inst];
   1686     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1687     const lhs = try self.resolveInst(bin_op.lhs);
   1688     const rhs = try self.resolveInst(bin_op.rhs);
   1689     const lhs_ty = self.air.typeOf(bin_op.lhs);
   1690     const rhs_ty = self.air.typeOf(bin_op.rhs);
   1691 
   1692     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else try self.binOp(tag, inst, lhs, rhs, lhs_ty, rhs_ty);
   1693     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1694 }
   1695 
   1696 fn airAddSat(self: *Self, inst: Air.Inst.Index) !void {
   1697     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1698     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement add_sat for {}", .{self.target.cpu.arch});
   1699     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1700 }
   1701 
   1702 fn airSubSat(self: *Self, inst: Air.Inst.Index) !void {
   1703     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1704     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement sub_sat for {}", .{self.target.cpu.arch});
   1705     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1706 }
   1707 
   1708 fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
   1709     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1710     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement mul_sat for {}", .{self.target.cpu.arch});
   1711     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1712 }
   1713 
   1714 fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
   1715     _ = inst;
   1716     return self.fail("TODO implement airAddWithOverflow for {}", .{self.target.cpu.arch});
   1717 }
   1718 
   1719 fn airSubWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
   1720     _ = inst;
   1721     return self.fail("TODO implement airSubWithOverflow for {}", .{self.target.cpu.arch});
   1722 }
   1723 
   1724 fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
   1725     _ = inst;
   1726     return self.fail("TODO implement airMulWithOverflow for {}", .{self.target.cpu.arch});
   1727 }
   1728 
   1729 fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
   1730     _ = inst;
   1731     return self.fail("TODO implement airShlWithOverflow for {}", .{self.target.cpu.arch});
   1732 }
   1733 
   1734 fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
   1735     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1736     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch});
   1737     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1738 }
   1739 
   1740 fn airRem(self: *Self, inst: Air.Inst.Index) !void {
   1741     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1742     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement rem for {}", .{self.target.cpu.arch});
   1743     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1744 }
   1745 
   1746 fn airMod(self: *Self, inst: Air.Inst.Index) !void {
   1747     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1748     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement mod for {}", .{self.target.cpu.arch});
   1749     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1750 }
   1751 
   1752 fn airShlSat(self: *Self, inst: Air.Inst.Index) !void {
   1753     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1754     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement shl_sat for {}", .{self.target.cpu.arch});
   1755     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1756 }
   1757 
   1758 fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) !void {
   1759     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1760     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload for {}", .{self.target.cpu.arch});
   1761     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1762 }
   1763 
   1764 fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void {
   1765     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1766     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr for {}", .{self.target.cpu.arch});
   1767     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1768 }
   1769 
   1770 fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
   1771     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1772     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch});
   1773     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1774 }
   1775 
   1776 fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
   1777     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1778     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   1779         const error_union_ty = self.air.typeOf(ty_op.operand);
   1780         const payload_ty = error_union_ty.errorUnionPayload();
   1781         const mcv = try self.resolveInst(ty_op.operand);
   1782         if (!payload_ty.hasRuntimeBits()) break :result mcv;
   1783 
   1784         return self.fail("TODO implement unwrap error union error for non-empty payloads", .{});
   1785     };
   1786     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1787 }
   1788 
   1789 fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void {
   1790     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1791     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   1792         const error_union_ty = self.air.typeOf(ty_op.operand);
   1793         const payload_ty = error_union_ty.errorUnionPayload();
   1794         if (!payload_ty.hasRuntimeBits()) break :result MCValue.none;
   1795 
   1796         return self.fail("TODO implement unwrap error union payload for non-empty payloads", .{});
   1797     };
   1798     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1799 }
   1800 
   1801 // *(E!T) -> E
   1802 fn airUnwrapErrErrPtr(self: *Self, inst: Air.Inst.Index) !void {
   1803     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1804     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error ptr for {}", .{self.target.cpu.arch});
   1805     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1806 }
   1807 
   1808 // *(E!T) -> *T
   1809 fn airUnwrapErrPayloadPtr(self: *Self, inst: Air.Inst.Index) !void {
   1810     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1811     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union payload ptr for {}", .{self.target.cpu.arch});
   1812     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1813 }
   1814 
   1815 fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
   1816     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1817     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .errunion_payload_ptr_set for {}", .{self.target.cpu.arch});
   1818     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1819 }
   1820 
   1821 fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void {
   1822     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1823     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   1824         const optional_ty = self.air.typeOfIndex(inst);
   1825 
   1826         // Optional with a zero-bit payload type is just a boolean true
   1827         if (optional_ty.abiSize(self.target.*) == 1)
   1828             break :result MCValue{ .immediate = 1 };
   1829 
   1830         return self.fail("TODO implement wrap optional for {}", .{self.target.cpu.arch});
   1831     };
   1832     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1833 }
   1834 
   1835 /// T to E!T
   1836 fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void {
   1837     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1838     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement wrap errunion payload for {}", .{self.target.cpu.arch});
   1839     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1840 }
   1841 
   1842 /// E to E!T
   1843 fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void {
   1844     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1845     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   1846         const error_union_ty = self.air.getRefType(ty_op.ty);
   1847         const payload_ty = error_union_ty.errorUnionPayload();
   1848         const mcv = try self.resolveInst(ty_op.operand);
   1849         if (!payload_ty.hasRuntimeBits()) break :result mcv;
   1850 
   1851         return self.fail("TODO implement wrap errunion error for non-empty payloads", .{});
   1852     };
   1853     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1854 }
   1855 
   1856 fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void {
   1857     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1858     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   1859         const mcv = try self.resolveInst(ty_op.operand);
   1860         switch (mcv) {
   1861             .dead, .unreach, .none => unreachable,
   1862             .register => unreachable, // a slice doesn't fit in one register
   1863             .stack_offset => |off| {
   1864                 break :result MCValue{ .stack_offset = off };
   1865             },
   1866             .memory => |addr| {
   1867                 break :result MCValue{ .memory = addr };
   1868             },
   1869             else => return self.fail("TODO implement slice_len for {}", .{mcv}),
   1870         }
   1871     };
   1872     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1873 }
   1874 
   1875 fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void {
   1876     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1877     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   1878         const ptr_bits = self.target.cpu.arch.ptrBitWidth();
   1879         const ptr_bytes = @divExact(ptr_bits, 8);
   1880         const mcv = try self.resolveInst(ty_op.operand);
   1881         switch (mcv) {
   1882             .dead, .unreach, .none => unreachable,
   1883             .register => unreachable, // a slice doesn't fit in one register
   1884             .stack_offset => |off| {
   1885                 break :result MCValue{ .stack_offset = off - ptr_bytes };
   1886             },
   1887             .memory => |addr| {
   1888                 break :result MCValue{ .memory = addr + ptr_bytes };
   1889             },
   1890             else => return self.fail("TODO implement slice_len for {}", .{mcv}),
   1891         }
   1892     };
   1893     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1894 }
   1895 
   1896 fn airPtrSliceLenPtr(self: *Self, inst: Air.Inst.Index) !void {
   1897     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1898     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   1899         const ptr_bits = self.target.cpu.arch.ptrBitWidth();
   1900         const ptr_bytes = @divExact(ptr_bits, 8);
   1901         const mcv = try self.resolveInst(ty_op.operand);
   1902         switch (mcv) {
   1903             .dead, .unreach, .none => unreachable,
   1904             .ptr_stack_offset => |off| {
   1905                 break :result MCValue{ .ptr_stack_offset = off - ptr_bytes };
   1906             },
   1907             else => return self.fail("TODO implement ptr_slice_len_ptr for {}", .{mcv}),
   1908         }
   1909     };
   1910     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1911 }
   1912 
   1913 fn airPtrSlicePtrPtr(self: *Self, inst: Air.Inst.Index) !void {
   1914     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   1915     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   1916         const mcv = try self.resolveInst(ty_op.operand);
   1917         switch (mcv) {
   1918             .dead, .unreach, .none => unreachable,
   1919             .ptr_stack_offset => |off| {
   1920                 break :result MCValue{ .ptr_stack_offset = off };
   1921             },
   1922             else => return self.fail("TODO implement ptr_slice_len_ptr for {}", .{mcv}),
   1923         }
   1924     };
   1925     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   1926 }
   1927 
   1928 fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void {
   1929     const is_volatile = false; // TODO
   1930     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1931 
   1932     if (!is_volatile and self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ bin_op.lhs, bin_op.rhs, .none });
   1933     const result: MCValue = result: {
   1934         const slice_mcv = try self.resolveInst(bin_op.lhs);
   1935 
   1936         // TODO optimize for the case where the index is a constant,
   1937         // i.e. index_mcv == .immediate
   1938         const index_mcv = try self.resolveInst(bin_op.rhs);
   1939         const index_is_register = index_mcv == .register;
   1940 
   1941         const slice_ty = self.air.typeOf(bin_op.lhs);
   1942         const elem_ty = slice_ty.childType();
   1943         const elem_size = elem_ty.abiSize(self.target.*);
   1944 
   1945         var buf: Type.SlicePtrFieldTypeBuffer = undefined;
   1946         const slice_ptr_field_type = slice_ty.slicePtrFieldType(&buf);
   1947 
   1948         if (index_is_register) self.register_manager.freezeRegs(&.{index_mcv.register});
   1949         defer if (index_is_register) self.register_manager.unfreezeRegs(&.{index_mcv.register});
   1950 
   1951         const base_mcv: MCValue = switch (slice_mcv) {
   1952             .stack_offset => |off| .{ .register = try self.copyToTmpRegister(slice_ptr_field_type, .{ .stack_offset = off }) },
   1953             else => return self.fail("TODO slice_elem_val when slice is {}", .{slice_mcv}),
   1954         };
   1955         self.register_manager.freezeRegs(&.{base_mcv.register});
   1956 
   1957         switch (elem_size) {
   1958             else => {
   1959                 const dest = try self.allocRegOrMem(inst, true);
   1960                 const addr = try self.binOp(.ptr_add, null, base_mcv, index_mcv, slice_ty, Type.usize);
   1961                 try self.load(dest, addr, slice_ptr_field_type);
   1962 
   1963                 break :result dest;
   1964             },
   1965         }
   1966     };
   1967     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1968 }
   1969 
   1970 fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) !void {
   1971     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   1972     const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
   1973     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement slice_elem_ptr for {}", .{self.target.cpu.arch});
   1974     return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
   1975 }
   1976 
   1977 fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void {
   1978     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1979     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement array_elem_val for {}", .{self.target.cpu.arch});
   1980     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1981 }
   1982 
   1983 fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void {
   1984     const is_volatile = false; // TODO
   1985     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1986     const result: MCValue = if (!is_volatile and self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement ptr_elem_val for {}", .{self.target.cpu.arch});
   1987     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   1988 }
   1989 
   1990 fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void {
   1991     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   1992     const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
   1993     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement ptr_elem_ptr for {}", .{self.target.cpu.arch});
   1994     return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
   1995 }
   1996 
   1997 fn airSetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
   1998     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   1999     _ = bin_op;
   2000     return self.fail("TODO implement airSetUnionTag for {}", .{self.target.cpu.arch});
   2001 }
   2002 
   2003 fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
   2004     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   2005     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airGetUnionTag for {}", .{self.target.cpu.arch});
   2006     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   2007 }
   2008 
   2009 fn airClz(self: *Self, inst: Air.Inst.Index) !void {
   2010     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   2011     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airClz for {}", .{self.target.cpu.arch});
   2012     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   2013 }
   2014 
   2015 fn airCtz(self: *Self, inst: Air.Inst.Index) !void {
   2016     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   2017     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCtz for {}", .{self.target.cpu.arch});
   2018     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   2019 }
   2020 
   2021 fn airPopcount(self: *Self, inst: Air.Inst.Index) !void {
   2022     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   2023     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airPopcount for {}", .{self.target.cpu.arch});
   2024     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   2025 }
   2026 
   2027 fn airByteSwap(self: *Self, inst: Air.Inst.Index) !void {
   2028     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   2029     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airByteSwap for {}", .{self.target.cpu.arch});
   2030     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   2031 }
   2032 
   2033 fn airBitReverse(self: *Self, inst: Air.Inst.Index) !void {
   2034     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   2035     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airBitReverse for {}", .{self.target.cpu.arch});
   2036     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   2037 }
   2038 
   2039 fn airUnaryMath(self: *Self, inst: Air.Inst.Index) !void {
   2040     const un_op = self.air.instructions.items(.data)[inst].un_op;
   2041     const result: MCValue = if (self.liveness.isUnused(inst))
   2042         .dead
   2043     else
   2044         return self.fail("TODO implement airUnaryMath for {}", .{self.target.cpu.arch});
   2045     return self.finishAir(inst, result, .{ un_op, .none, .none });
   2046 }
   2047 
   2048 fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
   2049     if (!self.liveness.operandDies(inst, op_index))
   2050         return false;
   2051 
   2052     switch (mcv) {
   2053         .register => |reg| {
   2054             // If it's in the registers table, need to associate the register with the
   2055             // new instruction.
   2056             if (RegisterManager.indexOfRegIntoTracked(reg)) |index| {
   2057                 if (!self.register_manager.isRegFree(reg)) {
   2058                     self.register_manager.registers[index] = inst;
   2059                 }
   2060             }
   2061             log.debug("%{d} => {} (reused)", .{ inst, reg });
   2062         },
   2063         .stack_offset => |off| {
   2064             log.debug("%{d} => stack offset {d} (reused)", .{ inst, off });
   2065         },
   2066         else => return false,
   2067     }
   2068 
   2069     // Prevent the operand deaths processing code from deallocating it.
   2070     self.liveness.clearOperandDeath(inst, op_index);
   2071 
   2072     // That makes us responsible for doing the rest of the stuff that processDeath would have done.
   2073     const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
   2074     branch.inst_table.putAssumeCapacity(Air.refToIndex(operand).?, .dead);
   2075 
   2076     return true;
   2077 }
   2078 
   2079 fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!void {
   2080     const elem_ty = ptr_ty.elemType();
   2081     const elem_size = elem_ty.abiSize(self.target.*);
   2082 
   2083     switch (ptr) {
   2084         .none => unreachable,
   2085         .undef => unreachable,
   2086         .unreach => unreachable,
   2087         .dead => unreachable,
   2088         .compare_flags_unsigned => unreachable,
   2089         .compare_flags_signed => unreachable,
   2090         .immediate => |imm| try self.setRegOrMem(elem_ty, dst_mcv, .{ .memory = imm }),
   2091         .ptr_stack_offset => |off| try self.setRegOrMem(elem_ty, dst_mcv, .{ .stack_offset = off }),
   2092         .register => |addr_reg| {
   2093             self.register_manager.freezeRegs(&.{addr_reg});
   2094             defer self.register_manager.unfreezeRegs(&.{addr_reg});
   2095 
   2096             switch (dst_mcv) {
   2097                 .dead => unreachable,
   2098                 .undef => unreachable,
   2099                 .compare_flags_signed, .compare_flags_unsigned => unreachable,
   2100                 .register => |dst_reg| {
   2101                     try self.genLdrRegister(dst_reg, addr_reg, elem_ty);
   2102                 },
   2103                 .stack_offset => |off| {
   2104                     if (elem_size <= 8) {
   2105                         const raw_tmp_reg = try self.register_manager.allocReg(null);
   2106                         const tmp_reg = registerAlias(raw_tmp_reg, elem_size);
   2107                         self.register_manager.freezeRegs(&.{tmp_reg});
   2108                         defer self.register_manager.unfreezeRegs(&.{tmp_reg});
   2109 
   2110                         try self.load(.{ .register = tmp_reg }, ptr, ptr_ty);
   2111                         try self.genSetStack(elem_ty, off, MCValue{ .register = tmp_reg });
   2112                     } else {
   2113                         // TODO optimize the register allocation
   2114                         const regs = try self.register_manager.allocRegs(4, .{ null, null, null, null });
   2115                         self.register_manager.freezeRegs(&regs);
   2116                         defer self.register_manager.unfreezeRegs(&regs);
   2117 
   2118                         const src_reg = addr_reg;
   2119                         const dst_reg = regs[0];
   2120                         const len_reg = regs[1];
   2121                         const count_reg = regs[2];
   2122                         const tmp_reg = regs[3];
   2123 
   2124                         // sub dst_reg, fp, #off
   2125                         try self.genSetReg(ptr_ty, dst_reg, .{ .ptr_stack_offset = off });
   2126 
   2127                         // mov len, #elem_size
   2128                         try self.genSetReg(Type.usize, len_reg, .{ .immediate = elem_size });
   2129 
   2130                         // memcpy(src, dst, len)
   2131                         try self.genInlineMemcpy(src_reg, dst_reg, len_reg, count_reg, tmp_reg);
   2132                     }
   2133                 },
   2134                 else => return self.fail("TODO load from register into {}", .{dst_mcv}),
   2135             }
   2136         },
   2137         .memory,
   2138         .stack_offset,
   2139         .got_load,
   2140         .direct_load,
   2141         => {
   2142             const addr_reg = try self.copyToTmpRegister(ptr_ty, ptr);
   2143             try self.load(dst_mcv, .{ .register = addr_reg }, ptr_ty);
   2144         },
   2145     }
   2146 }
   2147 
   2148 fn genInlineMemcpy(
   2149     self: *Self,
   2150     src: Register,
   2151     dst: Register,
   2152     len: Register,
   2153     count: Register,
   2154     tmp: Register,
   2155 ) !void {
   2156     // movz count, #0
   2157     _ = try self.addInst(.{
   2158         .tag = .movz,
   2159         .data = .{ .r_imm16_sh = .{
   2160             .rd = count,
   2161             .imm16 = 0,
   2162         } },
   2163     });
   2164 
   2165     // loop:
   2166     // cmp count, len
   2167     _ = try self.addInst(.{
   2168         .tag = .cmp_shifted_register,
   2169         .data = .{ .rr_imm6_shift = .{
   2170             .rn = count,
   2171             .rm = len,
   2172             .imm6 = 0,
   2173             .shift = .lsl,
   2174         } },
   2175     });
   2176 
   2177     // bge end
   2178     _ = try self.addInst(.{
   2179         .tag = .b_cond,
   2180         .data = .{ .inst_cond = .{
   2181             .inst = @intCast(u32, self.mir_instructions.len + 5),
   2182             .cond = .ge,
   2183         } },
   2184     });
   2185 
   2186     // ldrb tmp, [src, count]
   2187     _ = try self.addInst(.{
   2188         .tag = .ldrb_register,
   2189         .data = .{ .load_store_register_register = .{
   2190             .rt = tmp,
   2191             .rn = src,
   2192             .offset = Instruction.LoadStoreOffset.reg(count).register,
   2193         } },
   2194     });
   2195 
   2196     // strb tmp, [dest, count]
   2197     _ = try self.addInst(.{
   2198         .tag = .strb_register,
   2199         .data = .{ .load_store_register_register = .{
   2200             .rt = tmp,
   2201             .rn = dst,
   2202             .offset = Instruction.LoadStoreOffset.reg(count).register,
   2203         } },
   2204     });
   2205 
   2206     // add count, count, #1
   2207     _ = try self.addInst(.{
   2208         .tag = .add_immediate,
   2209         .data = .{ .rr_imm12_sh = .{
   2210             .rd = count,
   2211             .rn = count,
   2212             .imm12 = 1,
   2213         } },
   2214     });
   2215 
   2216     // b loop
   2217     _ = try self.addInst(.{
   2218         .tag = .b,
   2219         .data = .{ .inst = @intCast(u32, self.mir_instructions.len - 5) },
   2220     });
   2221 
   2222     // end:
   2223 }
   2224 
   2225 fn airLoad(self: *Self, inst: Air.Inst.Index) !void {
   2226     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   2227     const elem_ty = self.air.typeOfIndex(inst);
   2228     const elem_size = elem_ty.abiSize(self.target.*);
   2229     const result: MCValue = result: {
   2230         if (!elem_ty.hasRuntimeBits())
   2231             break :result MCValue.none;
   2232 
   2233         const ptr = try self.resolveInst(ty_op.operand);
   2234         const is_volatile = self.air.typeOf(ty_op.operand).isVolatilePtr();
   2235         if (self.liveness.isUnused(inst) and !is_volatile)
   2236             break :result MCValue.dead;
   2237 
   2238         const dst_mcv: MCValue = blk: {
   2239             if (elem_size <= 8 and self.reuseOperand(inst, ty_op.operand, 0, ptr)) {
   2240                 // The MCValue that holds the pointer can be re-used as the value.
   2241                 break :blk switch (ptr) {
   2242                     .register => |r| MCValue{ .register = registerAlias(r, elem_size) },
   2243                     else => ptr,
   2244                 };
   2245             } else {
   2246                 break :blk try self.allocRegOrMem(inst, true);
   2247             }
   2248         };
   2249         try self.load(dst_mcv, ptr, self.air.typeOf(ty_op.operand));
   2250         break :result dst_mcv;
   2251     };
   2252     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   2253 }
   2254 
   2255 fn genLdrRegister(self: *Self, value_reg: Register, addr_reg: Register, ty: Type) !void {
   2256     const abi_size = ty.abiSize(self.target.*);
   2257 
   2258     const tag: Mir.Inst.Tag = switch (abi_size) {
   2259         1 => if (ty.isSignedInt()) Mir.Inst.Tag.ldrsb_immediate else .ldrb_immediate,
   2260         2 => if (ty.isSignedInt()) Mir.Inst.Tag.ldrsh_immediate else .ldrh_immediate,
   2261         4 => .ldr_immediate,
   2262         8 => .ldr_immediate,
   2263         3, 5, 6, 7 => return self.fail("TODO: genLdrRegister for more abi_sizes", .{}),
   2264         else => unreachable,
   2265     };
   2266 
   2267     _ = try self.addInst(.{
   2268         .tag = tag,
   2269         .data = .{ .load_store_register_immediate = .{
   2270             .rt = value_reg,
   2271             .rn = addr_reg,
   2272             .offset = Instruction.LoadStoreOffset.none.immediate,
   2273         } },
   2274     });
   2275 }
   2276 
   2277 fn genStrRegister(self: *Self, value_reg: Register, addr_reg: Register, ty: Type) !void {
   2278     const abi_size = ty.abiSize(self.target.*);
   2279 
   2280     const tag: Mir.Inst.Tag = switch (abi_size) {
   2281         1 => .strb_immediate,
   2282         2 => .strh_immediate,
   2283         4, 8 => .str_immediate,
   2284         3, 5, 6, 7 => return self.fail("TODO: genStrRegister for more abi_sizes", .{}),
   2285         else => unreachable,
   2286     };
   2287 
   2288     _ = try self.addInst(.{
   2289         .tag = tag,
   2290         .data = .{ .load_store_register_immediate = .{
   2291             .rt = value_reg,
   2292             .rn = addr_reg,
   2293             .offset = Instruction.LoadStoreOffset.none.immediate,
   2294         } },
   2295     });
   2296 }
   2297 
   2298 fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type) InnerError!void {
   2299     const abi_size = value_ty.abiSize(self.target.*);
   2300 
   2301     switch (ptr) {
   2302         .none => unreachable,
   2303         .undef => unreachable,
   2304         .unreach => unreachable,
   2305         .dead => unreachable,
   2306         .compare_flags_unsigned => unreachable,
   2307         .compare_flags_signed => unreachable,
   2308         .immediate => |imm| {
   2309             try self.setRegOrMem(value_ty, .{ .memory = imm }, value);
   2310         },
   2311         .ptr_stack_offset => |off| {
   2312             try self.genSetStack(value_ty, off, value);
   2313         },
   2314         .register => |addr_reg| {
   2315             self.register_manager.freezeRegs(&.{addr_reg});
   2316             defer self.register_manager.unfreezeRegs(&.{addr_reg});
   2317 
   2318             switch (value) {
   2319                 .register => |value_reg| {
   2320                     try self.genStrRegister(value_reg, addr_reg, value_ty);
   2321                 },
   2322                 else => {
   2323                     if (abi_size <= 8) {
   2324                         const raw_tmp_reg = try self.register_manager.allocReg(null);
   2325                         const tmp_reg = registerAlias(raw_tmp_reg, abi_size);
   2326                         self.register_manager.freezeRegs(&.{tmp_reg});
   2327                         defer self.register_manager.unfreezeRegs(&.{tmp_reg});
   2328 
   2329                         try self.genSetReg(value_ty, tmp_reg, value);
   2330                         try self.store(ptr, .{ .register = tmp_reg }, ptr_ty, value_ty);
   2331                     } else {
   2332                         return self.fail("TODO implement memcpy", .{});
   2333                     }
   2334                 },
   2335             }
   2336         },
   2337         .memory,
   2338         .stack_offset,
   2339         .got_load,
   2340         .direct_load,
   2341         => {
   2342             const addr_reg = try self.copyToTmpRegister(ptr_ty, ptr);
   2343             try self.store(.{ .register = addr_reg }, value, ptr_ty, value_ty);
   2344         },
   2345     }
   2346 }
   2347 
   2348 fn airStore(self: *Self, inst: Air.Inst.Index) !void {
   2349     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   2350     const ptr = try self.resolveInst(bin_op.lhs);
   2351     const value = try self.resolveInst(bin_op.rhs);
   2352     const ptr_ty = self.air.typeOf(bin_op.lhs);
   2353     const value_ty = self.air.typeOf(bin_op.rhs);
   2354 
   2355     try self.store(ptr, value, ptr_ty, value_ty);
   2356 
   2357     return self.finishAir(inst, .dead, .{ bin_op.lhs, bin_op.rhs, .none });
   2358 }
   2359 
   2360 fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) !void {
   2361     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   2362     const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
   2363     const result = try self.structFieldPtr(inst, extra.struct_operand, extra.field_index);
   2364     return self.finishAir(inst, result, .{ extra.struct_operand, .none, .none });
   2365 }
   2366 
   2367 fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u8) !void {
   2368     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   2369     const result = try self.structFieldPtr(inst, ty_op.operand, index);
   2370     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   2371 }
   2372 
   2373 fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, index: u32) !MCValue {
   2374     return if (self.liveness.isUnused(inst)) .dead else result: {
   2375         const mcv = try self.resolveInst(operand);
   2376         const ptr_ty = self.air.typeOf(operand);
   2377         const struct_ty = ptr_ty.childType();
   2378         const struct_field_offset = @intCast(u32, struct_ty.structFieldOffset(index, self.target.*));
   2379         switch (mcv) {
   2380             .ptr_stack_offset => |off| {
   2381                 break :result MCValue{ .ptr_stack_offset = off - struct_field_offset };
   2382             },
   2383             else => {
   2384                 const offset_reg = try self.copyToTmpRegister(ptr_ty, .{
   2385                     .immediate = struct_field_offset,
   2386                 });
   2387                 self.register_manager.freezeRegs(&.{offset_reg});
   2388                 defer self.register_manager.unfreezeRegs(&.{offset_reg});
   2389 
   2390                 const addr_reg = try self.copyToTmpRegister(ptr_ty, mcv);
   2391                 self.register_manager.freezeRegs(&.{addr_reg});
   2392                 defer self.register_manager.unfreezeRegs(&.{addr_reg});
   2393 
   2394                 const dest = try self.binOp(
   2395                     .add,
   2396                     null,
   2397                     .{ .register = addr_reg },
   2398                     .{ .register = offset_reg },
   2399                     Type.usize,
   2400                     Type.usize,
   2401                 );
   2402 
   2403                 break :result dest;
   2404             },
   2405         }
   2406     };
   2407 }
   2408 
   2409 fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void {
   2410     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   2411     const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
   2412     _ = extra;
   2413     return self.fail("TODO implement codegen struct_field_val", .{});
   2414     //return self.finishAir(inst, result, .{ extra.struct_ptr, .none, .none });
   2415 }
   2416 
   2417 fn airFieldParentPtr(self: *Self, inst: Air.Inst.Index) !void {
   2418     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   2419     const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
   2420     _ = extra;
   2421     return self.fail("TODO implement codegen airFieldParentPtr", .{});
   2422 }
   2423 
   2424 fn airArg(self: *Self, inst: Air.Inst.Index) !void {
   2425     const arg_index = self.arg_index;
   2426     self.arg_index += 1;
   2427 
   2428     const ty = self.air.typeOfIndex(inst);
   2429 
   2430     const result = self.args[arg_index];
   2431     const mcv = switch (result) {
   2432         // Copy registers to the stack
   2433         .register => |reg| blk: {
   2434             const mod = self.bin_file.options.module.?;
   2435             const abi_size = math.cast(u32, ty.abiSize(self.target.*)) catch {
   2436                 return self.fail("type '{}' too big to fit into stack frame", .{ty.fmt(mod)});
   2437             };
   2438             const abi_align = ty.abiAlignment(self.target.*);
   2439             const stack_offset = try self.allocMem(inst, abi_size, abi_align);
   2440             try self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
   2441 
   2442             break :blk MCValue{ .stack_offset = stack_offset };
   2443         },
   2444         else => result,
   2445     };
   2446     // TODO generate debug info
   2447     // try self.genArgDbgInfo(inst, mcv);
   2448 
   2449     if (self.liveness.isUnused(inst))
   2450         return self.finishAirBookkeeping();
   2451 
   2452     switch (mcv) {
   2453         .register => |reg| {
   2454             self.register_manager.getRegAssumeFree(toCanonicalReg(reg), inst);
   2455         },
   2456         else => {},
   2457     }
   2458 
   2459     return self.finishAir(inst, mcv, .{ .none, .none, .none });
   2460 }
   2461 
   2462 fn airBreakpoint(self: *Self) !void {
   2463     _ = try self.addInst(.{
   2464         .tag = .brk,
   2465         .data = .{ .imm16 = 1 },
   2466     });
   2467     return self.finishAirBookkeeping();
   2468 }
   2469 
   2470 fn airRetAddr(self: *Self, inst: Air.Inst.Index) !void {
   2471     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airRetAddr for aarch64", .{});
   2472     return self.finishAir(inst, result, .{ .none, .none, .none });
   2473 }
   2474 
   2475 fn airFrameAddress(self: *Self, inst: Air.Inst.Index) !void {
   2476     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFrameAddress for aarch64", .{});
   2477     return self.finishAir(inst, result, .{ .none, .none, .none });
   2478 }
   2479 
   2480 fn airFence(self: *Self) !void {
   2481     return self.fail("TODO implement fence() for {}", .{self.target.cpu.arch});
   2482     //return self.finishAirBookkeeping();
   2483 }
   2484 
   2485 fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.Modifier) !void {
   2486     if (modifier == .always_tail) return self.fail("TODO implement tail calls for aarch64", .{});
   2487     const pl_op = self.air.instructions.items(.data)[inst].pl_op;
   2488     const callee = pl_op.operand;
   2489     const extra = self.air.extraData(Air.Call, pl_op.payload);
   2490     const args = @ptrCast([]const Air.Inst.Ref, self.air.extra[extra.end..][0..extra.data.args_len]);
   2491     const ty = self.air.typeOf(callee);
   2492 
   2493     const fn_ty = switch (ty.zigTypeTag()) {
   2494         .Fn => ty,
   2495         .Pointer => ty.childType(),
   2496         else => unreachable,
   2497     };
   2498 
   2499     var info = try self.resolveCallingConventionValues(fn_ty);
   2500     defer info.deinit(self);
   2501 
   2502     // According to the Procedure Call Standard for the ARM
   2503     // Architecture, compare flags are not preserved across
   2504     // calls. Therefore, if some value is currently stored there, we
   2505     // need to save it.
   2506     //
   2507     // TODO once caller-saved registers are implemented, save them
   2508     // here too, but crucially *after* we save the compare flags as
   2509     // saving compare flags may require a new caller-saved register
   2510     try self.spillCompareFlagsIfOccupied();
   2511 
   2512     for (info.args) |mc_arg, arg_i| {
   2513         const arg = args[arg_i];
   2514         const arg_ty = self.air.typeOf(arg);
   2515         const arg_mcv = try self.resolveInst(args[arg_i]);
   2516 
   2517         switch (mc_arg) {
   2518             .none => continue,
   2519             .undef => unreachable,
   2520             .immediate => unreachable,
   2521             .unreach => unreachable,
   2522             .dead => unreachable,
   2523             .memory => unreachable,
   2524             .compare_flags_signed => unreachable,
   2525             .compare_flags_unsigned => unreachable,
   2526             .got_load => unreachable,
   2527             .direct_load => unreachable,
   2528             .register => |reg| {
   2529                 try self.register_manager.getReg(reg, null);
   2530                 try self.genSetReg(arg_ty, reg, arg_mcv);
   2531             },
   2532             .stack_offset => {
   2533                 return self.fail("TODO implement calling with parameters in memory", .{});
   2534             },
   2535             .ptr_stack_offset => {
   2536                 return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{});
   2537             },
   2538         }
   2539     }
   2540 
   2541     // Due to incremental compilation, how function calls are generated depends
   2542     // on linking.
   2543     const mod = self.bin_file.options.module.?;
   2544     if (self.air.value(callee)) |func_value| {
   2545         if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) {
   2546             if (func_value.castTag(.function)) |func_payload| {
   2547                 const func = func_payload.data;
   2548                 const ptr_bits = self.target.cpu.arch.ptrBitWidth();
   2549                 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
   2550                 const fn_owner_decl = mod.declPtr(func.owner_decl);
   2551                 const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
   2552                     const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
   2553                     break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes);
   2554                 } else if (self.bin_file.cast(link.File.Coff)) |coff_file|
   2555                     coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes
   2556                 else
   2557                     unreachable;
   2558 
   2559                 try self.genSetReg(Type.initTag(.usize), .x30, .{ .memory = got_addr });
   2560 
   2561                 _ = try self.addInst(.{
   2562                     .tag = .blr,
   2563                     .data = .{ .reg = .x30 },
   2564                 });
   2565             } else if (func_value.castTag(.extern_fn)) |_| {
   2566                 return self.fail("TODO implement calling extern functions", .{});
   2567             } else {
   2568                 return self.fail("TODO implement calling bitcasted functions", .{});
   2569             }
   2570         } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
   2571             if (func_value.castTag(.function)) |func_payload| {
   2572                 const func = func_payload.data;
   2573                 const fn_owner_decl = mod.declPtr(func.owner_decl);
   2574                 try self.genSetReg(Type.initTag(.u64), .x30, .{
   2575                     .got_load = fn_owner_decl.link.macho.local_sym_index,
   2576                 });
   2577                 // blr x30
   2578                 _ = try self.addInst(.{
   2579                     .tag = .blr,
   2580                     .data = .{ .reg = .x30 },
   2581                 });
   2582             } else if (func_value.castTag(.extern_fn)) |func_payload| {
   2583                 const extern_fn = func_payload.data;
   2584                 const decl_name = mod.declPtr(extern_fn.owner_decl).name;
   2585                 if (extern_fn.lib_name) |lib_name| {
   2586                     log.debug("TODO enforce that '{s}' is expected in '{s}' library", .{
   2587                         decl_name,
   2588                         lib_name,
   2589                     });
   2590                 }
   2591                 const n_strx = try macho_file.addExternFn(mem.sliceTo(decl_name, 0));
   2592 
   2593                 _ = try self.addInst(.{
   2594                     .tag = .call_extern,
   2595                     .data = .{
   2596                         .extern_fn = .{
   2597                             .atom_index = mod.declPtr(self.mod_fn.owner_decl).link.macho.local_sym_index,
   2598                             .sym_name = n_strx,
   2599                         },
   2600                     },
   2601                 });
   2602             } else {
   2603                 return self.fail("TODO implement calling bitcasted functions", .{});
   2604             }
   2605         } else if (self.bin_file.cast(link.File.Plan9)) |p9| {
   2606             if (func_value.castTag(.function)) |func_payload| {
   2607                 try p9.seeDecl(func_payload.data.owner_decl);
   2608                 const ptr_bits = self.target.cpu.arch.ptrBitWidth();
   2609                 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
   2610                 const got_addr = p9.bases.data;
   2611                 const got_index = mod.declPtr(func_payload.data.owner_decl).link.plan9.got_index.?;
   2612                 const fn_got_addr = got_addr + got_index * ptr_bytes;
   2613 
   2614                 try self.genSetReg(Type.initTag(.usize), .x30, .{ .memory = fn_got_addr });
   2615 
   2616                 _ = try self.addInst(.{
   2617                     .tag = .blr,
   2618                     .data = .{ .reg = .x30 },
   2619                 });
   2620             } else if (func_value.castTag(.extern_fn)) |_| {
   2621                 return self.fail("TODO implement calling extern functions", .{});
   2622             } else {
   2623                 return self.fail("TODO implement calling bitcasted functions", .{});
   2624             }
   2625         } else unreachable;
   2626     } else {
   2627         assert(ty.zigTypeTag() == .Pointer);
   2628         const mcv = try self.resolveInst(callee);
   2629         try self.genSetReg(ty, .x30, mcv);
   2630 
   2631         _ = try self.addInst(.{
   2632             .tag = .blr,
   2633             .data = .{ .reg = .x30 },
   2634         });
   2635     }
   2636 
   2637     const result: MCValue = result: {
   2638         switch (info.return_value) {
   2639             .register => |reg| {
   2640                 if (RegisterManager.indexOfReg(&callee_preserved_regs, reg) == null) {
   2641                     // Save function return value in a callee saved register
   2642                     break :result try self.copyToNewRegister(inst, info.return_value);
   2643                 }
   2644             },
   2645             else => {},
   2646         }
   2647         break :result info.return_value;
   2648     };
   2649 
   2650     if (args.len + 1 <= Liveness.bpi - 1) {
   2651         var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1);
   2652         buf[0] = callee;
   2653         std.mem.copy(Air.Inst.Ref, buf[1..], args);
   2654         return self.finishAir(inst, result, buf);
   2655     }
   2656     var bt = try self.iterateBigTomb(inst, 1 + args.len);
   2657     bt.feed(callee);
   2658     for (args) |arg| {
   2659         bt.feed(arg);
   2660     }
   2661     return bt.finishAir(result);
   2662 }
   2663 
   2664 fn ret(self: *Self, mcv: MCValue) !void {
   2665     const ret_ty = self.fn_type.fnReturnType();
   2666     try self.setRegOrMem(ret_ty, self.ret_mcv, mcv);
   2667     // Just add space for an instruction, patch this later
   2668     const index = try self.addInst(.{
   2669         .tag = .nop,
   2670         .data = .{ .nop = {} },
   2671     });
   2672     try self.exitlude_jump_relocs.append(self.gpa, index);
   2673 }
   2674 
   2675 fn airRet(self: *Self, inst: Air.Inst.Index) !void {
   2676     const un_op = self.air.instructions.items(.data)[inst].un_op;
   2677     const operand = try self.resolveInst(un_op);
   2678     try self.ret(operand);
   2679     return self.finishAir(inst, .dead, .{ un_op, .none, .none });
   2680 }
   2681 
   2682 fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void {
   2683     const un_op = self.air.instructions.items(.data)[inst].un_op;
   2684     const ptr = try self.resolveInst(un_op);
   2685     _ = ptr;
   2686     return self.fail("TODO implement airRetLoad for {}", .{self.target.cpu.arch});
   2687     //return self.finishAir(inst, .dead, .{ un_op, .none, .none });
   2688 }
   2689 
   2690 fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {
   2691     const bin_op = self.air.instructions.items(.data)[inst].bin_op;
   2692     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   2693         const lhs = try self.resolveInst(bin_op.lhs);
   2694         const rhs = try self.resolveInst(bin_op.rhs);
   2695         const lhs_ty = self.air.typeOf(bin_op.lhs);
   2696 
   2697         var int_buffer: Type.Payload.Bits = undefined;
   2698         const int_ty = switch (lhs_ty.zigTypeTag()) {
   2699             .Vector => return self.fail("TODO AArch64 cmp vectors", .{}),
   2700             .Enum => lhs_ty.intTagType(&int_buffer),
   2701             .Int => lhs_ty,
   2702             .Bool => Type.initTag(.u1),
   2703             .Pointer => Type.usize,
   2704             .ErrorSet => Type.initTag(.u16),
   2705             .Optional => blk: {
   2706                 var opt_buffer: Type.Payload.ElemType = undefined;
   2707                 const payload_ty = lhs_ty.optionalChild(&opt_buffer);
   2708                 if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
   2709                     break :blk Type.initTag(.u1);
   2710                 } else if (lhs_ty.isPtrLikeOptional()) {
   2711                     break :blk Type.usize;
   2712                 } else {
   2713                     return self.fail("TODO AArch64 cmp non-pointer optionals", .{});
   2714                 }
   2715             },
   2716             .Float => return self.fail("TODO AArch64 cmp floats", .{}),
   2717             else => unreachable,
   2718         };
   2719 
   2720         const int_info = int_ty.intInfo(self.target.*);
   2721         if (int_info.bits <= 64) {
   2722             _ = try self.binOp(.cmp_eq, inst, lhs, rhs, int_ty, int_ty);
   2723 
   2724             try self.spillCompareFlagsIfOccupied();
   2725             self.compare_flags_inst = inst;
   2726 
   2727             break :result switch (int_info.signedness) {
   2728                 .signed => MCValue{ .compare_flags_signed = op },
   2729                 .unsigned => MCValue{ .compare_flags_unsigned = op },
   2730             };
   2731         } else {
   2732             return self.fail("TODO AArch64 cmp for ints > 64 bits", .{});
   2733         }
   2734     };
   2735     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
   2736 }
   2737 
   2738 fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void {
   2739     _ = inst;
   2740     return self.fail("TODO implement airCmpVector for {}", .{self.target.cpu.arch});
   2741 }
   2742 
   2743 fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void {
   2744     const un_op = self.air.instructions.items(.data)[inst].un_op;
   2745     const operand = try self.resolveInst(un_op);
   2746     _ = operand;
   2747     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCmpLtErrorsLen for {}", .{self.target.cpu.arch});
   2748     return self.finishAir(inst, result, .{ un_op, .none, .none });
   2749 }
   2750 
   2751 fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void {
   2752     const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;
   2753 
   2754     _ = try self.addInst(.{
   2755         .tag = .dbg_line,
   2756         .data = .{ .dbg_line_column = .{
   2757             .line = dbg_stmt.line,
   2758             .column = dbg_stmt.column,
   2759         } },
   2760     });
   2761 
   2762     return self.finishAirBookkeeping();
   2763 }
   2764 
   2765 fn airDbgInline(self: *Self, inst: Air.Inst.Index) !void {
   2766     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   2767     const function = self.air.values[ty_pl.payload].castTag(.function).?.data;
   2768     // TODO emit debug info for function change
   2769     _ = function;
   2770     return self.finishAir(inst, .dead, .{ .none, .none, .none });
   2771 }
   2772 
   2773 fn airDbgBlock(self: *Self, inst: Air.Inst.Index) !void {
   2774     // TODO emit debug info lexical block
   2775     return self.finishAir(inst, .dead, .{ .none, .none, .none });
   2776 }
   2777 
   2778 fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void {
   2779     const pl_op = self.air.instructions.items(.data)[inst].pl_op;
   2780     const name = self.air.nullTerminatedString(pl_op.payload);
   2781     const operand = pl_op.operand;
   2782     // TODO emit debug info for this variable
   2783     _ = name;
   2784     return self.finishAir(inst, .dead, .{ operand, .none, .none });
   2785 }
   2786 
   2787 fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
   2788     const pl_op = self.air.instructions.items(.data)[inst].pl_op;
   2789     const cond = try self.resolveInst(pl_op.operand);
   2790     const extra = self.air.extraData(Air.CondBr, pl_op.payload);
   2791     const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
   2792     const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
   2793     const liveness_condbr = self.liveness.getCondBr(inst);
   2794 
   2795     const reloc: Mir.Inst.Index = switch (cond) {
   2796         .compare_flags_signed,
   2797         .compare_flags_unsigned,
   2798         => try self.addInst(.{
   2799             .tag = .b_cond,
   2800             .data = .{
   2801                 .inst_cond = .{
   2802                     .inst = undefined, // populated later through performReloc
   2803                     .cond = switch (cond) {
   2804                         .compare_flags_signed => |cmp_op| blk: {
   2805                             // Here we map to the opposite condition because the jump is to the false branch.
   2806                             const condition = Instruction.Condition.fromCompareOperatorSigned(cmp_op);
   2807                             break :blk condition.negate();
   2808                         },
   2809                         .compare_flags_unsigned => |cmp_op| blk: {
   2810                             // Here we map to the opposite condition because the jump is to the false branch.
   2811                             const condition = Instruction.Condition.fromCompareOperatorUnsigned(cmp_op);
   2812                             break :blk condition.negate();
   2813                         },
   2814                         else => unreachable,
   2815                     },
   2816                 },
   2817             },
   2818         }),
   2819         else => blk: {
   2820             const reg = switch (cond) {
   2821                 .register => |r| r,
   2822                 else => try self.copyToTmpRegister(Type.bool, cond),
   2823             };
   2824 
   2825             break :blk try self.addInst(.{
   2826                 .tag = .cbz,
   2827                 .data = .{
   2828                     .r_inst = .{
   2829                         .rt = reg,
   2830                         .inst = undefined, // populated later through performReloc
   2831                     },
   2832                 },
   2833             });
   2834         },
   2835     };
   2836 
   2837     // If the condition dies here in this condbr instruction, process
   2838     // that death now instead of later as this has an effect on
   2839     // whether it needs to be spilled in the branches
   2840     if (self.liveness.operandDies(inst, 0)) {
   2841         const op_int = @enumToInt(pl_op.operand);
   2842         if (op_int >= Air.Inst.Ref.typed_value_map.len) {
   2843             const op_index = @intCast(Air.Inst.Index, op_int - Air.Inst.Ref.typed_value_map.len);
   2844             self.processDeath(op_index);
   2845         }
   2846     }
   2847 
   2848     // Capture the state of register and stack allocation state so that we can revert to it.
   2849     const parent_next_stack_offset = self.next_stack_offset;
   2850     const parent_free_registers = self.register_manager.free_registers;
   2851     var parent_stack = try self.stack.clone(self.gpa);
   2852     defer parent_stack.deinit(self.gpa);
   2853     const parent_registers = self.register_manager.registers;
   2854     const parent_compare_flags_inst = self.compare_flags_inst;
   2855 
   2856     try self.branch_stack.append(.{});
   2857 
   2858     try self.ensureProcessDeathCapacity(liveness_condbr.then_deaths.len);
   2859     for (liveness_condbr.then_deaths) |operand| {
   2860         self.processDeath(operand);
   2861     }
   2862     try self.genBody(then_body);
   2863 
   2864     // Revert to the previous register and stack allocation state.
   2865 
   2866     var saved_then_branch = self.branch_stack.pop();
   2867     defer saved_then_branch.deinit(self.gpa);
   2868 
   2869     self.register_manager.registers = parent_registers;
   2870     self.compare_flags_inst = parent_compare_flags_inst;
   2871 
   2872     self.stack.deinit(self.gpa);
   2873     self.stack = parent_stack;
   2874     parent_stack = .{};
   2875 
   2876     self.next_stack_offset = parent_next_stack_offset;
   2877     self.register_manager.free_registers = parent_free_registers;
   2878 
   2879     try self.performReloc(reloc);
   2880     const else_branch = self.branch_stack.addOneAssumeCapacity();
   2881     else_branch.* = .{};
   2882 
   2883     try self.ensureProcessDeathCapacity(liveness_condbr.else_deaths.len);
   2884     for (liveness_condbr.else_deaths) |operand| {
   2885         self.processDeath(operand);
   2886     }
   2887     try self.genBody(else_body);
   2888 
   2889     // At this point, each branch will possibly have conflicting values for where
   2890     // each instruction is stored. They agree, however, on which instructions are alive/dead.
   2891     // We use the first ("then") branch as canonical, and here emit
   2892     // instructions into the second ("else") branch to make it conform.
   2893     // We continue respect the data structure semantic guarantees of the else_branch so
   2894     // that we can use all the code emitting abstractions. This is why at the bottom we
   2895     // assert that parent_branch.free_registers equals the saved_then_branch.free_registers
   2896     // rather than assigning it.
   2897     const parent_branch = &self.branch_stack.items[self.branch_stack.items.len - 2];
   2898     try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, else_branch.inst_table.count());
   2899 
   2900     const else_slice = else_branch.inst_table.entries.slice();
   2901     const else_keys = else_slice.items(.key);
   2902     const else_values = else_slice.items(.value);
   2903     for (else_keys) |else_key, else_idx| {
   2904         const else_value = else_values[else_idx];
   2905         const canon_mcv = if (saved_then_branch.inst_table.fetchSwapRemove(else_key)) |then_entry| blk: {
   2906             // The instruction's MCValue is overridden in both branches.
   2907             parent_branch.inst_table.putAssumeCapacity(else_key, then_entry.value);
   2908             if (else_value == .dead) {
   2909                 assert(then_entry.value == .dead);
   2910                 continue;
   2911             }
   2912             break :blk then_entry.value;
   2913         } else blk: {
   2914             if (else_value == .dead)
   2915                 continue;
   2916             // The instruction is only overridden in the else branch.
   2917             var i: usize = self.branch_stack.items.len - 2;
   2918             while (true) {
   2919                 i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead?
   2920                 if (self.branch_stack.items[i].inst_table.get(else_key)) |mcv| {
   2921                     assert(mcv != .dead);
   2922                     break :blk mcv;
   2923                 }
   2924             }
   2925         };
   2926         log.debug("consolidating else_entry {d} {}=>{}", .{ else_key, else_value, canon_mcv });
   2927         // TODO make sure the destination stack offset / register does not already have something
   2928         // going on there.
   2929         try self.setRegOrMem(self.air.typeOfIndex(else_key), canon_mcv, else_value);
   2930         // TODO track the new register / stack allocation
   2931     }
   2932     try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, saved_then_branch.inst_table.count());
   2933     const then_slice = saved_then_branch.inst_table.entries.slice();
   2934     const then_keys = then_slice.items(.key);
   2935     const then_values = then_slice.items(.value);
   2936     for (then_keys) |then_key, then_idx| {
   2937         const then_value = then_values[then_idx];
   2938         // We already deleted the items from this table that matched the else_branch.
   2939         // So these are all instructions that are only overridden in the then branch.
   2940         parent_branch.inst_table.putAssumeCapacity(then_key, then_value);
   2941         if (then_value == .dead)
   2942             continue;
   2943         const parent_mcv = blk: {
   2944             var i: usize = self.branch_stack.items.len - 2;
   2945             while (true) {
   2946                 i -= 1;
   2947                 if (self.branch_stack.items[i].inst_table.get(then_key)) |mcv| {
   2948                     assert(mcv != .dead);
   2949                     break :blk mcv;
   2950                 }
   2951             }
   2952         };
   2953         log.debug("consolidating then_entry {d} {}=>{}", .{ then_key, parent_mcv, then_value });
   2954         // TODO make sure the destination stack offset / register does not already have something
   2955         // going on there.
   2956         try self.setRegOrMem(self.air.typeOfIndex(then_key), parent_mcv, then_value);
   2957         // TODO track the new register / stack allocation
   2958     }
   2959 
   2960     {
   2961         var item = self.branch_stack.pop();
   2962         item.deinit(self.gpa);
   2963     }
   2964 
   2965     // We already took care of pl_op.operand earlier, so we're going
   2966     // to pass .none here
   2967     return self.finishAir(inst, .unreach, .{ .none, .none, .none });
   2968 }
   2969 
   2970 fn isNull(self: *Self, operand: MCValue) !MCValue {
   2971     _ = operand;
   2972     // Here you can specialize this instruction if it makes sense to, otherwise the default
   2973     // will call isNonNull and invert the result.
   2974     return self.fail("TODO call isNonNull and invert the result", .{});
   2975 }
   2976 
   2977 fn isNonNull(self: *Self, operand: MCValue) !MCValue {
   2978     _ = operand;
   2979     // Here you can specialize this instruction if it makes sense to, otherwise the default
   2980     // will call isNull and invert the result.
   2981     return self.fail("TODO call isNull and invert the result", .{});
   2982 }
   2983 
   2984 fn isErr(self: *Self, ty: Type, operand: MCValue) !MCValue {
   2985     const error_type = ty.errorUnionSet();
   2986     const payload_type = ty.errorUnionPayload();
   2987 
   2988     if (!error_type.hasRuntimeBits()) {
   2989         return MCValue{ .immediate = 0 }; // always false
   2990     } else if (!payload_type.hasRuntimeBits()) {
   2991         if (error_type.abiSize(self.target.*) <= 8) {
   2992             const reg_mcv: MCValue = switch (operand) {
   2993                 .register => operand,
   2994                 else => .{ .register = try self.copyToTmpRegister(error_type, operand) },
   2995             };
   2996 
   2997             _ = try self.addInst(.{
   2998                 .tag = .cmp_immediate,
   2999                 .data = .{ .r_imm12_sh = .{
   3000                     .rn = reg_mcv.register,
   3001                     .imm12 = 0,
   3002                 } },
   3003             });
   3004 
   3005             return MCValue{ .compare_flags_unsigned = .gt };
   3006         } else {
   3007             return self.fail("TODO isErr for errors with size > 8", .{});
   3008         }
   3009     } else {
   3010         return self.fail("TODO isErr for non-empty payloads", .{});
   3011     }
   3012 }
   3013 
   3014 fn isNonErr(self: *Self, ty: Type, operand: MCValue) !MCValue {
   3015     const is_err_result = try self.isErr(ty, operand);
   3016     switch (is_err_result) {
   3017         .compare_flags_unsigned => |op| {
   3018             assert(op == .gt);
   3019             return MCValue{ .compare_flags_unsigned = .lte };
   3020         },
   3021         .immediate => |imm| {
   3022             assert(imm == 0);
   3023             return MCValue{ .immediate = 1 };
   3024         },
   3025         else => unreachable,
   3026     }
   3027 }
   3028 
   3029 fn airIsNull(self: *Self, inst: Air.Inst.Index) !void {
   3030     const un_op = self.air.instructions.items(.data)[inst].un_op;
   3031     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   3032         const operand = try self.resolveInst(un_op);
   3033         break :result try self.isNull(operand);
   3034     };
   3035     return self.finishAir(inst, result, .{ un_op, .none, .none });
   3036 }
   3037 
   3038 fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void {
   3039     const un_op = self.air.instructions.items(.data)[inst].un_op;
   3040     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   3041         const operand_ptr = try self.resolveInst(un_op);
   3042         const operand: MCValue = blk: {
   3043             if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {
   3044                 // The MCValue that holds the pointer can be re-used as the value.
   3045                 break :blk operand_ptr;
   3046             } else {
   3047                 break :blk try self.allocRegOrMem(inst, true);
   3048             }
   3049         };
   3050         try self.load(operand, operand_ptr, self.air.typeOf(un_op));
   3051         break :result try self.isNull(operand);
   3052     };
   3053     return self.finishAir(inst, result, .{ un_op, .none, .none });
   3054 }
   3055 
   3056 fn airIsNonNull(self: *Self, inst: Air.Inst.Index) !void {
   3057     const un_op = self.air.instructions.items(.data)[inst].un_op;
   3058     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   3059         const operand = try self.resolveInst(un_op);
   3060         break :result try self.isNonNull(operand);
   3061     };
   3062     return self.finishAir(inst, result, .{ un_op, .none, .none });
   3063 }
   3064 
   3065 fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void {
   3066     const un_op = self.air.instructions.items(.data)[inst].un_op;
   3067     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   3068         const operand_ptr = try self.resolveInst(un_op);
   3069         const operand: MCValue = blk: {
   3070             if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {
   3071                 // The MCValue that holds the pointer can be re-used as the value.
   3072                 break :blk operand_ptr;
   3073             } else {
   3074                 break :blk try self.allocRegOrMem(inst, true);
   3075             }
   3076         };
   3077         try self.load(operand, operand_ptr, self.air.typeOf(un_op));
   3078         break :result try self.isNonNull(operand);
   3079     };
   3080     return self.finishAir(inst, result, .{ un_op, .none, .none });
   3081 }
   3082 
   3083 fn airIsErr(self: *Self, inst: Air.Inst.Index) !void {
   3084     const un_op = self.air.instructions.items(.data)[inst].un_op;
   3085     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   3086         const operand = try self.resolveInst(un_op);
   3087         const ty = self.air.typeOf(un_op);
   3088         break :result try self.isErr(ty, operand);
   3089     };
   3090     return self.finishAir(inst, result, .{ un_op, .none, .none });
   3091 }
   3092 
   3093 fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void {
   3094     const un_op = self.air.instructions.items(.data)[inst].un_op;
   3095     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   3096         const operand_ptr = try self.resolveInst(un_op);
   3097         const ptr_ty = self.air.typeOf(un_op);
   3098         const operand: MCValue = blk: {
   3099             if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {
   3100                 // The MCValue that holds the pointer can be re-used as the value.
   3101                 break :blk operand_ptr;
   3102             } else {
   3103                 break :blk try self.allocRegOrMem(inst, true);
   3104             }
   3105         };
   3106         try self.load(operand, operand_ptr, self.air.typeOf(un_op));
   3107         break :result try self.isErr(ptr_ty.elemType(), operand);
   3108     };
   3109     return self.finishAir(inst, result, .{ un_op, .none, .none });
   3110 }
   3111 
   3112 fn airIsNonErr(self: *Self, inst: Air.Inst.Index) !void {
   3113     const un_op = self.air.instructions.items(.data)[inst].un_op;
   3114     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   3115         const operand = try self.resolveInst(un_op);
   3116         const ty = self.air.typeOf(un_op);
   3117         break :result try self.isNonErr(ty, operand);
   3118     };
   3119     return self.finishAir(inst, result, .{ un_op, .none, .none });
   3120 }
   3121 
   3122 fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void {
   3123     const un_op = self.air.instructions.items(.data)[inst].un_op;
   3124     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   3125         const operand_ptr = try self.resolveInst(un_op);
   3126         const ptr_ty = self.air.typeOf(un_op);
   3127         const operand: MCValue = blk: {
   3128             if (self.reuseOperand(inst, un_op, 0, operand_ptr)) {
   3129                 // The MCValue that holds the pointer can be re-used as the value.
   3130                 break :blk operand_ptr;
   3131             } else {
   3132                 break :blk try self.allocRegOrMem(inst, true);
   3133             }
   3134         };
   3135         try self.load(operand, operand_ptr, self.air.typeOf(un_op));
   3136         break :result try self.isNonErr(ptr_ty.elemType(), operand);
   3137     };
   3138     return self.finishAir(inst, result, .{ un_op, .none, .none });
   3139 }
   3140 
   3141 fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
   3142     // A loop is a setup to be able to jump back to the beginning.
   3143     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   3144     const loop = self.air.extraData(Air.Block, ty_pl.payload);
   3145     const body = self.air.extra[loop.end..][0..loop.data.body_len];
   3146     const start_index = @intCast(u32, self.mir_instructions.len);
   3147     try self.genBody(body);
   3148     try self.jump(start_index);
   3149     return self.finishAirBookkeeping();
   3150 }
   3151 
   3152 /// Send control flow to `inst`.
   3153 fn jump(self: *Self, inst: Mir.Inst.Index) !void {
   3154     _ = try self.addInst(.{
   3155         .tag = .b,
   3156         .data = .{ .inst = inst },
   3157     });
   3158 }
   3159 
   3160 fn airBlock(self: *Self, inst: Air.Inst.Index) !void {
   3161     try self.blocks.putNoClobber(self.gpa, inst, .{
   3162         // A block is a setup to be able to jump to the end.
   3163         .relocs = .{},
   3164         // It also acts as a receptacle for break operands.
   3165         // Here we use `MCValue.none` to represent a null value so that the first
   3166         // break instruction will choose a MCValue for the block result and overwrite
   3167         // this field. Following break instructions will use that MCValue to put their
   3168         // block results.
   3169         .mcv = MCValue{ .none = {} },
   3170     });
   3171     defer self.blocks.getPtr(inst).?.relocs.deinit(self.gpa);
   3172 
   3173     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   3174     const extra = self.air.extraData(Air.Block, ty_pl.payload);
   3175     const body = self.air.extra[extra.end..][0..extra.data.body_len];
   3176     try self.genBody(body);
   3177 
   3178     // relocations for `br` instructions
   3179     const relocs = &self.blocks.getPtr(inst).?.relocs;
   3180     if (relocs.items.len > 0 and relocs.items[relocs.items.len - 1] == self.mir_instructions.len - 1) {
   3181         // If the last Mir instruction is the last relocation (which
   3182         // would just jump one instruction further), it can be safely
   3183         // removed
   3184         self.mir_instructions.orderedRemove(relocs.pop());
   3185     }
   3186     for (relocs.items) |reloc| {
   3187         try self.performReloc(reloc);
   3188     }
   3189 
   3190     const result = self.blocks.getPtr(inst).?.mcv;
   3191     return self.finishAir(inst, result, .{ .none, .none, .none });
   3192 }
   3193 
   3194 fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
   3195     const pl_op = self.air.instructions.items(.data)[inst].pl_op;
   3196     const condition = pl_op.operand;
   3197     _ = condition;
   3198 
   3199     return self.fail("TODO airSwitch for {}", .{self.target.cpu.arch});
   3200 }
   3201 
   3202 fn performReloc(self: *Self, inst: Mir.Inst.Index) !void {
   3203     const tag = self.mir_instructions.items(.tag)[inst];
   3204     switch (tag) {
   3205         .cbz => self.mir_instructions.items(.data)[inst].r_inst.inst = @intCast(Mir.Inst.Index, self.mir_instructions.len),
   3206         .b_cond => self.mir_instructions.items(.data)[inst].inst_cond.inst = @intCast(Mir.Inst.Index, self.mir_instructions.len),
   3207         .b => self.mir_instructions.items(.data)[inst].inst = @intCast(Mir.Inst.Index, self.mir_instructions.len),
   3208         else => unreachable,
   3209     }
   3210 }
   3211 
   3212 fn airBr(self: *Self, inst: Air.Inst.Index) !void {
   3213     const branch = self.air.instructions.items(.data)[inst].br;
   3214     try self.br(branch.block_inst, branch.operand);
   3215     return self.finishAir(inst, .dead, .{ branch.operand, .none, .none });
   3216 }
   3217 
   3218 fn br(self: *Self, block: Air.Inst.Index, operand: Air.Inst.Ref) !void {
   3219     const block_data = self.blocks.getPtr(block).?;
   3220 
   3221     if (self.air.typeOf(operand).hasRuntimeBits()) {
   3222         const operand_mcv = try self.resolveInst(operand);
   3223         const block_mcv = block_data.mcv;
   3224         if (block_mcv == .none) {
   3225             block_data.mcv = switch (operand_mcv) {
   3226                 .none, .dead, .unreach => unreachable,
   3227                 .register, .stack_offset, .memory => operand_mcv,
   3228                 .immediate => blk: {
   3229                     const new_mcv = try self.allocRegOrMem(block, true);
   3230                     try self.setRegOrMem(self.air.typeOfIndex(block), new_mcv, operand_mcv);
   3231                     break :blk new_mcv;
   3232                 },
   3233                 else => return self.fail("TODO implement block_data.mcv = operand_mcv for {}", .{operand_mcv}),
   3234             };
   3235         } else {
   3236             try self.setRegOrMem(self.air.typeOfIndex(block), block_mcv, operand_mcv);
   3237         }
   3238     }
   3239     return self.brVoid(block);
   3240 }
   3241 
   3242 fn brVoid(self: *Self, block: Air.Inst.Index) !void {
   3243     const block_data = self.blocks.getPtr(block).?;
   3244 
   3245     // Emit a jump with a relocation. It will be patched up after the block ends.
   3246     try block_data.relocs.ensureUnusedCapacity(self.gpa, 1);
   3247 
   3248     block_data.relocs.appendAssumeCapacity(try self.addInst(.{
   3249         .tag = .b,
   3250         .data = .{ .inst = undefined }, // populated later through performReloc
   3251     }));
   3252 }
   3253 
   3254 fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
   3255     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   3256     const extra = self.air.extraData(Air.Asm, ty_pl.payload);
   3257     const is_volatile = @truncate(u1, extra.data.flags >> 31) != 0;
   3258     const clobbers_len = @truncate(u31, extra.data.flags);
   3259     var extra_i: usize = extra.end;
   3260     const outputs = @ptrCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.outputs_len]);
   3261     extra_i += outputs.len;
   3262     const inputs = @ptrCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.inputs_len]);
   3263     extra_i += inputs.len;
   3264 
   3265     const dead = !is_volatile and self.liveness.isUnused(inst);
   3266     const result: MCValue = if (dead) .dead else result: {
   3267         if (outputs.len > 1) {
   3268             return self.fail("TODO implement codegen for asm with more than 1 output", .{});
   3269         }
   3270 
   3271         const output_constraint: ?[]const u8 = for (outputs) |output| {
   3272             if (output != .none) {
   3273                 return self.fail("TODO implement codegen for non-expr asm", .{});
   3274             }
   3275             const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0);
   3276             // This equation accounts for the fact that even if we have exactly 4 bytes
   3277             // for the string, we still use the next u32 for the null terminator.
   3278             extra_i += constraint.len / 4 + 1;
   3279 
   3280             break constraint;
   3281         } else null;
   3282 
   3283         for (inputs) |input| {
   3284             const input_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
   3285             const constraint = std.mem.sliceTo(input_bytes, 0);
   3286             const input_name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
   3287             // This equation accounts for the fact that even if we have exactly 4 bytes
   3288             // for the string, we still use the next u32 for the null terminator.
   3289             extra_i += (constraint.len + input_name.len + 1) / 4 + 1;
   3290 
   3291             if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') {
   3292                 return self.fail("unrecognized asm input constraint: '{s}'", .{constraint});
   3293             }
   3294             const reg_name = constraint[1 .. constraint.len - 1];
   3295             const reg = parseRegName(reg_name) orelse
   3296                 return self.fail("unrecognized register: '{s}'", .{reg_name});
   3297 
   3298             const arg_mcv = try self.resolveInst(input);
   3299             try self.register_manager.getReg(reg, null);
   3300             try self.genSetReg(self.air.typeOf(input), reg, arg_mcv);
   3301         }
   3302 
   3303         {
   3304             var clobber_i: u32 = 0;
   3305             while (clobber_i < clobbers_len) : (clobber_i += 1) {
   3306                 const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0);
   3307                 // This equation accounts for the fact that even if we have exactly 4 bytes
   3308                 // for the string, we still use the next u32 for the null terminator.
   3309                 extra_i += clobber.len / 4 + 1;
   3310 
   3311                 // TODO honor these
   3312             }
   3313         }
   3314 
   3315         const asm_source = std.mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len];
   3316 
   3317         if (mem.eql(u8, asm_source, "svc #0")) {
   3318             _ = try self.addInst(.{
   3319                 .tag = .svc,
   3320                 .data = .{ .imm16 = 0x0 },
   3321             });
   3322         } else if (mem.eql(u8, asm_source, "svc #0x80")) {
   3323             _ = try self.addInst(.{
   3324                 .tag = .svc,
   3325                 .data = .{ .imm16 = 0x80 },
   3326             });
   3327         } else {
   3328             return self.fail("TODO implement support for more aarch64 assembly instructions", .{});
   3329         }
   3330 
   3331         if (output_constraint) |output| {
   3332             if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
   3333                 return self.fail("unrecognized asm output constraint: '{s}'", .{output});
   3334             }
   3335             const reg_name = output[2 .. output.len - 1];
   3336             const reg = parseRegName(reg_name) orelse
   3337                 return self.fail("unrecognized register: '{s}'", .{reg_name});
   3338             break :result MCValue{ .register = reg };
   3339         } else {
   3340             break :result MCValue{ .none = {} };
   3341         }
   3342     };
   3343 
   3344     simple: {
   3345         var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1);
   3346         var buf_index: usize = 0;
   3347         for (outputs) |output| {
   3348             if (output == .none) continue;
   3349 
   3350             if (buf_index >= buf.len) break :simple;
   3351             buf[buf_index] = output;
   3352             buf_index += 1;
   3353         }
   3354         if (buf_index + inputs.len > buf.len) break :simple;
   3355         std.mem.copy(Air.Inst.Ref, buf[buf_index..], inputs);
   3356         return self.finishAir(inst, result, buf);
   3357     }
   3358     var bt = try self.iterateBigTomb(inst, outputs.len + inputs.len);
   3359     for (outputs) |output| {
   3360         if (output == .none) continue;
   3361 
   3362         bt.feed(output);
   3363     }
   3364     for (inputs) |input| {
   3365         bt.feed(input);
   3366     }
   3367     return bt.finishAir(result);
   3368 }
   3369 
   3370 fn iterateBigTomb(self: *Self, inst: Air.Inst.Index, operand_count: usize) !BigTomb {
   3371     try self.ensureProcessDeathCapacity(operand_count + 1);
   3372     return BigTomb{
   3373         .function = self,
   3374         .inst = inst,
   3375         .lbt = self.liveness.iterateBigTomb(inst),
   3376     };
   3377 }
   3378 
   3379 /// Sets the value without any modifications to register allocation metadata or stack allocation metadata.
   3380 fn setRegOrMem(self: *Self, ty: Type, loc: MCValue, val: MCValue) !void {
   3381     switch (loc) {
   3382         .none => return,
   3383         .register => |reg| return self.genSetReg(ty, reg, val),
   3384         .stack_offset => |off| return self.genSetStack(ty, off, val),
   3385         .memory => {
   3386             return self.fail("TODO implement setRegOrMem for memory", .{});
   3387         },
   3388         else => unreachable,
   3389     }
   3390 }
   3391 
   3392 fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void {
   3393     const abi_size = ty.abiSize(self.target.*);
   3394     switch (mcv) {
   3395         .dead => unreachable,
   3396         .unreach, .none => return, // Nothing to do.
   3397         .undef => {
   3398             if (!self.wantSafety())
   3399                 return; // The already existing value will do just fine.
   3400             // TODO Upgrade this to a memset call when we have that available.
   3401             switch (ty.abiSize(self.target.*)) {
   3402                 1 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaa }),
   3403                 2 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaa }),
   3404                 4 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
   3405                 8 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
   3406                 else => return self.fail("TODO implement memset", .{}),
   3407             }
   3408         },
   3409         .compare_flags_unsigned,
   3410         .compare_flags_signed,
   3411         .immediate,
   3412         .ptr_stack_offset,
   3413         => {
   3414             const reg = try self.copyToTmpRegister(ty, mcv);
   3415             return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
   3416         },
   3417         .register => |reg| {
   3418             switch (abi_size) {
   3419                 1, 2, 4, 8 => {
   3420                     const tag: Mir.Inst.Tag = switch (abi_size) {
   3421                         1 => .strb_stack,
   3422                         2 => .strh_stack,
   3423                         4, 8 => .str_stack,
   3424                         else => unreachable, // unexpected abi size
   3425                     };
   3426                     const rt = registerAlias(reg, abi_size);
   3427 
   3428                     _ = try self.addInst(.{
   3429                         .tag = tag,
   3430                         .data = .{ .load_store_stack = .{
   3431                             .rt = rt,
   3432                             .offset = @intCast(u32, stack_offset),
   3433                         } },
   3434                     });
   3435                 },
   3436                 else => return self.fail("TODO implement storing other types abi_size={}", .{abi_size}),
   3437             }
   3438         },
   3439         .got_load,
   3440         .direct_load,
   3441         .memory,
   3442         .stack_offset,
   3443         => {
   3444             switch (mcv) {
   3445                 .stack_offset => |off| {
   3446                     if (stack_offset == off)
   3447                         return; // Copy stack variable to itself; nothing to do.
   3448                 },
   3449                 else => {},
   3450             }
   3451 
   3452             if (abi_size <= 8) {
   3453                 const reg = try self.copyToTmpRegister(ty, mcv);
   3454                 return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
   3455             } else {
   3456                 var ptr_ty_payload: Type.Payload.ElemType = .{
   3457                     .base = .{ .tag = .single_mut_pointer },
   3458                     .data = ty,
   3459                 };
   3460                 const ptr_ty = Type.initPayload(&ptr_ty_payload.base);
   3461 
   3462                 // TODO call extern memcpy
   3463                 const regs = try self.register_manager.allocRegs(5, .{ null, null, null, null, null });
   3464                 self.register_manager.freezeRegs(&regs);
   3465                 defer self.register_manager.unfreezeRegs(&regs);
   3466 
   3467                 const src_reg = regs[0];
   3468                 const dst_reg = regs[1];
   3469                 const len_reg = regs[2];
   3470                 const count_reg = regs[3];
   3471                 const tmp_reg = regs[4];
   3472 
   3473                 switch (mcv) {
   3474                     .stack_offset => |off| {
   3475                         // sub src_reg, fp, #off
   3476                         try self.genSetReg(ptr_ty, src_reg, .{ .ptr_stack_offset = off });
   3477                     },
   3478                     .memory => |addr| try self.genSetReg(Type.usize, src_reg, .{ .immediate = addr }),
   3479                     .got_load,
   3480                     .direct_load,
   3481                     => |sym_index| {
   3482                         const tag: Mir.Inst.Tag = switch (mcv) {
   3483                             .got_load => .load_memory_ptr_got,
   3484                             .direct_load => .load_memory_ptr_direct,
   3485                             else => unreachable,
   3486                         };
   3487                         const mod = self.bin_file.options.module.?;
   3488                         _ = try self.addInst(.{
   3489                             .tag = tag,
   3490                             .data = .{
   3491                                 .payload = try self.addExtra(Mir.LoadMemoryPie{
   3492                                     .register = @enumToInt(src_reg),
   3493                                     .atom_index = mod.declPtr(self.mod_fn.owner_decl).link.macho.local_sym_index,
   3494                                     .sym_index = sym_index,
   3495                                 }),
   3496                             },
   3497                         });
   3498                     },
   3499                     else => unreachable,
   3500                 }
   3501 
   3502                 // sub dst_reg, fp, #stack_offset
   3503                 try self.genSetReg(ptr_ty, dst_reg, .{ .ptr_stack_offset = stack_offset });
   3504 
   3505                 // mov len, #abi_size
   3506                 try self.genSetReg(Type.usize, len_reg, .{ .immediate = abi_size });
   3507 
   3508                 // memcpy(src, dst, len)
   3509                 try self.genInlineMemcpy(src_reg, dst_reg, len_reg, count_reg, tmp_reg);
   3510             }
   3511         },
   3512     }
   3513 }
   3514 
   3515 fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void {
   3516     switch (mcv) {
   3517         .dead => unreachable,
   3518         .unreach, .none => return, // Nothing to do.
   3519         .undef => {
   3520             if (!self.wantSafety())
   3521                 return; // The already existing value will do just fine.
   3522             // Write the debug undefined value.
   3523             switch (reg.size()) {
   3524                 32 => return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaa }),
   3525                 64 => return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
   3526                 else => unreachable, // unexpected register size
   3527             }
   3528         },
   3529         .ptr_stack_offset => |off| {
   3530             // TODO: maybe addressing from sp instead of fp
   3531             const imm12 = math.cast(u12, off) catch
   3532                 return self.fail("TODO larger stack offsets", .{});
   3533 
   3534             _ = try self.addInst(.{
   3535                 .tag = .sub_immediate,
   3536                 .data = .{ .rr_imm12_sh = .{
   3537                     .rd = reg,
   3538                     .rn = .x29,
   3539                     .imm12 = imm12,
   3540                 } },
   3541             });
   3542         },
   3543         .compare_flags_unsigned,
   3544         .compare_flags_signed,
   3545         => |op| {
   3546             const condition = switch (mcv) {
   3547                 .compare_flags_unsigned => Instruction.Condition.fromCompareOperatorUnsigned(op),
   3548                 .compare_flags_signed => Instruction.Condition.fromCompareOperatorSigned(op),
   3549                 else => unreachable,
   3550             };
   3551 
   3552             _ = try self.addInst(.{
   3553                 .tag = .cset,
   3554                 .data = .{ .r_cond = .{
   3555                     .rd = reg,
   3556                     .cond = condition.negate(),
   3557                 } },
   3558             });
   3559         },
   3560         .immediate => |x| {
   3561             _ = try self.addInst(.{
   3562                 .tag = .movz,
   3563                 .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @truncate(u16, x) } },
   3564             });
   3565 
   3566             if (x & 0x0000_0000_ffff_0000 != 0) {
   3567                 _ = try self.addInst(.{
   3568                     .tag = .movk,
   3569                     .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @truncate(u16, x >> 16), .hw = 1 } },
   3570                 });
   3571             }
   3572 
   3573             if (reg.size() == 64) {
   3574                 if (x & 0x0000_ffff_0000_0000 != 0) {
   3575                     _ = try self.addInst(.{
   3576                         .tag = .movk,
   3577                         .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @truncate(u16, x >> 32), .hw = 2 } },
   3578                     });
   3579                 }
   3580                 if (x & 0xffff_0000_0000_0000 != 0) {
   3581                     _ = try self.addInst(.{
   3582                         .tag = .movk,
   3583                         .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @truncate(u16, x >> 48), .hw = 3 } },
   3584                     });
   3585                 }
   3586             }
   3587         },
   3588         .register => |src_reg| {
   3589             // If the registers are the same, nothing to do.
   3590             if (src_reg.id() == reg.id())
   3591                 return;
   3592 
   3593             // mov reg, src_reg
   3594             _ = try self.addInst(.{
   3595                 .tag = .mov_register,
   3596                 .data = .{ .rr = .{ .rd = reg, .rn = src_reg } },
   3597             });
   3598         },
   3599         .got_load,
   3600         .direct_load,
   3601         => |sym_index| {
   3602             const tag: Mir.Inst.Tag = switch (mcv) {
   3603                 .got_load => .load_memory_got,
   3604                 .direct_load => .load_memory_direct,
   3605                 else => unreachable,
   3606             };
   3607             const mod = self.bin_file.options.module.?;
   3608             _ = try self.addInst(.{
   3609                 .tag = tag,
   3610                 .data = .{
   3611                     .payload = try self.addExtra(Mir.LoadMemoryPie{
   3612                         .register = @enumToInt(reg),
   3613                         .atom_index = mod.declPtr(self.mod_fn.owner_decl).link.macho.local_sym_index,
   3614                         .sym_index = sym_index,
   3615                     }),
   3616                 },
   3617             });
   3618         },
   3619         .memory => |addr| {
   3620             // The value is in memory at a hard-coded address.
   3621             // If the type is a pointer, it means the pointer address is at this memory location.
   3622             try self.genSetReg(ty, reg.to64(), .{ .immediate = addr });
   3623             try self.genLdrRegister(reg, reg.to64(), ty);
   3624         },
   3625         .stack_offset => |off| {
   3626             const abi_size = ty.abiSize(self.target.*);
   3627 
   3628             switch (abi_size) {
   3629                 1, 2, 4, 8 => {
   3630                     const tag: Mir.Inst.Tag = switch (abi_size) {
   3631                         1 => if (ty.isSignedInt()) Mir.Inst.Tag.ldrsb_stack else .ldrb_stack,
   3632                         2 => if (ty.isSignedInt()) Mir.Inst.Tag.ldrsh_stack else .ldrh_stack,
   3633                         4, 8 => .ldr_stack,
   3634                         else => unreachable, // unexpected abi size
   3635                     };
   3636 
   3637                     _ = try self.addInst(.{
   3638                         .tag = tag,
   3639                         .data = .{ .load_store_stack = .{
   3640                             .rt = reg,
   3641                             .offset = @intCast(u32, off),
   3642                         } },
   3643                     });
   3644                 },
   3645                 3, 5, 6, 7 => return self.fail("TODO implement genSetReg types size {}", .{abi_size}),
   3646                 else => unreachable,
   3647             }
   3648         },
   3649     }
   3650 }
   3651 
   3652 fn airPtrToInt(self: *Self, inst: Air.Inst.Index) !void {
   3653     const un_op = self.air.instructions.items(.data)[inst].un_op;
   3654     const result = try self.resolveInst(un_op);
   3655     return self.finishAir(inst, result, .{ un_op, .none, .none });
   3656 }
   3657 
   3658 fn airBitCast(self: *Self, inst: Air.Inst.Index) !void {
   3659     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   3660     const result = try self.resolveInst(ty_op.operand);
   3661     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   3662 }
   3663 
   3664 fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) !void {
   3665     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   3666     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
   3667         const ptr_ty = self.air.typeOf(ty_op.operand);
   3668         const ptr = try self.resolveInst(ty_op.operand);
   3669         const array_ty = ptr_ty.childType();
   3670         const array_len = @intCast(u32, array_ty.arrayLen());
   3671 
   3672         const ptr_bits = self.target.cpu.arch.ptrBitWidth();
   3673         const ptr_bytes = @divExact(ptr_bits, 8);
   3674 
   3675         const stack_offset = try self.allocMem(inst, ptr_bytes * 2, ptr_bytes * 2);
   3676         try self.genSetStack(ptr_ty, stack_offset, ptr);
   3677         try self.genSetStack(Type.initTag(.usize), stack_offset - ptr_bytes, .{ .immediate = array_len });
   3678         break :result MCValue{ .stack_offset = stack_offset };
   3679     };
   3680     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   3681 }
   3682 
   3683 fn airIntToFloat(self: *Self, inst: Air.Inst.Index) !void {
   3684     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   3685     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airIntToFloat for {}", .{
   3686         self.target.cpu.arch,
   3687     });
   3688     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   3689 }
   3690 
   3691 fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void {
   3692     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   3693     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFloatToInt for {}", .{
   3694         self.target.cpu.arch,
   3695     });
   3696     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   3697 }
   3698 
   3699 fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void {
   3700     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   3701     const extra = self.air.extraData(Air.Block, ty_pl.payload);
   3702     _ = extra;
   3703 
   3704     return self.fail("TODO implement airCmpxchg for {}", .{
   3705         self.target.cpu.arch,
   3706     });
   3707 }
   3708 
   3709 fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) !void {
   3710     _ = inst;
   3711     return self.fail("TODO implement airCmpxchg for {}", .{self.target.cpu.arch});
   3712 }
   3713 
   3714 fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) !void {
   3715     _ = inst;
   3716     return self.fail("TODO implement airAtomicLoad for {}", .{self.target.cpu.arch});
   3717 }
   3718 
   3719 fn airAtomicStore(self: *Self, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) !void {
   3720     _ = inst;
   3721     _ = order;
   3722     return self.fail("TODO implement airAtomicStore for {}", .{self.target.cpu.arch});
   3723 }
   3724 
   3725 fn airMemset(self: *Self, inst: Air.Inst.Index) !void {
   3726     _ = inst;
   3727     return self.fail("TODO implement airMemset for {}", .{self.target.cpu.arch});
   3728 }
   3729 
   3730 fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void {
   3731     _ = inst;
   3732     return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch});
   3733 }
   3734 
   3735 fn airTagName(self: *Self, inst: Air.Inst.Index) !void {
   3736     const un_op = self.air.instructions.items(.data)[inst].un_op;
   3737     const operand = try self.resolveInst(un_op);
   3738     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
   3739         _ = operand;
   3740         return self.fail("TODO implement airTagName for aarch64", .{});
   3741     };
   3742     return self.finishAir(inst, result, .{ un_op, .none, .none });
   3743 }
   3744 
   3745 fn airErrorName(self: *Self, inst: Air.Inst.Index) !void {
   3746     const un_op = self.air.instructions.items(.data)[inst].un_op;
   3747     const operand = try self.resolveInst(un_op);
   3748     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
   3749         _ = operand;
   3750         return self.fail("TODO implement airErrorName for aarch64", .{});
   3751     };
   3752     return self.finishAir(inst, result, .{ un_op, .none, .none });
   3753 }
   3754 
   3755 fn airSplat(self: *Self, inst: Air.Inst.Index) !void {
   3756     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   3757     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSplat for {}", .{self.target.cpu.arch});
   3758     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   3759 }
   3760 
   3761 fn airSelect(self: *Self, inst: Air.Inst.Index) !void {
   3762     const pl_op = self.air.instructions.items(.data)[inst].pl_op;
   3763     const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
   3764     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSelect for {}", .{self.target.cpu.arch});
   3765     return self.finishAir(inst, result, .{ pl_op.operand, extra.lhs, extra.rhs });
   3766 }
   3767 
   3768 fn airShuffle(self: *Self, inst: Air.Inst.Index) !void {
   3769     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
   3770     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airShuffle for {}", .{self.target.cpu.arch});
   3771     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
   3772 }
   3773 
   3774 fn airReduce(self: *Self, inst: Air.Inst.Index) !void {
   3775     const reduce = self.air.instructions.items(.data)[inst].reduce;
   3776     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airReduce for aarch64", .{});
   3777     return self.finishAir(inst, result, .{ reduce.operand, .none, .none });
   3778 }
   3779 
   3780 fn airAggregateInit(self: *Self, inst: Air.Inst.Index) !void {
   3781     const vector_ty = self.air.typeOfIndex(inst);
   3782     const len = vector_ty.vectorLen();
   3783     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   3784     const elements = @ptrCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]);
   3785     const result: MCValue = res: {
   3786         if (self.liveness.isUnused(inst)) break :res MCValue.dead;
   3787         return self.fail("TODO implement airAggregateInit for {}", .{self.target.cpu.arch});
   3788     };
   3789 
   3790     if (elements.len <= Liveness.bpi - 1) {
   3791         var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1);
   3792         std.mem.copy(Air.Inst.Ref, &buf, elements);
   3793         return self.finishAir(inst, result, buf);
   3794     }
   3795     var bt = try self.iterateBigTomb(inst, elements.len);
   3796     for (elements) |elem| {
   3797         bt.feed(elem);
   3798     }
   3799     return bt.finishAir(result);
   3800 }
   3801 
   3802 fn airUnionInit(self: *Self, inst: Air.Inst.Index) !void {
   3803     const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
   3804     const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data;
   3805     _ = extra;
   3806     return self.fail("TODO implement airUnionInit for aarch64", .{});
   3807 }
   3808 
   3809 fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void {
   3810     const prefetch = self.air.instructions.items(.data)[inst].prefetch;
   3811     return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none });
   3812 }
   3813 
   3814 fn airMulAdd(self: *Self, inst: Air.Inst.Index) !void {
   3815     const pl_op = self.air.instructions.items(.data)[inst].pl_op;
   3816     const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
   3817     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
   3818         return self.fail("TODO implement airMulAdd for aarch64", .{});
   3819     };
   3820     return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, pl_op.operand });
   3821 }
   3822 
   3823 fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
   3824     // First section of indexes correspond to a set number of constant values.
   3825     const ref_int = @enumToInt(inst);
   3826     if (ref_int < Air.Inst.Ref.typed_value_map.len) {
   3827         const tv = Air.Inst.Ref.typed_value_map[ref_int];
   3828         if (!tv.ty.hasRuntimeBits()) {
   3829             return MCValue{ .none = {} };
   3830         }
   3831         return self.genTypedValue(tv);
   3832     }
   3833 
   3834     // If the type has no codegen bits, no need to store it.
   3835     const inst_ty = self.air.typeOf(inst);
   3836     if (!inst_ty.hasRuntimeBits())
   3837         return MCValue{ .none = {} };
   3838 
   3839     const inst_index = @intCast(Air.Inst.Index, ref_int - Air.Inst.Ref.typed_value_map.len);
   3840     switch (self.air.instructions.items(.tag)[inst_index]) {
   3841         .constant => {
   3842             // Constants have static lifetimes, so they are always memoized in the outer most table.
   3843             const branch = &self.branch_stack.items[0];
   3844             const gop = try branch.inst_table.getOrPut(self.gpa, inst_index);
   3845             if (!gop.found_existing) {
   3846                 const ty_pl = self.air.instructions.items(.data)[inst_index].ty_pl;
   3847                 gop.value_ptr.* = try self.genTypedValue(.{
   3848                     .ty = inst_ty,
   3849                     .val = self.air.values[ty_pl.payload],
   3850                 });
   3851             }
   3852             return gop.value_ptr.*;
   3853         },
   3854         .const_ty => unreachable,
   3855         else => return self.getResolvedInstValue(inst_index),
   3856     }
   3857 }
   3858 
   3859 fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue {
   3860     // Treat each stack item as a "layer" on top of the previous one.
   3861     var i: usize = self.branch_stack.items.len;
   3862     while (true) {
   3863         i -= 1;
   3864         if (self.branch_stack.items[i].inst_table.get(inst)) |mcv| {
   3865             assert(mcv != .dead);
   3866             return mcv;
   3867         }
   3868     }
   3869 }
   3870 
   3871 fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) InnerError!MCValue {
   3872     const ptr_bits = self.target.cpu.arch.ptrBitWidth();
   3873     const ptr_bytes: u64 = @divExact(ptr_bits, 8);
   3874 
   3875     // TODO this feels clunky. Perhaps we should check for it in `genTypedValue`?
   3876     if (tv.ty.zigTypeTag() == .Pointer) blk: {
   3877         if (tv.ty.castPtrToFn()) |_| break :blk;
   3878         if (!tv.ty.elemType2().hasRuntimeBits()) {
   3879             return MCValue.none;
   3880         }
   3881     }
   3882 
   3883     const mod = self.bin_file.options.module.?;
   3884     const decl = mod.declPtr(decl_index);
   3885     mod.markDeclAlive(decl);
   3886 
   3887     if (self.bin_file.cast(link.File.Elf)) |elf_file| {
   3888         const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
   3889         const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes;
   3890         return MCValue{ .memory = got_addr };
   3891     } else if (self.bin_file.cast(link.File.MachO)) |_| {
   3892         // Because MachO is PIE-always-on, we defer memory address resolution until
   3893         // the linker has enough info to perform relocations.
   3894         assert(decl.link.macho.local_sym_index != 0);
   3895         return MCValue{ .got_load = decl.link.macho.local_sym_index };
   3896     } else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
   3897         const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes;
   3898         return MCValue{ .memory = got_addr };
   3899     } else if (self.bin_file.cast(link.File.Plan9)) |p9| {
   3900         try p9.seeDecl(decl_index);
   3901         const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes;
   3902         return MCValue{ .memory = got_addr };
   3903     } else {
   3904         return self.fail("TODO codegen non-ELF const Decl pointer", .{});
   3905     }
   3906     _ = tv;
   3907 }
   3908 
   3909 fn lowerUnnamedConst(self: *Self, tv: TypedValue) InnerError!MCValue {
   3910     log.debug("lowerUnnamedConst: ty = {}, val = {}", .{ tv.ty.fmtDebug(), tv.val.fmtDebug() });
   3911     const local_sym_index = self.bin_file.lowerUnnamedConst(tv, self.mod_fn.owner_decl) catch |err| {
   3912         return self.fail("lowering unnamed constant failed: {s}", .{@errorName(err)});
   3913     };
   3914     if (self.bin_file.cast(link.File.Elf)) |elf_file| {
   3915         const vaddr = elf_file.local_symbols.items[local_sym_index].st_value;
   3916         return MCValue{ .memory = vaddr };
   3917     } else if (self.bin_file.cast(link.File.MachO)) |_| {
   3918         return MCValue{ .direct_load = local_sym_index };
   3919     } else if (self.bin_file.cast(link.File.Coff)) |_| {
   3920         return self.fail("TODO lower unnamed const in COFF", .{});
   3921     } else if (self.bin_file.cast(link.File.Plan9)) |_| {
   3922         return self.fail("TODO lower unnamed const in Plan9", .{});
   3923     } else {
   3924         return self.fail("TODO lower unnamed const", .{});
   3925     }
   3926 }
   3927 
   3928 fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue {
   3929     if (typed_value.val.isUndef())
   3930         return MCValue{ .undef = {} };
   3931 
   3932     if (typed_value.val.castTag(.decl_ref)) |payload| {
   3933         return self.lowerDeclRef(typed_value, payload.data);
   3934     }
   3935     if (typed_value.val.castTag(.decl_ref_mut)) |payload| {
   3936         return self.lowerDeclRef(typed_value, payload.data.decl_index);
   3937     }
   3938     const target = self.target.*;
   3939 
   3940     switch (typed_value.ty.zigTypeTag()) {
   3941         .Pointer => switch (typed_value.ty.ptrSize()) {
   3942             .Slice => {
   3943                 return self.lowerUnnamedConst(typed_value);
   3944             },
   3945             else => {
   3946                 switch (typed_value.val.tag()) {
   3947                     .int_u64 => {
   3948                         return MCValue{ .immediate = typed_value.val.toUnsignedInt(target) };
   3949                     },
   3950                     .slice => {
   3951                         return self.lowerUnnamedConst(typed_value);
   3952                     },
   3953                     else => {
   3954                         return self.fail("TODO codegen more kinds of const pointers: {}", .{typed_value.val.tag()});
   3955                     },
   3956                 }
   3957             },
   3958         },
   3959         .Int => {
   3960             const info = typed_value.ty.intInfo(self.target.*);
   3961             if (info.bits <= 64) {
   3962                 const unsigned = switch (info.signedness) {
   3963                     .signed => blk: {
   3964                         const signed = typed_value.val.toSignedInt();
   3965                         break :blk @bitCast(u64, signed);
   3966                     },
   3967                     .unsigned => typed_value.val.toUnsignedInt(target),
   3968                 };
   3969 
   3970                 return MCValue{ .immediate = unsigned };
   3971             } else {
   3972                 return self.lowerUnnamedConst(typed_value);
   3973             }
   3974         },
   3975         .Bool => {
   3976             return MCValue{ .immediate = @boolToInt(typed_value.val.toBool()) };
   3977         },
   3978         .ComptimeInt => unreachable, // semantic analysis prevents this
   3979         .ComptimeFloat => unreachable, // semantic analysis prevents this
   3980         .Optional => {
   3981             if (typed_value.ty.isPtrLikeOptional()) {
   3982                 if (typed_value.val.isNull())
   3983                     return MCValue{ .immediate = 0 };
   3984 
   3985                 var buf: Type.Payload.ElemType = undefined;
   3986                 return self.genTypedValue(.{
   3987                     .ty = typed_value.ty.optionalChild(&buf),
   3988                     .val = typed_value.val,
   3989                 });
   3990             } else if (typed_value.ty.abiSize(self.target.*) == 1) {
   3991                 return MCValue{ .immediate = @boolToInt(typed_value.val.isNull()) };
   3992             }
   3993             return self.fail("TODO non pointer optionals", .{});
   3994         },
   3995         .Enum => {
   3996             if (typed_value.val.castTag(.enum_field_index)) |field_index| {
   3997                 switch (typed_value.ty.tag()) {
   3998                     .enum_simple => {
   3999                         return MCValue{ .immediate = field_index.data };
   4000                     },
   4001                     .enum_full, .enum_nonexhaustive => {
   4002                         const enum_full = typed_value.ty.cast(Type.Payload.EnumFull).?.data;
   4003                         if (enum_full.values.count() != 0) {
   4004                             const tag_val = enum_full.values.keys()[field_index.data];
   4005                             return self.genTypedValue(.{ .ty = enum_full.tag_ty, .val = tag_val });
   4006                         } else {
   4007                             return MCValue{ .immediate = field_index.data };
   4008                         }
   4009                     },
   4010                     else => unreachable,
   4011                 }
   4012             } else {
   4013                 var int_tag_buffer: Type.Payload.Bits = undefined;
   4014                 const int_tag_ty = typed_value.ty.intTagType(&int_tag_buffer);
   4015                 return self.genTypedValue(.{ .ty = int_tag_ty, .val = typed_value.val });
   4016             }
   4017         },
   4018         .ErrorSet => {
   4019             const err_name = typed_value.val.castTag(.@"error").?.data.name;
   4020             const module = self.bin_file.options.module.?;
   4021             const global_error_set = module.global_error_set;
   4022             const error_index = global_error_set.get(err_name).?;
   4023             return MCValue{ .immediate = error_index };
   4024         },
   4025         .ErrorUnion => {
   4026             const error_type = typed_value.ty.errorUnionSet();
   4027             const payload_type = typed_value.ty.errorUnionPayload();
   4028 
   4029             if (typed_value.val.castTag(.eu_payload)) |pl| {
   4030                 if (!payload_type.hasRuntimeBits()) {
   4031                     // We use the error type directly as the type.
   4032                     return MCValue{ .immediate = 0 };
   4033                 }
   4034 
   4035                 _ = pl;
   4036                 return self.fail("TODO implement error union const of type '{}' (non-error)", .{typed_value.ty.fmtDebug()});
   4037             } else {
   4038                 if (!payload_type.hasRuntimeBits()) {
   4039                     // We use the error type directly as the type.
   4040                     return self.genTypedValue(.{ .ty = error_type, .val = typed_value.val });
   4041                 }
   4042 
   4043                 return self.fail("TODO implement error union const of type '{}' (error)", .{typed_value.ty.fmtDebug()});
   4044             }
   4045         },
   4046         .Struct => {
   4047             return self.lowerUnnamedConst(typed_value);
   4048         },
   4049         else => return self.fail("TODO implement const of type '{}'", .{typed_value.ty.fmtDebug()}),
   4050     }
   4051 }
   4052 
   4053 const CallMCValues = struct {
   4054     args: []MCValue,
   4055     return_value: MCValue,
   4056     stack_byte_count: u32,
   4057     stack_align: u32,
   4058 
   4059     fn deinit(self: *CallMCValues, func: *Self) void {
   4060         func.gpa.free(self.args);
   4061         self.* = undefined;
   4062     }
   4063 };
   4064 
   4065 /// Caller must call `CallMCValues.deinit`.
   4066 fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues {
   4067     const cc = fn_ty.fnCallingConvention();
   4068     const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
   4069     defer self.gpa.free(param_types);
   4070     fn_ty.fnParamTypes(param_types);
   4071     var result: CallMCValues = .{
   4072         .args = try self.gpa.alloc(MCValue, param_types.len),
   4073         // These undefined values must be populated before returning from this function.
   4074         .return_value = undefined,
   4075         .stack_byte_count = undefined,
   4076         .stack_align = undefined,
   4077     };
   4078     errdefer self.gpa.free(result.args);
   4079 
   4080     const ret_ty = fn_ty.fnReturnType();
   4081 
   4082     switch (cc) {
   4083         .Naked => {
   4084             assert(result.args.len == 0);
   4085             result.return_value = .{ .unreach = {} };
   4086             result.stack_byte_count = 0;
   4087             result.stack_align = 1;
   4088             return result;
   4089         },
   4090         .Unspecified, .C => {
   4091             // ARM64 Procedure Call Standard
   4092             var ncrn: usize = 0; // Next Core Register Number
   4093             var nsaa: u32 = 0; // Next stacked argument address
   4094 
   4095             for (param_types) |ty, i| {
   4096                 const param_size = @intCast(u32, ty.abiSize(self.target.*));
   4097                 if (param_size == 0) {
   4098                     result.args[i] = .{ .none = {} };
   4099                     continue;
   4100                 }
   4101 
   4102                 // We round up NCRN only for non-Apple platforms which allow the 16-byte aligned
   4103                 // values to spread across odd-numbered registers.
   4104                 if (ty.abiAlignment(self.target.*) == 16 and !self.target.isDarwin()) {
   4105                     // Round up NCRN to the next even number
   4106                     ncrn += ncrn % 2;
   4107                 }
   4108 
   4109                 if (std.math.divCeil(u32, param_size, 8) catch unreachable <= 8 - ncrn) {
   4110                     if (param_size <= 8) {
   4111                         result.args[i] = .{ .register = registerAlias(c_abi_int_param_regs[ncrn], param_size) };
   4112                         ncrn += 1;
   4113                     } else {
   4114                         return self.fail("TODO MCValues with multiple registers", .{});
   4115                     }
   4116                 } else if (ncrn < 8 and nsaa == 0) {
   4117                     return self.fail("TODO MCValues split between registers and stack", .{});
   4118                 } else {
   4119                     ncrn = 8;
   4120                     // TODO Apple allows the arguments on the stack to be non-8-byte aligned provided
   4121                     // that the entire stack space consumed by the arguments is 8-byte aligned.
   4122                     if (ty.abiAlignment(self.target.*) == 8) {
   4123                         if (nsaa % 8 != 0) {
   4124                             nsaa += 8 - (nsaa % 8);
   4125                         }
   4126                     }
   4127 
   4128                     result.args[i] = .{ .stack_offset = nsaa };
   4129                     nsaa += param_size;
   4130                 }
   4131             }
   4132 
   4133             result.stack_byte_count = nsaa;
   4134             result.stack_align = 16;
   4135         },
   4136         else => return self.fail("TODO implement function parameters for {} on aarch64", .{cc}),
   4137     }
   4138 
   4139     if (ret_ty.zigTypeTag() == .NoReturn) {
   4140         result.return_value = .{ .unreach = {} };
   4141     } else if (!ret_ty.hasRuntimeBits()) {
   4142         result.return_value = .{ .none = {} };
   4143     } else switch (cc) {
   4144         .Naked => unreachable,
   4145         .Unspecified, .C => {
   4146             const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*));
   4147             if (ret_ty_size <= 8) {
   4148                 result.return_value = .{ .register = registerAlias(c_abi_int_return_regs[0], ret_ty_size) };
   4149             } else {
   4150                 return self.fail("TODO support more return types for ARM backend", .{});
   4151             }
   4152         },
   4153         else => return self.fail("TODO implement function return values for {}", .{cc}),
   4154     }
   4155     return result;
   4156 }
   4157 
   4158 /// TODO support scope overrides. Also note this logic is duplicated with `Module.wantSafety`.
   4159 fn wantSafety(self: *Self) bool {
   4160     return switch (self.bin_file.options.optimize_mode) {
   4161         .Debug => true,
   4162         .ReleaseSafe => true,
   4163         .ReleaseFast => false,
   4164         .ReleaseSmall => false,
   4165     };
   4166 }
   4167 
   4168 fn fail(self: *Self, comptime format: []const u8, args: anytype) InnerError {
   4169     @setCold(true);
   4170     assert(self.err_msg == null);
   4171     self.err_msg = try ErrorMsg.create(self.bin_file.allocator, self.src_loc, format, args);
   4172     return error.CodegenFail;
   4173 }
   4174 
   4175 fn failSymbol(self: *Self, comptime format: []const u8, args: anytype) InnerError {
   4176     @setCold(true);
   4177     assert(self.err_msg == null);
   4178     self.err_msg = try ErrorMsg.create(self.bin_file.allocator, self.src_loc, format, args);
   4179     return error.CodegenFail;
   4180 }
   4181 
   4182 fn parseRegName(name: []const u8) ?Register {
   4183     if (@hasDecl(Register, "parseRegName")) {
   4184         return Register.parseRegName(name);
   4185     }
   4186     return std.meta.stringToEnum(Register, name);
   4187 }
   4188 
   4189 fn registerAlias(reg: Register, size_bytes: u64) Register {
   4190     if (size_bytes == 0) {
   4191         unreachable; // should be comptime known
   4192     } else if (size_bytes <= 4) {
   4193         return reg.to32();
   4194     } else if (size_bytes <= 8) {
   4195         return reg.to64();
   4196     } else {
   4197         unreachable; // TODO handle floating-point registers
   4198     }
   4199 }
   4200 
   4201 /// Resolves any aliased registers to the 64-bit wide ones.
   4202 fn toCanonicalReg(reg: Register) Register {
   4203     return reg.to64();
   4204 }