Commit ee6432537e

Andrew Kelley <andrew@ziglang.org>
2021-07-12 08:38:29
stage2: first pass over codegen.zig for AIR memory layout
1 parent ef7080a
src/codegen.zig
@@ -722,16 +722,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         }
 
         fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
-            for (body.instructions) |inst| {
-                try self.ensureProcessDeathCapacity(@popCount(@TypeOf(inst.deaths), inst.deaths));
+            for (body) |inst| {
+                const tomb_bits = self.liveness.getTombBits(inst);
+                try self.ensureProcessDeathCapacity(@popCount(@TypeOf(tomb_bits), tomb_bits));
 
                 const mcv = try self.genFuncInst(inst);
-                if (!inst.isUnused()) {
-                    log.debug("{*} => {}", .{ inst, mcv });
+                if (!self.liveness.isUnused(inst)) {
+                    log.debug("{} => {}", .{ inst, mcv });
                     const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
                     try branch.inst_table.putNoClobber(self.gpa, inst, mcv);
                 }
 
+                // TODO inline this logic into every instruction
                 var i: ir.Inst.DeathsBitIndex = 0;
                 while (inst.getOperand(i)) |operand| : (i += 1) {
                     if (inst.operandDies(i))
@@ -785,8 +787,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         }
 
         /// Asserts there is already capacity to insert into top branch inst_table.
-        fn processDeath(self: *Self, inst: *ir.Inst) void {
-            if (inst.tag == .constant) return; // Constants are immortal.
+        fn processDeath(self: *Self, inst: Air.Inst.Index) void {
+            const air_tags = self.air.instructions.items(.tag);
+            if (air_tags[inst] == .constant) return; // Constants are immortal.
             // When editing this function, note that the logic must synchronize with `reuseOperand`.
             const prev_value = self.getResolvedInstValue(inst);
             const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
@@ -827,74 +830,82 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
-        fn genFuncInst(self: *Self, inst: *ir.Inst) !MCValue {
-            switch (inst.tag) {
-                .add => return self.genAdd(inst.castTag(.add).?),
+        fn genFuncInst(self: *Self, inst: Air.Inst.Index) !MCValue {
+            const air_tags = self.air.instructions.items(.tag);
+            switch (air_tags[inst]) {
+                // zig fmt: off
+                .add     => return self.genAdd(inst.castTag(.add).?),
                 .addwrap => return self.genAddWrap(inst.castTag(.addwrap).?),
-                .alloc => return self.genAlloc(inst.castTag(.alloc).?),
-                .arg => return self.genArg(inst.castTag(.arg).?),
-                .assembly => return self.genAsm(inst.castTag(.assembly).?),
-                .bitcast => return self.genBitCast(inst.castTag(.bitcast).?),
-                .bit_and => return self.genBitAnd(inst.castTag(.bit_and).?),
-                .bit_or => return self.genBitOr(inst.castTag(.bit_or).?),
-                .block => return self.genBlock(inst.castTag(.block).?),
-                .br => return self.genBr(inst.castTag(.br).?),
-                .br_block_flat => return self.genBrBlockFlat(inst.castTag(.br_block_flat).?),
-                .breakpoint => return self.genBreakpoint(inst.src),
-                .br_void => return self.genBrVoid(inst.castTag(.br_void).?),
-                .bool_and => return self.genBoolOp(inst.castTag(.bool_and).?),
-                .bool_or => return self.genBoolOp(inst.castTag(.bool_or).?),
-                .call => return self.genCall(inst.castTag(.call).?),
-                .cmp_lt => return self.genCmp(inst.castTag(.cmp_lt).?, .lt),
+                .sub     => return self.genSub(inst.castTag(.sub).?),
+                .subwrap => return self.genSubWrap(inst.castTag(.subwrap).?),
+                .mul     => return self.genMul(inst.castTag(.mul).?),
+                .mulwrap => return self.genMulWrap(inst.castTag(.mulwrap).?),
+                .div     => return self.genDiv(inst.castTag(.div).?),
+
+                .cmp_lt  => return self.genCmp(inst.castTag(.cmp_lt).?,  .lt),
                 .cmp_lte => return self.genCmp(inst.castTag(.cmp_lte).?, .lte),
-                .cmp_eq => return self.genCmp(inst.castTag(.cmp_eq).?, .eq),
+                .cmp_eq  => return self.genCmp(inst.castTag(.cmp_eq).?,  .eq),
                 .cmp_gte => return self.genCmp(inst.castTag(.cmp_gte).?, .gte),
-                .cmp_gt => return self.genCmp(inst.castTag(.cmp_gt).?, .gt),
+                .cmp_gt  => return self.genCmp(inst.castTag(.cmp_gt).?,  .gt),
                 .cmp_neq => return self.genCmp(inst.castTag(.cmp_neq).?, .neq),
-                .condbr => return self.genCondBr(inst.castTag(.condbr).?),
-                .constant => unreachable, // excluded from function bodies
-                .dbg_stmt => return self.genDbgStmt(inst.castTag(.dbg_stmt).?),
-                .floatcast => return self.genFloatCast(inst.castTag(.floatcast).?),
-                .intcast => return self.genIntCast(inst.castTag(.intcast).?),
-                .is_non_null => return self.genIsNonNull(inst.castTag(.is_non_null).?),
+
+                .bool_and => return self.genBoolOp(inst.castTag(.bool_and).?),
+                .bool_or  => return self.genBoolOp(inst.castTag(.bool_or).?),
+                .bit_and  => return self.genBitAnd(inst.castTag(.bit_and).?),
+                .bit_or   => return self.genBitOr(inst.castTag(.bit_or).?),
+                .xor      => return self.genXor(inst.castTag(.xor).?),
+
+                .alloc           => return self.genAlloc(inst.castTag(.alloc).?),
+                .arg             => return self.genArg(inst.castTag(.arg).?),
+                .assembly        => return self.genAsm(inst.castTag(.assembly).?),
+                .bitcast         => return self.genBitCast(inst.castTag(.bitcast).?),
+                .block           => return self.genBlock(inst.castTag(.block).?),
+                .br              => return self.genBr(inst.castTag(.br).?),
+                .br_block_flat   => return self.genBrBlockFlat(inst.castTag(.br_block_flat).?),
+                .breakpoint      => return self.genBreakpoint(inst.src),
+                .call            => return self.genCall(inst.castTag(.call).?),
+                .cond_br         => return self.genCondBr(inst.castTag(.condbr).?),
+                .dbg_stmt        => return self.genDbgStmt(inst.castTag(.dbg_stmt).?),
+                .floatcast       => return self.genFloatCast(inst.castTag(.floatcast).?),
+                .intcast         => return self.genIntCast(inst.castTag(.intcast).?),
+                .is_non_null     => return self.genIsNonNull(inst.castTag(.is_non_null).?),
                 .is_non_null_ptr => return self.genIsNonNullPtr(inst.castTag(.is_non_null_ptr).?),
-                .is_null => return self.genIsNull(inst.castTag(.is_null).?),
-                .is_null_ptr => return self.genIsNullPtr(inst.castTag(.is_null_ptr).?),
-                .is_non_err => return self.genIsNonErr(inst.castTag(.is_non_err).?),
-                .is_non_err_ptr => return self.genIsNonErrPtr(inst.castTag(.is_non_err_ptr).?),
-                .is_err => return self.genIsErr(inst.castTag(.is_err).?),
-                .is_err_ptr => return self.genIsErrPtr(inst.castTag(.is_err_ptr).?),
-                .load => return self.genLoad(inst.castTag(.load).?),
-                .loop => return self.genLoop(inst.castTag(.loop).?),
-                .not => return self.genNot(inst.castTag(.not).?),
-                .mul => return self.genMul(inst.castTag(.mul).?),
-                .mulwrap => return self.genMulWrap(inst.castTag(.mulwrap).?),
-                .div => return self.genDiv(inst.castTag(.div).?),
-                .ptrtoint => return self.genPtrToInt(inst.castTag(.ptrtoint).?),
-                .ref => return self.genRef(inst.castTag(.ref).?),
-                .ret => return self.genRet(inst.castTag(.ret).?),
-                .retvoid => return self.genRetVoid(inst.castTag(.retvoid).?),
-                .store => return self.genStore(inst.castTag(.store).?),
-                .struct_field_ptr => return self.genStructFieldPtr(inst.castTag(.struct_field_ptr).?),
-                .sub => return self.genSub(inst.castTag(.sub).?),
-                .subwrap => return self.genSubWrap(inst.castTag(.subwrap).?),
-                .switchbr => return self.genSwitch(inst.castTag(.switchbr).?),
-                .unreach => return MCValue{ .unreach = {} },
-                .optional_payload => return self.genOptionalPayload(inst.castTag(.optional_payload).?),
-                .optional_payload_ptr => return self.genOptionalPayloadPtr(inst.castTag(.optional_payload_ptr).?),
-                .unwrap_errunion_err => return self.genUnwrapErrErr(inst.castTag(.unwrap_errunion_err).?),
-                .unwrap_errunion_payload => return self.genUnwrapErrPayload(inst.castTag(.unwrap_errunion_payload).?),
-                .unwrap_errunion_err_ptr => return self.genUnwrapErrErrPtr(inst.castTag(.unwrap_errunion_err_ptr).?),
-                .unwrap_errunion_payload_ptr => return self.genUnwrapErrPayloadPtr(inst.castTag(.unwrap_errunion_payload_ptr).?),
-                .wrap_optional => return self.genWrapOptional(inst.castTag(.wrap_optional).?),
+                .is_null         => return self.genIsNull(inst.castTag(.is_null).?),
+                .is_null_ptr     => return self.genIsNullPtr(inst.castTag(.is_null_ptr).?),
+                .is_non_err      => return self.genIsNonErr(inst.castTag(.is_non_err).?),
+                .is_non_err_ptr  => return self.genIsNonErrPtr(inst.castTag(.is_non_err_ptr).?),
+                .is_err          => return self.genIsErr(inst.castTag(.is_err).?),
+                .is_err_ptr      => return self.genIsErrPtr(inst.castTag(.is_err_ptr).?),
+                .load            => return self.genLoad(inst.castTag(.load).?),
+                .loop            => return self.genLoop(inst.castTag(.loop).?),
+                .not             => return self.genNot(inst.castTag(.not).?),
+                .ptrtoint        => return self.genPtrToInt(inst.castTag(.ptrtoint).?),
+                .ref             => return self.genRef(inst.castTag(.ref).?),
+                .ret             => return self.genRet(inst.castTag(.ret).?),
+                .store           => return self.genStore(inst.castTag(.store).?),
+                .struct_field_ptr=> return self.genStructFieldPtr(inst.castTag(.struct_field_ptr).?),
+                .switchbr        => return self.genSwitch(inst.castTag(.switchbr).?),
+                .varptr          => return self.genVarPtr(inst.castTag(.varptr).?),
+
+                .constant => unreachable, // excluded from function bodies
+                .unreach  => return MCValue{ .unreach = {} },
+
+                .optional_payload           => return self.genOptionalPayload(inst.castTag(.optional_payload).?),
+                .optional_payload_ptr       => return self.genOptionalPayloadPtr(inst.castTag(.optional_payload_ptr).?),
+                .unwrap_errunion_err        => return self.genUnwrapErrErr(inst.castTag(.unwrap_errunion_err).?),
+                .unwrap_errunion_payload    => return self.genUnwrapErrPayload(inst.castTag(.unwrap_errunion_payload).?),
+                .unwrap_errunion_err_ptr    => return self.genUnwrapErrErrPtr(inst.castTag(.unwrap_errunion_err_ptr).?),
+                .unwrap_errunion_payload_ptr=> return self.genUnwrapErrPayloadPtr(inst.castTag(.unwrap_errunion_payload_ptr).?),
+
+                .wrap_optional         => return self.genWrapOptional(inst.castTag(.wrap_optional).?),
                 .wrap_errunion_payload => return self.genWrapErrUnionPayload(inst.castTag(.wrap_errunion_payload).?),
-                .wrap_errunion_err => return self.genWrapErrUnionErr(inst.castTag(.wrap_errunion_err).?),
-                .varptr => return self.genVarPtr(inst.castTag(.varptr).?),
-                .xor => return self.genXor(inst.castTag(.xor).?),
+                .wrap_errunion_err     => return self.genWrapErrUnionErr(inst.castTag(.wrap_errunion_err).?),
+
+                // zig fmt: on
             }
         }
 
-        fn allocMem(self: *Self, inst: *ir.Inst, abi_size: u32, abi_align: u32) !u32 {
+        fn allocMem(self: *Self, inst: Air.Inst.Index, abi_size: u32, abi_align: u32) !u32 {
             if (abi_align > self.stack_align)
                 self.stack_align = abi_align;
             // TODO find a free slot instead of always appending
@@ -910,20 +921,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         }
 
         /// Use a pointer instruction as the basis for allocating stack memory.
-        fn allocMemPtr(self: *Self, inst: *ir.Inst) !u32 {
-            const elem_ty = inst.ty.elemType();
+        fn allocMemPtr(self: *Self, inst: Air.Inst.Index) !u32 {
+            const elem_ty = self.air.getType(inst).elemType();
             const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch {
-                return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty});
+                return self.fail("type '{}' too big to fit into stack frame", .{elem_ty});
             };
             // TODO swap this for inst.ty.ptrAlign
             const abi_align = elem_ty.abiAlignment(self.target.*);
             return self.allocMem(inst, abi_size, abi_align);
         }
 
-        fn allocRegOrMem(self: *Self, inst: *ir.Inst, reg_ok: bool) !MCValue {
+        fn allocRegOrMem(self: *Self, inst: Air.Inst.Index, reg_ok: bool) !MCValue {
             const elem_ty = inst.ty;
             const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch {
-                return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty});
+                return self.fail("type '{}' too big to fit into stack frame", .{elem_ty});
             };
             const abi_align = elem_ty.abiAlignment(self.target.*);
             if (abi_align > self.stack_align)
@@ -943,72 +954,75 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return MCValue{ .stack_offset = stack_offset };
         }
 
-        pub fn spillInstruction(self: *Self, src: LazySrcLoc, reg: Register, inst: *ir.Inst) !void {
+        pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void {
             const stack_mcv = try self.allocRegOrMem(inst, false);
             log.debug("spilling {*} to stack mcv {any}", .{ inst, stack_mcv });
             const reg_mcv = self.getResolvedInstValue(inst);
             assert(reg == toCanonicalReg(reg_mcv.register));
             const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
             try branch.inst_table.put(self.gpa, inst, stack_mcv);
-            try self.genSetStack(src, inst.ty, stack_mcv.stack_offset, reg_mcv);
+            try self.genSetStack(inst.ty, stack_mcv.stack_offset, reg_mcv);
         }
 
         /// Copies a value to a register without tracking the register. The register is not considered
         /// allocated. A second call to `copyToTmpRegister` may return the same register.
         /// This can have a side effect of spilling instructions to the stack to free up a register.
-        fn copyToTmpRegister(self: *Self, src: LazySrcLoc, ty: Type, mcv: MCValue) !Register {
+        fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register {
             const reg = try self.register_manager.allocReg(null, &.{});
-            try self.genSetReg(src, ty, reg, mcv);
+            try self.genSetReg(ty, reg, mcv);
             return reg;
         }
 
         /// Allocates a new register and copies `mcv` into it.
         /// `reg_owner` is the instruction that gets associated with the register in the register table.
         /// This can have a side effect of spilling instructions to the stack to free up a register.
-        fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue {
+        fn copyToNewRegister(self: *Self, reg_owner: Air.Inst.Index, mcv: MCValue) !MCValue {
             const reg = try self.register_manager.allocReg(reg_owner, &.{});
-            try self.genSetReg(reg_owner.src, reg_owner.ty, reg, mcv);
+            try self.genSetReg(reg_owner.ty, reg, mcv);
             return MCValue{ .register = reg };
         }
 
-        fn genAlloc(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
-            const stack_offset = try self.allocMemPtr(&inst.base);
+        fn genAlloc(self: *Self, inst: Air.Inst.Index) !MCValue {
+            const stack_offset = try self.allocMemPtr(inst);
             return MCValue{ .ptr_stack_offset = stack_offset };
         }
 
-        fn genFloatCast(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn genFloatCast(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement floatCast for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement floatCast for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genIntCast(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn genIntCast(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
 
-            const operand = try self.resolveInst(inst.operand);
-            const info_a = inst.operand.ty.intInfo(self.target.*);
-            const info_b = inst.base.ty.intInfo(self.target.*);
+            const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+            const operand_ty = self.air.getType(ty_op.operand);
+            const operand = try self.resolveInst(ty_op.operand);
+            const info_a = operand_ty.intInfo(self.target.*);
+            const info_b = self.air.getType(inst).intInfo(self.target.*);
             if (info_a.signedness != info_b.signedness)
-                return self.fail(inst.base.src, "TODO gen intcast sign safety in semantic analysis", .{});
+                return self.fail("TODO gen intcast sign safety in semantic analysis", .{});
 
             if (info_a.bits == info_b.bits)
                 return operand;
 
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement intCast for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement intCast for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genNot(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn genNot(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
-            const operand = try self.resolveInst(inst.operand);
+            const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+            const operand = try self.resolveInst(ty_op.operand);
             switch (operand) {
                 .dead => unreachable,
                 .unreach => unreachable,
@@ -1037,216 +1051,209 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
             switch (arch) {
                 .x86_64 => {
-                    var imm = ir.Inst.Constant{
-                        .base = .{
-                            .tag = .constant,
-                            .deaths = 0,
-                            .ty = inst.operand.ty,
-                            .src = inst.operand.src,
-                        },
-                        .val = Value.initTag(.bool_true),
-                    };
-                    return try self.genX8664BinMath(&inst.base, inst.operand, &imm.base);
+                    return try self.genX8664BinMath(inst, ty_op.operand, .bool_true);
                 },
                 .arm, .armeb => {
-                    var imm = ir.Inst.Constant{
-                        .base = .{
-                            .tag = .constant,
-                            .deaths = 0,
-                            .ty = inst.operand.ty,
-                            .src = inst.operand.src,
-                        },
-                        .val = Value.initTag(.bool_true),
-                    };
-                    return try self.genArmBinOp(&inst.base, inst.operand, &imm.base, .not);
+                    return try self.genArmBinOp(inst, ty_op.operand, .bool_true, .not);
                 },
-                else => return self.fail(inst.base.src, "TODO implement NOT for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement NOT for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genAdd(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
+        fn genAdd(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
             switch (arch) {
                 .x86_64 => {
-                    return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs);
+                    return try self.genX8664BinMath(inst, bin_op.lhs, bin_op.rhs);
                 },
-                .arm, .armeb => return try self.genArmBinOp(&inst.base, inst.lhs, inst.rhs, .add),
-                else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}),
+                .arm, .armeb => return try self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .add),
+                else => return self.fail("TODO implement add for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genAddWrap(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
+        fn genAddWrap(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+            _ = bin_op;
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement addwrap for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement addwrap for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genMul(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
+        fn genMul(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
             switch (arch) {
-                .x86_64 => return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs),
-                .arm, .armeb => return try self.genArmMul(&inst.base, inst.lhs, inst.rhs),
-                else => return self.fail(inst.base.src, "TODO implement mul for {}", .{self.target.cpu.arch}),
+                .x86_64 => return try self.genX8664BinMath(inst, bin_op.lhs, bin_op.rhs),
+                .arm, .armeb => return try self.genArmMul(inst, bin_op.lhs, bin_op.rhs),
+                else => return self.fail("TODO implement mul for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genMulWrap(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
+        fn genMulWrap(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+            _ = bin_op;
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement mulwrap for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement mulwrap for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genDiv(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
+        fn genDiv(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+            _ = bin_op;
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement div for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement div for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genBitAnd(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
+        fn genBitAnd(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
             switch (arch) {
-                .arm, .armeb => return try self.genArmBinOp(&inst.base, inst.lhs, inst.rhs, .bit_and),
-                else => return self.fail(inst.base.src, "TODO implement bitwise and for {}", .{self.target.cpu.arch}),
+                .arm, .armeb => return try self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .bit_and),
+                else => return self.fail("TODO implement bitwise and for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genBitOr(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
+        fn genBitOr(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
             switch (arch) {
-                .arm, .armeb => return try self.genArmBinOp(&inst.base, inst.lhs, inst.rhs, .bit_or),
-                else => return self.fail(inst.base.src, "TODO implement bitwise or for {}", .{self.target.cpu.arch}),
+                .arm, .armeb => return try self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .bit_or),
+                else => return self.fail("TODO implement bitwise or for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genXor(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
+        fn genXor(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
             switch (arch) {
-                .arm, .armeb => return try self.genArmBinOp(&inst.base, inst.lhs, inst.rhs, .xor),
-                else => return self.fail(inst.base.src, "TODO implement xor for {}", .{self.target.cpu.arch}),
+                .arm, .armeb => return try self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .xor),
+                else => return self.fail("TODO implement xor for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genOptionalPayload(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn genOptionalPayload(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement .optional_payload for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement .optional_payload for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genOptionalPayloadPtr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn genOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement .optional_payload_ptr for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement .optional_payload_ptr for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genUnwrapErrErr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn genUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement unwrap error union error for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement unwrap error union error for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genUnwrapErrPayload(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn genUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement unwrap error union payload for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement unwrap error union payload for {}", .{self.target.cpu.arch}),
             }
         }
         // *(E!T) -> E
-        fn genUnwrapErrErrPtr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn genUnwrapErrErrPtr(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement unwrap error union error ptr for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement unwrap error union error ptr for {}", .{self.target.cpu.arch}),
             }
         }
         // *(E!T) -> *T
-        fn genUnwrapErrPayloadPtr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn genUnwrapErrPayloadPtr(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement unwrap error union payload ptr for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement unwrap error union payload ptr for {}", .{self.target.cpu.arch}),
             }
         }
-        fn genWrapOptional(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
-            const optional_ty = inst.base.ty;
-
+        fn genWrapOptional(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
 
+            const optional_ty = self.air.getType(inst);
+
             // Optional type is just a boolean true
             if (optional_ty.abiSize(self.target.*) == 1)
                 return MCValue{ .immediate = 1 };
 
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement wrap optional for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement wrap optional for {}", .{self.target.cpu.arch}),
             }
         }
 
         /// T to E!T
-        fn genWrapErrUnionPayload(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn genWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
 
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement wrap errunion payload for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement wrap errunion payload for {}", .{self.target.cpu.arch}),
             }
         }
 
         /// E to E!T
-        fn genWrapErrUnionErr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn genWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
 
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement wrap errunion error for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement wrap errunion error for {}", .{self.target.cpu.arch}),
             }
         }
-        fn genVarPtr(self: *Self, inst: *ir.Inst.VarPtr) !MCValue {
+        fn genVarPtr(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
 
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement varptr for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement varptr for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn reuseOperand(self: *Self, inst: *ir.Inst, op_index: ir.Inst.DeathsBitIndex, mcv: MCValue) bool {
-            if (!inst.operandDies(op_index))
+        fn reuseOperand(self: *Self, inst: Air.Inst.Index, op_index: u2, mcv: MCValue) bool {
+            if (!self.liveness.operandDies(inst, op_index))
                 return false;
 
             switch (mcv) {
@@ -1258,16 +1265,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             self.register_manager.registers[index] = inst;
                         }
                     }
-                    log.debug("reusing {} => {*}", .{ reg, inst });
+                    log.debug("reusing {} => {}", .{ reg, inst });
                 },
                 .stack_offset => |off| {
-                    log.debug("reusing stack offset {} => {*}", .{ off, inst });
+                    log.debug("reusing stack offset {} => {}", .{ off, inst });
                 },
                 else => return false,
             }
 
             // Prevent the operand deaths processing code from deallocating it.
-            inst.clearOperandDeath(op_index);
+            self.liveness.clearOperandDeath(inst, op_index);
 
             // That makes us responsible for doing the rest of the stuff that processDeath would have done.
             const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
@@ -1276,22 +1283,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return true;
         }
 
-        fn genLoad(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
-            const elem_ty = inst.base.ty;
-            if (!elem_ty.hasCodeGenBits())
-                return MCValue.none;
-            const ptr = try self.resolveInst(inst.operand);
-            const is_volatile = inst.operand.ty.isVolatilePtr();
-            if (inst.base.isUnused() and !is_volatile)
-                return MCValue.dead;
-            const dst_mcv: MCValue = blk: {
-                if (self.reuseOperand(&inst.base, 0, ptr)) {
-                    // The MCValue that holds the pointer can be re-used as the value.
-                    break :blk ptr;
-                } else {
-                    break :blk try self.allocRegOrMem(&inst.base, true);
-                }
-            };
+        fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue) !void {
             switch (ptr) {
                 .none => unreachable,
                 .undef => unreachable,
@@ -1299,31 +1291,51 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .dead => unreachable,
                 .compare_flags_unsigned => unreachable,
                 .compare_flags_signed => unreachable,
-                .immediate => |imm| try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .memory = imm }),
-                .ptr_stack_offset => |off| try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .stack_offset = off }),
+                .immediate => |imm| try self.setRegOrMem(elem_ty, dst_mcv, .{ .memory = imm }),
+                .ptr_stack_offset => |off| try self.setRegOrMem(elem_ty, dst_mcv, .{ .stack_offset = off }),
                 .ptr_embedded_in_code => |off| {
-                    try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .embedded_in_code = off });
+                    try self.setRegOrMem(elem_ty, dst_mcv, .{ .embedded_in_code = off });
                 },
                 .embedded_in_code => {
-                    return self.fail(inst.base.src, "TODO implement loading from MCValue.embedded_in_code", .{});
+                    return self.fail("TODO implement loading from MCValue.embedded_in_code", .{});
                 },
                 .register => {
-                    return self.fail(inst.base.src, "TODO implement loading from MCValue.register", .{});
+                    return self.fail("TODO implement loading from MCValue.register", .{});
                 },
                 .memory => {
-                    return self.fail(inst.base.src, "TODO implement loading from MCValue.memory", .{});
+                    return self.fail("TODO implement loading from MCValue.memory", .{});
                 },
                 .stack_offset => {
-                    return self.fail(inst.base.src, "TODO implement loading from MCValue.stack_offset", .{});
+                    return self.fail("TODO implement loading from MCValue.stack_offset", .{});
                 },
             }
+        }
+
+        fn genLoad(self: *Self, inst: Air.Inst.Index) !MCValue {
+            const elem_ty = self.air.getType(inst);
+            if (!elem_ty.hasCodeGenBits())
+                return MCValue.none;
+            const ptr = try self.resolveInst(inst.operand);
+            const is_volatile = inst.operand.ty.isVolatilePtr();
+            if (self.liveness.isUnused(inst) and !is_volatile)
+                return MCValue.dead;
+            const dst_mcv: MCValue = blk: {
+                if (self.reuseOperand(inst, 0, ptr)) {
+                    // The MCValue that holds the pointer can be re-used as the value.
+                    break :blk ptr;
+                } else {
+                    break :blk try self.allocRegOrMem(inst, true);
+                }
+            };
+            self.load(dst_mcv, ptr);
             return dst_mcv;
         }
 
-        fn genStore(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
-            const ptr = try self.resolveInst(inst.lhs);
-            const value = try self.resolveInst(inst.rhs);
-            const elem_ty = inst.rhs.ty;
+        fn genStore(self: *Self, inst: Air.Inst.Index) !MCValue {
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+            const ptr = try self.resolveInst(bin_op.lhs);
+            const value = try self.resolveInst(bin_op.rhs);
+            const elem_ty = self.getType(bin_op.rhs);
             switch (ptr) {
                 .none => unreachable,
                 .undef => unreachable,
@@ -1332,57 +1344,60 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .compare_flags_unsigned => unreachable,
                 .compare_flags_signed => unreachable,
                 .immediate => |imm| {
-                    try self.setRegOrMem(inst.base.src, elem_ty, .{ .memory = imm }, value);
+                    try self.setRegOrMem(elem_ty, .{ .memory = imm }, value);
                 },
                 .ptr_stack_offset => |off| {
-                    try self.genSetStack(inst.base.src, elem_ty, off, value);
+                    try self.genSetStack(elem_ty, off, value);
                 },
                 .ptr_embedded_in_code => |off| {
-                    try self.setRegOrMem(inst.base.src, elem_ty, .{ .embedded_in_code = off }, value);
+                    try self.setRegOrMem(elem_ty, .{ .embedded_in_code = off }, value);
                 },
                 .embedded_in_code => {
-                    return self.fail(inst.base.src, "TODO implement storing to MCValue.embedded_in_code", .{});
+                    return self.fail("TODO implement storing to MCValue.embedded_in_code", .{});
                 },
                 .register => {
-                    return self.fail(inst.base.src, "TODO implement storing to MCValue.register", .{});
+                    return self.fail("TODO implement storing to MCValue.register", .{});
                 },
                 .memory => {
-                    return self.fail(inst.base.src, "TODO implement storing to MCValue.memory", .{});
+                    return self.fail("TODO implement storing to MCValue.memory", .{});
                 },
                 .stack_offset => {
-                    return self.fail(inst.base.src, "TODO implement storing to MCValue.stack_offset", .{});
+                    return self.fail("TODO implement storing to MCValue.stack_offset", .{});
                 },
             }
             return .none;
         }
 
-        fn genStructFieldPtr(self: *Self, inst: *ir.Inst.StructFieldPtr) !MCValue {
-            return self.fail(inst.base.src, "TODO implement codegen struct_field_ptr", .{});
+        fn genStructFieldPtr(self: *Self, inst: Air.Inst.Index) !MCValue {
+            const struct_field_ptr = self.air.instructions.items(.data)[inst].struct_field_ptr;
+            _ = struct_field_ptr;
+            return self.fail("TODO implement codegen struct_field_ptr", .{});
         }
 
-        fn genSub(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
+        fn genSub(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
             switch (arch) {
-                .x86_64 => {
-                    return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs);
-                },
-                .arm, .armeb => return try self.genArmBinOp(&inst.base, inst.lhs, inst.rhs, .sub),
-                else => return self.fail(inst.base.src, "TODO implement sub for {}", .{self.target.cpu.arch}),
+                .x86_64 => return self.genX8664BinMath(inst, bin_op.lhs, bin_op.rhs),
+                .arm, .armeb => return self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .sub),
+                else => return self.fail("TODO implement sub for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genSubWrap(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
+        fn genSubWrap(self: *Self, inst: Air.Inst.Index) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+            _ = bin_op;
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement subwrap for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement subwrap for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn armOperandShouldBeRegister(self: *Self, src: LazySrcLoc, mcv: MCValue) !bool {
+        fn armOperandShouldBeRegister(self: *Self, mcv: MCValue) !bool {
             return switch (mcv) {
                 .none => unreachable,
                 .undef => unreachable,
@@ -1392,7 +1407,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .ptr_stack_offset => unreachable,
                 .ptr_embedded_in_code => unreachable,
                 .immediate => |imm| blk: {
-                    if (imm > std.math.maxInt(u32)) return self.fail(src, "TODO ARM binary arithmetic immediate larger than u32", .{});
+                    if (imm > std.math.maxInt(u32)) return self.fail("TODO ARM binary arithmetic immediate larger than u32", .{});
 
                     // Load immediate into register if it doesn't fit
                     // in an operand
@@ -1406,14 +1421,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             };
         }
 
-        fn genArmBinOp(self: *Self, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst, op: ir.Inst.Tag) !MCValue {
+        fn genArmBinOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: Air.Inst.Ref, op: ir.Inst.Tag) !MCValue {
             const lhs = try self.resolveInst(op_lhs);
             const rhs = try self.resolveInst(op_rhs);
 
             const lhs_is_register = lhs == .register;
             const rhs_is_register = rhs == .register;
-            const lhs_should_be_register = try self.armOperandShouldBeRegister(op_lhs.src, lhs);
-            const rhs_should_be_register = try self.armOperandShouldBeRegister(op_rhs.src, rhs);
+            const lhs_should_be_register = try self.armOperandShouldBeRegister(lhs);
+            const rhs_should_be_register = try self.armOperandShouldBeRegister(rhs);
             const reuse_lhs = lhs_is_register and self.reuseOperand(inst, 0, lhs);
             const reuse_rhs = !reuse_lhs and rhs_is_register and self.reuseOperand(inst, 1, rhs);
 
@@ -1486,14 +1501,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
             // Move the operands to the newly allocated registers
             if (lhs_mcv == .register and !lhs_is_register) {
-                try self.genSetReg(op_lhs.src, op_lhs.ty, lhs_mcv.register, lhs);
+                try self.genSetReg(op_lhs.ty, lhs_mcv.register, lhs);
             }
             if (rhs_mcv == .register and !rhs_is_register) {
-                try self.genSetReg(op_rhs.src, op_rhs.ty, rhs_mcv.register, rhs);
+                try self.genSetReg(op_rhs.ty, rhs_mcv.register, rhs);
             }
 
             try self.genArmBinOpCode(
-                inst.src,
                 dst_mcv.register,
                 lhs_mcv,
                 rhs_mcv,
@@ -1505,14 +1519,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
         fn genArmBinOpCode(
             self: *Self,
-            src: LazySrcLoc,
             dst_reg: Register,
             lhs_mcv: MCValue,
             rhs_mcv: MCValue,
             swap_lhs_and_rhs: bool,
             op: ir.Inst.Tag,
         ) !void {
-            _ = src;
             assert(lhs_mcv == .register or rhs_mcv == .register);
 
             const op1 = if (swap_lhs_and_rhs) rhs_mcv.register else lhs_mcv.register;
@@ -1561,7 +1573,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
-        fn genArmMul(self: *Self, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst) !MCValue {
+        fn genArmMul(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Index, op_rhs: Air.Inst.Index) !MCValue {
             const lhs = try self.resolveInst(op_lhs);
             const rhs = try self.resolveInst(op_rhs);
 
@@ -1618,10 +1630,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
             // Move the operands to the newly allocated registers
             if (!lhs_is_register) {
-                try self.genSetReg(op_lhs.src, op_lhs.ty, lhs_mcv.register, lhs);
+                try self.genSetReg(op_lhs.ty, lhs_mcv.register, lhs);
             }
             if (!rhs_is_register) {
-                try self.genSetReg(op_rhs.src, op_rhs.ty, rhs_mcv.register, rhs);
+                try self.genSetReg(op_rhs.ty, rhs_mcv.register, rhs);
             }
 
             writeInt(u32, try self.code.addManyAsArray(4), Instruction.mul(.al, dst_mcv.register, lhs_mcv.register, rhs_mcv.register).toU32());
@@ -1631,7 +1643,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         /// Perform "binary" operators, excluding comparisons.
         /// Currently, the following ops are supported:
         /// ADD, SUB, XOR, OR, AND
-        fn genX8664BinMath(self: *Self, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst) !MCValue {
+        fn genX8664BinMath(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: Air.Inst.Ref) !MCValue {
             // We'll handle these ops in two steps.
             // 1) Prepare an output location (register or memory)
             //    This location will be the location of the operand that dies (if one exists)
@@ -1654,7 +1666,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             // as the result MCValue.
             var dst_mcv: MCValue = undefined;
             var src_mcv: MCValue = undefined;
-            var src_inst: *ir.Inst = undefined;
+            var src_inst: Air.Inst.Index = undefined;
             if (self.reuseOperand(inst, 0, lhs)) {
                 // LHS dies; use it as the destination.
                 // Both operands cannot be memory.
@@ -1696,20 +1708,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             switch (src_mcv) {
                 .immediate => |imm| {
                     if (imm > math.maxInt(u31)) {
-                        src_mcv = MCValue{ .register = try self.copyToTmpRegister(src_inst.src, Type.initTag(.u64), src_mcv) };
+                        src_mcv = MCValue{ .register = try self.copyToTmpRegister(Type.initTag(.u64), src_mcv) };
                     }
                 },
                 else => {},
             }
 
             // Now for step 2, we perform the actual op
-            switch (inst.tag) {
+            const air_tags = self.air.instructions.items(.tag);
+            switch (air_tags[inst]) {
                 // TODO: Generate wrapping and non-wrapping versions separately
-                .add, .addwrap => try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, 0, 0x00),
-                .bool_or, .bit_or => try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, 1, 0x08),
-                .bool_and, .bit_and => try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, 4, 0x20),
-                .sub, .subwrap => try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, 5, 0x28),
-                .xor, .not => try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, 6, 0x30),
+                .add, .addwrap => try self.genX8664BinMathCode(inst.ty, dst_mcv, src_mcv, 0, 0x00),
+                .bool_or, .bit_or => try self.genX8664BinMathCode(inst.ty, dst_mcv, src_mcv, 1, 0x08),
+                .bool_and, .bit_and => try self.genX8664BinMathCode(inst.ty, dst_mcv, src_mcv, 4, 0x20),
+                .sub, .subwrap => try self.genX8664BinMathCode(inst.ty, dst_mcv, src_mcv, 5, 0x28),
+                .xor, .not => try self.genX8664BinMathCode(inst.ty, dst_mcv, src_mcv, 6, 0x30),
 
                 .mul, .mulwrap => try self.genX8664Imul(inst.src, inst.ty, dst_mcv, src_mcv),
                 else => unreachable,
@@ -1719,16 +1732,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         }
 
         /// Wrap over Instruction.encodeInto to translate errors
-        fn encodeX8664Instruction(
-            self: *Self,
-            src: LazySrcLoc,
-            inst: Instruction,
-        ) !void {
+        fn encodeX8664Instruction(self: *Self, inst: Instruction) !void {
             inst.encodeInto(self.code) catch |err| {
                 if (err == error.OutOfMemory)
                     return error.OutOfMemory
                 else
-                    return self.fail(src, "Instruction.encodeInto failed because {s}", .{@errorName(err)});
+                    return self.fail("Instruction.encodeInto failed because {s}", .{@errorName(err)});
             };
         }
 
@@ -1800,7 +1809,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         /// d3 /opx | *r/m16/32/64*, CL    (for context, CL is register 1)
         fn genX8664BinMathCode(
             self: *Self,
-            src: LazySrcLoc,
             dst_ty: Type,
             dst_mcv: MCValue,
             src_mcv: MCValue,
@@ -1818,7 +1826,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .register => |dst_reg| {
                     switch (src_mcv) {
                         .none => unreachable,
-                        .undef => try self.genSetReg(src, dst_ty, dst_reg, .undef),
+                        .undef => try self.genSetReg(dst_ty, dst_reg, .undef),
                         .dead, .unreach => unreachable,
                         .ptr_stack_offset => unreachable,
                         .ptr_embedded_in_code => unreachable,
@@ -1872,7 +1880,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             }
                         },
                         .embedded_in_code, .memory => {
-                            return self.fail(src, "TODO implement x86 ADD/SUB/CMP source memory", .{});
+                            return self.fail("TODO implement x86 ADD/SUB/CMP source memory", .{});
                         },
                         .stack_offset => |off| {
                             // register, indirect use mr + 3
@@ -1880,7 +1888,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             const abi_size = dst_ty.abiSize(self.target.*);
                             const adj_off = off + abi_size;
                             if (off > math.maxInt(i32)) {
-                                return self.fail(src, "stack offset too large", .{});
+                                return self.fail("stack offset too large", .{});
                             }
                             const encoder = try X8664Encoder.init(self.code, 7);
                             encoder.rex(.{
@@ -1903,17 +1911,17 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             }
                         },
                         .compare_flags_unsigned => {
-                            return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{});
+                            return self.fail("TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{});
                         },
                         .compare_flags_signed => {
-                            return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (signed)", .{});
+                            return self.fail("TODO implement x86 ADD/SUB/CMP source compare flag (signed)", .{});
                         },
                     }
                 },
                 .stack_offset => |off| {
                     switch (src_mcv) {
                         .none => unreachable,
-                        .undef => return self.genSetStack(src, dst_ty, off, .undef),
+                        .undef => return self.genSetStack(dst_ty, off, .undef),
                         .dead, .unreach => unreachable,
                         .ptr_stack_offset => unreachable,
                         .ptr_embedded_in_code => unreachable,
@@ -1922,21 +1930,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         },
                         .immediate => |imm| {
                             _ = imm;
-                            return self.fail(src, "TODO implement x86 ADD/SUB/CMP source immediate", .{});
+                            return self.fail("TODO implement x86 ADD/SUB/CMP source immediate", .{});
                         },
                         .embedded_in_code, .memory, .stack_offset => {
-                            return self.fail(src, "TODO implement x86 ADD/SUB/CMP source memory", .{});
+                            return self.fail("TODO implement x86 ADD/SUB/CMP source memory", .{});
                         },
                         .compare_flags_unsigned => {
-                            return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{});
+                            return self.fail("TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{});
                         },
                         .compare_flags_signed => {
-                            return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (signed)", .{});
+                            return self.fail("TODO implement x86 ADD/SUB/CMP source compare flag (signed)", .{});
                         },
                     }
                 },
                 .embedded_in_code, .memory => {
-                    return self.fail(src, "TODO implement x86 ADD/SUB/CMP destination memory", .{});
+                    return self.fail("TODO implement x86 ADD/SUB/CMP destination memory", .{});
                 },
             }
         }
@@ -1960,7 +1968,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .register => |dst_reg| {
                     switch (src_mcv) {
                         .none => unreachable,
-                        .undef => try self.genSetReg(src, dst_ty, dst_reg, .undef),
+                        .undef => try self.genSetReg(dst_ty, dst_reg, .undef),
                         .dead, .unreach => unreachable,
                         .ptr_stack_offset => unreachable,
                         .ptr_embedded_in_code => unreachable,
@@ -2026,31 +2034,31 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 );
                                 encoder.imm32(@intCast(i32, imm));
                             } else {
-                                const src_reg = try self.copyToTmpRegister(src, dst_ty, src_mcv);
+                                const src_reg = try self.copyToTmpRegister(dst_ty, src_mcv);
                                 return self.genX8664Imul(src, dst_ty, dst_mcv, MCValue{ .register = src_reg });
                             }
                         },
                         .embedded_in_code, .memory, .stack_offset => {
-                            return self.fail(src, "TODO implement x86 multiply source memory", .{});
+                            return self.fail("TODO implement x86 multiply source memory", .{});
                         },
                         .compare_flags_unsigned => {
-                            return self.fail(src, "TODO implement x86 multiply source compare flag (unsigned)", .{});
+                            return self.fail("TODO implement x86 multiply source compare flag (unsigned)", .{});
                         },
                         .compare_flags_signed => {
-                            return self.fail(src, "TODO implement x86 multiply source compare flag (signed)", .{});
+                            return self.fail("TODO implement x86 multiply source compare flag (signed)", .{});
                         },
                     }
                 },
                 .stack_offset => |off| {
                     switch (src_mcv) {
                         .none => unreachable,
-                        .undef => return self.genSetStack(src, dst_ty, off, .undef),
+                        .undef => return self.genSetStack(dst_ty, off, .undef),
                         .dead, .unreach => unreachable,
                         .ptr_stack_offset => unreachable,
                         .ptr_embedded_in_code => unreachable,
                         .register => |src_reg| {
                             // copy dst to a register
-                            const dst_reg = try self.copyToTmpRegister(src, dst_ty, dst_mcv);
+                            const dst_reg = try self.copyToTmpRegister(dst_ty, dst_mcv);
                             // multiply into dst_reg
                             // register, register
                             // Use the following imul opcode
@@ -2068,34 +2076,34 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 src_reg.low_id(),
                             );
                             // copy dst_reg back out
-                            return self.genSetStack(src, dst_ty, off, MCValue{ .register = dst_reg });
+                            return self.genSetStack(dst_ty, off, MCValue{ .register = dst_reg });
                         },
                         .immediate => |imm| {
                             _ = imm;
-                            return self.fail(src, "TODO implement x86 multiply source immediate", .{});
+                            return self.fail("TODO implement x86 multiply source immediate", .{});
                         },
                         .embedded_in_code, .memory, .stack_offset => {
-                            return self.fail(src, "TODO implement x86 multiply source memory", .{});
+                            return self.fail("TODO implement x86 multiply source memory", .{});
                         },
                         .compare_flags_unsigned => {
-                            return self.fail(src, "TODO implement x86 multiply source compare flag (unsigned)", .{});
+                            return self.fail("TODO implement x86 multiply source compare flag (unsigned)", .{});
                         },
                         .compare_flags_signed => {
-                            return self.fail(src, "TODO implement x86 multiply source compare flag (signed)", .{});
+                            return self.fail("TODO implement x86 multiply source compare flag (signed)", .{});
                         },
                     }
                 },
                 .embedded_in_code, .memory => {
-                    return self.fail(src, "TODO implement x86 multiply destination memory", .{});
+                    return self.fail("TODO implement x86 multiply destination memory", .{});
                 },
             }
         }
 
-        fn genX8664ModRMRegToStack(self: *Self, src: LazySrcLoc, ty: Type, off: u32, reg: Register, opcode: u8) !void {
+        fn genX8664ModRMRegToStack(self: *Self, ty: Type, off: u32, reg: Register, opcode: u8) !void {
             const abi_size = ty.abiSize(self.target.*);
             const adj_off = off + abi_size;
             if (off > math.maxInt(i32)) {
-                return self.fail(src, "stack offset too large", .{});
+                return self.fail("stack offset too large", .{});
             }
 
             const i_adj_off = -@intCast(i32, adj_off);
@@ -2122,8 +2130,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
-        fn genArgDbgInfo(self: *Self, inst: *ir.Inst.Arg, mcv: MCValue) !void {
+        fn genArgDbgInfo(self: *Self, inst: Air.Inst.Index, mcv: MCValue) !void {
             const name_with_null = inst.name[0 .. mem.lenZ(inst.name) + 1];
+            const ty = self.air.getType(inst);
 
             switch (mcv) {
                 .register => |reg| {
@@ -2136,7 +2145,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 reg.dwarfLocOp(),
                             });
                             try dbg_out.dbg_info.ensureCapacity(dbg_out.dbg_info.items.len + 5 + name_with_null.len);
-                            try self.addDbgInfoTypeReloc(inst.base.ty); // DW.AT_type,  DW.FORM_ref4
+                            try self.addDbgInfoTypeReloc(ty); // DW.AT_type,  DW.FORM_ref4
                             dbg_out.dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT_name, DW.FORM_string
                         },
                         .none => {},
@@ -2147,12 +2156,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         .dwarf => |dbg_out| {
                             switch (arch) {
                                 .arm, .armeb => {
-                                    const ty = inst.base.ty;
                                     const abi_size = math.cast(u32, ty.abiSize(self.target.*)) catch {
-                                        return self.fail(inst.base.src, "type '{}' too big to fit into stack frame", .{ty});
+                                        return self.fail("type '{}' too big to fit into stack frame", .{ty});
                                     };
                                     const adjusted_stack_offset = math.negateCast(offset + abi_size) catch {
-                                        return self.fail(inst.base.src, "Stack offset too large for arguments", .{});
+                                        return self.fail("Stack offset too large for arguments", .{});
                                     };
 
                                     try dbg_out.dbg_info.append(link.File.Elf.abbrev_parameter);
@@ -2168,7 +2176,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                     try leb128.writeILEB128(dbg_out.dbg_info.writer(), adjusted_stack_offset);
 
                                     try dbg_out.dbg_info.ensureCapacity(dbg_out.dbg_info.items.len + 5 + name_with_null.len);
-                                    try self.addDbgInfoTypeReloc(inst.base.ty); // DW.AT_type,  DW.FORM_ref4
+                                    try self.addDbgInfoTypeReloc(ty); // DW.AT_type,  DW.FORM_ref4
                                     dbg_out.dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT_name, DW.FORM_string
                                 },
                                 else => {},
@@ -2181,23 +2189,24 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
-        fn genArg(self: *Self, inst: *ir.Inst.Arg) !MCValue {
+        fn genArg(self: *Self, inst: Air.Inst.Index) !MCValue {
             const arg_index = self.arg_index;
             self.arg_index += 1;
 
+            const ty = self.air.getType(inst);
+
             const result = self.args[arg_index];
             const mcv = switch (arch) {
                 // TODO support stack-only arguments on all target architectures
                 .arm, .armeb, .aarch64, .aarch64_32, .aarch64_be => switch (result) {
                     // Copy registers to the stack
                     .register => |reg| blk: {
-                        const ty = inst.base.ty;
                         const abi_size = math.cast(u32, ty.abiSize(self.target.*)) catch {
-                            return self.fail(inst.base.src, "type '{}' too big to fit into stack frame", .{ty});
+                            return self.fail("type '{}' too big to fit into stack frame", .{ty});
                         };
                         const abi_align = ty.abiAlignment(self.target.*);
-                        const stack_offset = try self.allocMem(&inst.base, abi_size, abi_align);
-                        try self.genSetStack(inst.base.src, ty, stack_offset, MCValue{ .register = reg });
+                        const stack_offset = try self.allocMem(inst, abi_size, abi_align);
+                        try self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
 
                         break :blk MCValue{ .stack_offset = stack_offset };
                     },
@@ -2207,12 +2216,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             };
             try self.genArgDbgInfo(inst, mcv);
 
-            if (inst.base.isUnused())
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
 
             switch (mcv) {
                 .register => |reg| {
-                    self.register_manager.getRegAssumeFree(toCanonicalReg(reg), &inst.base);
+                    self.register_manager.getRegAssumeFree(toCanonicalReg(reg), inst);
                 },
                 else => {},
             }
@@ -2220,7 +2229,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return mcv;
         }
 
-        fn genBreakpoint(self: *Self, src: LazySrcLoc) !MCValue {
+        fn genBreakpoint(self: *Self) !MCValue {
             switch (arch) {
                 .i386, .x86_64 => {
                     try self.code.append(0xcc); // int3
@@ -2234,13 +2243,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .aarch64 => {
                     mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.brk(1).toU32());
                 },
-                else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement @breakpoint() for {}", .{self.target.cpu.arch}),
             }
             return .none;
         }
 
-        fn genCall(self: *Self, inst: *ir.Inst.Call) !MCValue {
-            var info = try self.resolveCallingConventionValues(inst.base.src, inst.func.ty);
+        fn genCall(self: *Self, inst: Air.Inst.Index) !MCValue {
+            const inst_datas = self.air.instructions.items(.data);
+            const pl_op = inst_datas[inst].pl_op;
+            const fn_ty = self.air.getType(pl_op.operand);
+            const callee = pl_op.operand;
+            const extra = self.air.extraData(Air.Call, inst_data.payload);
+            const args = self.air.extra[extra.end..][0..extra.data.args_len];
+
+            var info = try self.resolveCallingConventionValues(fn_ty);
             defer info.deinit(self);
 
             // Due to incremental compilation, how function calls are generated depends
@@ -2249,26 +2265,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 switch (arch) {
                     .x86_64 => {
                         for (info.args) |mc_arg, arg_i| {
-                            const arg = inst.args[arg_i];
-                            const arg_mcv = try self.resolveInst(inst.args[arg_i]);
+                            const arg = args[arg_i];
+                            const arg_mcv = try self.resolveInst(args[arg_i]);
                             // Here we do not use setRegOrMem even though the logic is similar, because
                             // the function call will move the stack pointer, so the offsets are different.
                             switch (mc_arg) {
                                 .none => continue,
                                 .register => |reg| {
                                     try self.register_manager.getReg(reg, null);
-                                    try self.genSetReg(arg.src, arg.ty, reg, arg_mcv);
+                                    try self.genSetReg(arg.ty, reg, arg_mcv);
                                 },
                                 .stack_offset => |off| {
                                     // Here we need to emit instructions like this:
                                     // mov     qword ptr [rsp + stack_offset], x
-                                    try self.genSetStack(arg.src, arg.ty, off, arg_mcv);
+                                    try self.genSetStack(arg.ty, off, arg_mcv);
                                 },
                                 .ptr_stack_offset => {
-                                    return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
+                                    return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{});
                                 },
                                 .ptr_embedded_in_code => {
-                                    return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
+                                    return self.fail("TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
                                 },
                                 .undef => unreachable,
                                 .immediate => unreachable,
@@ -2281,7 +2297,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             }
                         }
 
-                        if (inst.func.value()) |func_value| {
+                        if (self.air.value(callee)) |func_value| {
                             if (func_value.castTag(.function)) |func_payload| {
                                 const func = func_payload.data;
 
@@ -2300,18 +2316,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 });
                                 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), got_addr);
                             } else if (func_value.castTag(.extern_fn)) |_| {
-                                return self.fail(inst.base.src, "TODO implement calling extern functions", .{});
+                                return self.fail("TODO implement calling extern functions", .{});
                             } else {
-                                return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
+                                return self.fail("TODO implement calling bitcasted functions", .{});
                             }
                         } else {
-                            return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
+                            return self.fail("TODO implement calling runtime known function pointer", .{});
                         }
                     },
                     .riscv64 => {
-                        if (info.args.len > 0) return self.fail(inst.base.src, "TODO implement fn args for {}", .{self.target.cpu.arch});
+                        if (info.args.len > 0) return self.fail("TODO implement fn args for {}", .{self.target.cpu.arch});
 
-                        if (inst.func.value()) |func_value| {
+                        if (self.air.value(callee)) |func_value| {
                             if (func_value.castTag(.function)) |func_payload| {
                                 const func = func_payload.data;
 
@@ -2325,21 +2341,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 else
                                     unreachable;
 
-                                try self.genSetReg(inst.base.src, Type.initTag(.usize), .ra, .{ .memory = got_addr });
+                                try self.genSetReg(Type.initTag(.usize), .ra, .{ .memory = got_addr });
                                 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.jalr(.ra, 0, .ra).toU32());
                             } else if (func_value.castTag(.extern_fn)) |_| {
-                                return self.fail(inst.base.src, "TODO implement calling extern functions", .{});
+                                return self.fail("TODO implement calling extern functions", .{});
                             } else {
-                                return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
+                                return self.fail("TODO implement calling bitcasted functions", .{});
                             }
                         } else {
-                            return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
+                            return self.fail("TODO implement calling runtime known function pointer", .{});
                         }
                     },
                     .arm, .armeb => {
                         for (info.args) |mc_arg, arg_i| {
-                            const arg = inst.args[arg_i];
-                            const arg_mcv = try self.resolveInst(inst.args[arg_i]);
+                            const arg = args[arg_i];
+                            const arg_mcv = try self.resolveInst(args[arg_i]);
 
                             switch (mc_arg) {
                                 .none => continue,
@@ -2353,21 +2369,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 .compare_flags_unsigned => unreachable,
                                 .register => |reg| {
                                     try self.register_manager.getReg(reg, null);
-                                    try self.genSetReg(arg.src, arg.ty, reg, arg_mcv);
+                                    try self.genSetReg(arg.ty, reg, arg_mcv);
                                 },
                                 .stack_offset => {
-                                    return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
+                                    return self.fail("TODO implement calling with parameters in memory", .{});
                                 },
                                 .ptr_stack_offset => {
-                                    return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
+                                    return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{});
                                 },
                                 .ptr_embedded_in_code => {
-                                    return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
+                                    return self.fail("TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
                                 },
                             }
                         }
 
-                        if (inst.func.value()) |func_value| {
+                        if (self.air.value(callee)) |func_value| {
                             if (func_value.castTag(.function)) |func_payload| {
                                 const func = func_payload.data;
                                 const ptr_bits = self.target.cpu.arch.ptrBitWidth();
@@ -2380,7 +2396,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 else
                                     unreachable;
 
-                                try self.genSetReg(inst.base.src, Type.initTag(.usize), .lr, .{ .memory = got_addr });
+                                try self.genSetReg(Type.initTag(.usize), .lr, .{ .memory = got_addr });
 
                                 // TODO: add Instruction.supportedOn
                                 // function for ARM
@@ -2391,18 +2407,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                     writeInt(u32, try self.code.addManyAsArray(4), Instruction.bx(.al, .lr).toU32());
                                 }
                             } else if (func_value.castTag(.extern_fn)) |_| {
-                                return self.fail(inst.base.src, "TODO implement calling extern functions", .{});
+                                return self.fail("TODO implement calling extern functions", .{});
                             } else {
-                                return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
+                                return self.fail("TODO implement calling bitcasted functions", .{});
                             }
                         } else {
-                            return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
+                            return self.fail("TODO implement calling runtime known function pointer", .{});
                         }
                     },
                     .aarch64 => {
                         for (info.args) |mc_arg, arg_i| {
-                            const arg = inst.args[arg_i];
-                            const arg_mcv = try self.resolveInst(inst.args[arg_i]);
+                            const arg = args[arg_i];
+                            const arg_mcv = try self.resolveInst(args[arg_i]);
 
                             switch (mc_arg) {
                                 .none => continue,
@@ -2416,21 +2432,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 .compare_flags_unsigned => unreachable,
                                 .register => |reg| {
                                     try self.register_manager.getReg(reg, null);
-                                    try self.genSetReg(arg.src, arg.ty, reg, arg_mcv);
+                                    try self.genSetReg(arg.ty, reg, arg_mcv);
                                 },
                                 .stack_offset => {
-                                    return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
+                                    return self.fail("TODO implement calling with parameters in memory", .{});
                                 },
                                 .ptr_stack_offset => {
-                                    return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
+                                    return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{});
                                 },
                                 .ptr_embedded_in_code => {
-                                    return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
+                                    return self.fail("TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
                                 },
                             }
                         }
 
-                        if (inst.func.value()) |func_value| {
+                        if (self.air.value(callee)) |func_value| {
                             if (func_value.castTag(.function)) |func_payload| {
                                 const func = func_payload.data;
                                 const ptr_bits = self.target.cpu.arch.ptrBitWidth();
@@ -2443,24 +2459,24 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 else
                                     unreachable;
 
-                                try self.genSetReg(inst.base.src, Type.initTag(.usize), .x30, .{ .memory = got_addr });
+                                try self.genSetReg(Type.initTag(.usize), .x30, .{ .memory = got_addr });
 
                                 writeInt(u32, try self.code.addManyAsArray(4), Instruction.blr(.x30).toU32());
                             } else if (func_value.castTag(.extern_fn)) |_| {
-                                return self.fail(inst.base.src, "TODO implement calling extern functions", .{});
+                                return self.fail("TODO implement calling extern functions", .{});
                             } else {
-                                return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
+                                return self.fail("TODO implement calling bitcasted functions", .{});
                             }
                         } else {
-                            return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
+                            return self.fail("TODO implement calling runtime known function pointer", .{});
                         }
                     },
-                    else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),
+                    else => return self.fail("TODO implement call for {}", .{self.target.cpu.arch}),
                 }
             } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
                 for (info.args) |mc_arg, arg_i| {
-                    const arg = inst.args[arg_i];
-                    const arg_mcv = try self.resolveInst(inst.args[arg_i]);
+                    const arg = args[arg_i];
+                    const arg_mcv = try self.resolveInst(args[arg_i]);
                     // Here we do not use setRegOrMem even though the logic is similar, because
                     // the function call will move the stack pointer, so the offsets are different.
                     switch (mc_arg) {
@@ -2471,18 +2487,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 .x86_64, .aarch64 => try self.register_manager.getReg(reg, null),
                                 else => unreachable,
                             }
-                            try self.genSetReg(arg.src, arg.ty, reg, arg_mcv);
+                            try self.genSetReg(arg.ty, reg, arg_mcv);
                         },
                         .stack_offset => {
                             // Here we need to emit instructions like this:
                             // mov     qword ptr [rsp + stack_offset], x
-                            return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
+                            return self.fail("TODO implement calling with parameters in memory", .{});
                         },
                         .ptr_stack_offset => {
-                            return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
+                            return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{});
                         },
                         .ptr_embedded_in_code => {
-                            return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
+                            return self.fail("TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
                         },
                         .undef => unreachable,
                         .immediate => unreachable,
@@ -2495,7 +2511,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     }
                 }
 
-                if (inst.func.value()) |func_value| {
+                if (self.air.value(callee)) |func_value| {
                     if (func_value.castTag(.function)) |func_payload| {
                         const func = func_payload.data;
                         const got_addr = blk: {
@@ -2506,13 +2522,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         log.debug("got_addr = 0x{x}", .{got_addr});
                         switch (arch) {
                             .x86_64 => {
-                                try self.genSetReg(inst.base.src, Type.initTag(.u64), .rax, .{ .memory = got_addr });
+                                try self.genSetReg(Type.initTag(.u64), .rax, .{ .memory = got_addr });
                                 // callq *%rax
                                 try self.code.ensureCapacity(self.code.items.len + 2);
                                 self.code.appendSliceAssumeCapacity(&[2]u8{ 0xff, 0xd0 });
                             },
                             .aarch64 => {
-                                try self.genSetReg(inst.base.src, Type.initTag(.u64), .x30, .{ .memory = got_addr });
+                                try self.genSetReg(Type.initTag(.u64), .x30, .{ .memory = got_addr });
                                 // blr x30
                                 writeInt(u32, try self.code.addManyAsArray(4), Instruction.blr(.x30).toU32());
                             },
@@ -2552,35 +2568,35 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         });
                         // We mark the space and fix it up later.
                     } else {
-                        return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
+                        return self.fail("TODO implement calling bitcasted functions", .{});
                     }
                 } else {
-                    return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
+                    return self.fail("TODO implement calling runtime known function pointer", .{});
                 }
             } else if (self.bin_file.cast(link.File.Plan9)) |p9| {
                 switch (arch) {
                     .x86_64 => {
                         for (info.args) |mc_arg, arg_i| {
-                            const arg = inst.args[arg_i];
-                            const arg_mcv = try self.resolveInst(inst.args[arg_i]);
+                            const arg = args[arg_i];
+                            const arg_mcv = try self.resolveInst(args[arg_i]);
                             // Here we do not use setRegOrMem even though the logic is similar, because
                             // the function call will move the stack pointer, so the offsets are different.
                             switch (mc_arg) {
                                 .none => continue,
                                 .register => |reg| {
                                     try self.register_manager.getReg(reg, null);
-                                    try self.genSetReg(arg.src, arg.ty, reg, arg_mcv);
+                                    try self.genSetReg(arg.ty, reg, arg_mcv);
                                 },
                                 .stack_offset => {
                                     // Here we need to emit instructions like this:
                                     // mov     qword ptr [rsp + stack_offset], x
-                                    return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
+                                    return self.fail("TODO implement calling with parameters in memory", .{});
                                 },
                                 .ptr_stack_offset => {
-                                    return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
+                                    return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{});
                                 },
                                 .ptr_embedded_in_code => {
-                                    return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
+                                    return self.fail("TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
                                 },
                                 .undef => unreachable,
                                 .immediate => unreachable,
@@ -2592,7 +2608,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 .compare_flags_unsigned => unreachable,
                             }
                         }
-                        if (inst.func.value()) |func_value| {
+                        if (self.air.value(callee)) |func_value| {
                             if (func_value.castTag(.function)) |func_payload| {
                                 const ptr_bits = self.target.cpu.arch.ptrBitWidth();
                                 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
@@ -2603,9 +2619,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 });
                                 const fn_got_addr = got_addr + got_index * ptr_bytes;
                                 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), @intCast(u32, fn_got_addr));
-                            } else return self.fail(inst.base.src, "TODO implement calling extern fn on plan9", .{});
+                            } else return self.fail("TODO implement calling extern fn on plan9", .{});
                         } else {
-                            return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
+                            return self.fail("TODO implement calling runtime known function pointer", .{});
                         }
                     },
                     .aarch64 => {
@@ -2628,13 +2644,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                     try self.genSetReg(arg.src, arg.ty, reg, arg_mcv);
                                 },
                                 .stack_offset => {
-                                    return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
+                                    return self.fail("TODO implement calling with parameters in memory", .{});
                                 },
                                 .ptr_stack_offset => {
-                                    return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
+                                    return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{});
                                 },
                                 .ptr_embedded_in_code => {
-                                    return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
+                                    return self.fail("TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
                                 },
                             }
                         }
@@ -2650,15 +2666,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
                                 writeInt(u32, try self.code.addManyAsArray(4), Instruction.blr(.x30).toU32());
                             } else if (func_value.castTag(.extern_fn)) |_| {
-                                return self.fail(inst.base.src, "TODO implement calling extern functions", .{});
+                                return self.fail("TODO implement calling extern functions", .{});
                             } else {
-                                return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
+                                return self.fail("TODO implement calling bitcasted functions", .{});
                             }
                         } else {
-                            return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
+                            return self.fail("TODO implement calling runtime known function pointer", .{});
                         }
                     },
-                    else => return self.fail(inst.base.src, "TODO implement call on plan9 for {}", .{self.target.cpu.arch}),
+                    else => return self.fail("TODO implement call on plan9 for {}", .{self.target.cpu.arch}),
                 }
             } else unreachable;
 
@@ -2666,7 +2682,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .register => |reg| {
                     if (Register.allocIndex(reg) == null) {
                         // Save function return value in a callee saved register
-                        return try self.copyToNewRegister(&inst.base, info.return_value);
+                        return try self.copyToNewRegister(inst, info.return_value);
                     }
                 },
                 else => {},
@@ -2675,8 +2691,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return info.return_value;
         }
 
-        fn genRef(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
-            const operand = try self.resolveInst(inst.operand);
+        fn genRef(self: *Self, inst: Air.Inst.Index) !MCValue {
+            if (self.liveness.isUnused(inst))
+                return MCValue.dead;
+            const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+            const operand_ty = self.air.getType(ty_op.operand);
+            const operand = try self.resolveInst(ty_op.operand);
             switch (operand) {
                 .unreach => unreachable,
                 .dead => unreachable,
@@ -2689,8 +2709,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .compare_flags_unsigned,
                 .compare_flags_signed,
                 => {
-                    const stack_offset = try self.allocMemPtr(&inst.base);
-                    try self.genSetStack(inst.base.src, inst.operand.ty, stack_offset, operand);
+                    const stack_offset = try self.allocMemPtr(inst);
+                    try self.genSetStack(operand_ty, stack_offset, operand);
                     return MCValue{ .ptr_stack_offset = stack_offset };
                 },
 
@@ -2698,13 +2718,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .embedded_in_code => |offset| return MCValue{ .ptr_embedded_in_code = offset },
                 .memory => |vaddr| return MCValue{ .immediate = vaddr },
 
-                .undef => return self.fail(inst.base.src, "TODO implement ref on an undefined value", .{}),
+                .undef => return self.fail("TODO implement ref on an undefined value", .{}),
             }
         }
 
-        fn ret(self: *Self, src: LazySrcLoc, mcv: MCValue) !MCValue {
+        fn ret(self: *Self, mcv: MCValue) !MCValue {
             const ret_ty = self.fn_type.fnReturnType();
-            try self.setRegOrMem(src, ret_ty, self.ret_mcv, mcv);
+            try self.setRegOrMem(ret_ty, self.ret_mcv, mcv);
             switch (arch) {
                 .i386 => {
                     try self.code.append(0xc3); // ret
@@ -2730,58 +2750,54 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     try self.code.resize(self.code.items.len + 4);
                     try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4);
                 },
-                else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement return for {}", .{self.target.cpu.arch}),
             }
             return .unreach;
         }
 
-        fn genRet(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
-            const operand = try self.resolveInst(inst.operand);
+        fn genRet(self: *Self, inst: Air.Inst.Index) !MCValue {
+            const operand = try self.resolveInst(self.air.instructions.items(.data)[inst].un_op);
             return self.ret(inst.base.src, operand);
         }
 
-        fn genRetVoid(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
-            return self.ret(inst.base.src, .none);
-        }
-
-        fn genCmp(self: *Self, inst: *ir.Inst.BinOp, op: math.CompareOperator) !MCValue {
+        fn genCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !MCValue {
             // No side effects, so if it's unreferenced, do nothing.
-            if (inst.base.isUnused())
-                return MCValue{ .dead = {} };
-            if (inst.lhs.ty.zigTypeTag() == .ErrorSet or inst.rhs.ty.zigTypeTag() == .ErrorSet)
-                return self.fail(inst.base.src, "TODO implement cmp for errors", .{});
+            if (self.liveness.isUnused(inst))
+                return MCValue.dead;
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+            const ty = self.air.getType(bin_op.lhs);
+            assert(ty.eql(self.air.getType(bin_op.rhs)));
+            if (ty.zigTypeTag() == .ErrorSet)
+                return self.fail("TODO implement cmp for errors", .{});
+
+            const lhs = try self.resolveInst(bin_op.lhs);
+            const rhs = try self.resolveInst(bin_op.rhs);
             switch (arch) {
                 .x86_64 => {
                     try self.code.ensureCapacity(self.code.items.len + 8);
 
-                    const lhs = try self.resolveInst(inst.lhs);
-                    const rhs = try self.resolveInst(inst.rhs);
-
                     // There are 2 operands, destination and source.
                     // Either one, but not both, can be a memory operand.
                     // Source operand can be an immediate, 8 bits or 32 bits.
                     const dst_mcv = if (lhs.isImmediate() or (lhs.isMemory() and rhs.isMemory()))
-                        try self.copyToNewRegister(&inst.base, lhs)
+                        try self.copyToNewRegister(inst, lhs)
                     else
                         lhs;
                     // This instruction supports only signed 32-bit immediates at most.
-                    const src_mcv = try self.limitImmediateType(inst.rhs, i32);
+                    const src_mcv = try self.limitImmediateType(bin_op.rhs, i32);
 
-                    try self.genX8664BinMathCode(inst.base.src, inst.base.ty, dst_mcv, src_mcv, 7, 0x38);
-                    const info = inst.lhs.ty.intInfo(self.target.*);
+                    try self.genX8664BinMathCode(Type.initTag(.bool), dst_mcv, src_mcv, 7, 0x38);
+                    const info = ty.intInfo(self.target.*);
                     return switch (info.signedness) {
                         .signed => MCValue{ .compare_flags_signed = op },
                         .unsigned => MCValue{ .compare_flags_unsigned = op },
                     };
                 },
                 .arm, .armeb => {
-                    const lhs = try self.resolveInst(inst.lhs);
-                    const rhs = try self.resolveInst(inst.rhs);
-
                     const lhs_is_register = lhs == .register;
                     const rhs_is_register = rhs == .register;
                     // lhs should always be a register
-                    const rhs_should_be_register = try self.armOperandShouldBeRegister(inst.rhs.src, rhs);
+                    const rhs_should_be_register = try self.armOperandShouldBeRegister(rhs);
 
                     var lhs_mcv = lhs;
                     var rhs_mcv = rhs;
@@ -2789,49 +2805,55 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     // Allocate registers
                     if (rhs_should_be_register) {
                         if (!lhs_is_register and !rhs_is_register) {
-                            const regs = try self.register_manager.allocRegs(2, .{ inst.rhs, inst.lhs }, &.{});
+                            const regs = try self.register_manager.allocRegs(2, .{ bin_op.rhs, bin_op.lhs }, &.{});
                             lhs_mcv = MCValue{ .register = regs[0] };
                             rhs_mcv = MCValue{ .register = regs[1] };
                         } else if (!rhs_is_register) {
-                            rhs_mcv = MCValue{ .register = try self.register_manager.allocReg(inst.rhs, &.{}) };
+                            rhs_mcv = MCValue{ .register = try self.register_manager.allocReg(bin_op.rhs, &.{}) };
                         }
                     }
                     if (!lhs_is_register) {
-                        lhs_mcv = MCValue{ .register = try self.register_manager.allocReg(inst.lhs, &.{}) };
+                        lhs_mcv = MCValue{ .register = try self.register_manager.allocReg(bin_op.lhs, &.{}) };
                     }
 
                     // Move the operands to the newly allocated registers
                     const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
                     if (lhs_mcv == .register and !lhs_is_register) {
-                        try self.genSetReg(inst.lhs.src, inst.lhs.ty, lhs_mcv.register, lhs);
-                        branch.inst_table.putAssumeCapacity(inst.lhs, lhs);
+                        try self.genSetReg(ty, lhs_mcv.register, lhs);
+                        branch.inst_table.putAssumeCapacity(bin_op.lhs, lhs);
                     }
                     if (rhs_mcv == .register and !rhs_is_register) {
-                        try self.genSetReg(inst.rhs.src, inst.rhs.ty, rhs_mcv.register, rhs);
-                        branch.inst_table.putAssumeCapacity(inst.rhs, rhs);
+                        try self.genSetReg(ty, rhs_mcv.register, rhs);
+                        branch.inst_table.putAssumeCapacity(bin_op.rhs, rhs);
                     }
 
                     // The destination register is not present in the cmp instruction
-                    try self.genArmBinOpCode(inst.base.src, undefined, lhs_mcv, rhs_mcv, false, .cmp_eq);
+                    try self.genArmBinOpCode(undefined, lhs_mcv, rhs_mcv, false, .cmp_eq);
 
-                    const info = inst.lhs.ty.intInfo(self.target.*);
+                    const info = ty.intInfo(self.target.*);
                     return switch (info.signedness) {
                         .signed => MCValue{ .compare_flags_signed = op },
                         .unsigned => MCValue{ .compare_flags_unsigned = op },
                     };
                 },
-                else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement cmp for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genDbgStmt(self: *Self, inst: *ir.Inst.DbgStmt) !MCValue {
-            try self.dbgAdvancePCAndLine(inst.line, inst.column);
-            assert(inst.base.isUnused());
+        fn genDbgStmt(self: *Self, inst: Air.Inst.Index) !MCValue {
+            const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;
+            try self.dbgAdvancePCAndLine(dbg_stmt.line, dbg_stmt.column);
+            assert(self.liveness.isUnused(inst));
             return MCValue.dead;
         }
 
-        fn genCondBr(self: *Self, inst: *ir.Inst.CondBr) !MCValue {
-            const cond = try self.resolveInst(inst.condition);
+        fn genCondBr(self: *Self, inst: Air.Inst.Index) !MCValue {
+            const inst_datas = self.air.instructions.items(.data);
+            const pl_op = inst_datas[inst].pl_op;
+            const cond = try self.resolveInst(pl_op.operand);
+            const extra = self.air.extraData(Air.CondBr, inst_data.payload);
+            const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
+            const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
 
             const reloc: Reloc = switch (arch) {
                 .i386, .x86_64 => reloc: {
@@ -2880,7 +2902,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             encoder.disp8(1);
                             break :blk 0x84;
                         },
-                        else => return self.fail(inst.base.src, "TODO implement condbr {s} when condition is {s}", .{ self.target.cpu.arch, @tagName(cond) }),
+                        else => return self.fail("TODO implement condbr {s} when condition is {s}", .{ self.target.cpu.arch, @tagName(cond) }),
                     };
                     self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode });
                     const reloc = Reloc{ .rel32 = self.code.items.len };
@@ -2906,7 +2928,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             writeInt(u32, try self.code.addManyAsArray(4), Instruction.cmp(.al, reg, op).toU32());
                             break :blk .ne;
                         },
-                        else => return self.fail(inst.base.src, "TODO implement condbr {} when condition is {s}", .{ self.target.cpu.arch, @tagName(cond) }),
+                        else => return self.fail("TODO implement condbr {} when condition is {s}", .{ self.target.cpu.arch, @tagName(cond) }),
                     };
 
                     const reloc = Reloc{
@@ -2918,7 +2940,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     try self.code.resize(self.code.items.len + 4);
                     break :reloc reloc;
                 },
-                else => return self.fail(inst.base.src, "TODO implement condbr {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement condbr {}", .{self.target.cpu.arch}),
             };
 
             // Capture the state of register and stack allocation state so that we can revert to it.
@@ -2930,12 +2952,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
             try self.branch_stack.append(.{});
 
-            const then_deaths = inst.thenDeaths();
+            const then_deaths = self.liveness.thenDeaths(inst);
             try self.ensureProcessDeathCapacity(then_deaths.len);
             for (then_deaths) |operand| {
                 self.processDeath(operand);
             }
-            try self.genBody(inst.then_body);
+            try self.genBody(then_body);
 
             // Revert to the previous register and stack allocation state.
 
@@ -2951,16 +2973,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             self.next_stack_offset = parent_next_stack_offset;
             self.register_manager.free_registers = parent_free_registers;
 
-            try self.performReloc(inst.base.src, reloc);
+            try self.performReloc(reloc);
             const else_branch = self.branch_stack.addOneAssumeCapacity();
             else_branch.* = .{};
 
-            const else_deaths = inst.elseDeaths();
+            const else_deaths = self.liveness.elseDeaths(inst);
             try self.ensureProcessDeathCapacity(else_deaths.len);
             for (else_deaths) |operand| {
                 self.processDeath(operand);
             }
-            try self.genBody(inst.else_body);
+            try self.genBody(else_body);
 
             // At this point, each branch will possibly have conflicting values for where
             // each instruction is stored. They agree, however, on which instructions are alive/dead.
@@ -3003,7 +3025,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 log.debug("consolidating else_entry {*} {}=>{}", .{ else_key, else_value, canon_mcv });
                 // TODO make sure the destination stack offset / register does not already have something
                 // going on there.
-                try self.setRegOrMem(inst.base.src, else_key.ty, canon_mcv, else_value);
+                try self.setRegOrMem(else_key.ty, canon_mcv, else_value);
                 // TODO track the new register / stack allocation
             }
             try parent_branch.inst_table.ensureCapacity(self.gpa, parent_branch.inst_table.count() +
@@ -3031,7 +3053,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 log.debug("consolidating then_entry {*} {}=>{}", .{ then_key, parent_mcv, then_value });
                 // TODO make sure the destination stack offset / register does not already have something
                 // going on there.
-                try self.setRegOrMem(inst.base.src, then_key.ty, parent_mcv, then_value);
+                try self.setRegOrMem(then_key.ty, parent_mcv, then_value);
                 // TODO track the new register / stack allocation
             }
 
@@ -3040,58 +3062,155 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return MCValue.unreach;
         }
 
-        fn genIsNull(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn isNull(self: *Self, operand: MCValue) !MCValue {
+            _ = operand;
+            // Here you can specialize this instruction if it makes sense to, otherwise the default
+            // will call isNonNull and invert the result.
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO call isNonNull and invert the result", .{}),
             }
         }
 
-        fn genIsNullPtr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
-            return self.fail(inst.base.src, "TODO load the operand and call genIsNull", .{});
-        }
-
-        fn genIsNonNull(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn isNonNull(self: *Self, operand: MCValue) !MCValue {
+            _ = operand;
             // Here you can specialize this instruction if it makes sense to, otherwise the default
-            // will call genIsNull and invert the result.
+            // will call isNull and invert the result.
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO call genIsNull and invert the result ", .{}),
+                else => return self.fail("TODO call isNull and invert the result", .{}),
             }
         }
 
-        fn genIsNonNullPtr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
-            return self.fail(inst.base.src, "TODO load the operand and call genIsNonNull", .{});
+        fn isErr(self: *Self, operand: MCValue) !MCValue {
+            _ = operand;
+            // Here you can specialize this instruction if it makes sense to, otherwise the default
+            // will call isNonNull and invert the result.
+            switch (arch) {
+                else => return self.fail("TODO call isNonErr and invert the result", .{}),
+            }
         }
 
-        fn genIsErr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+        fn isNonErr(self: *Self, operand: MCValue) !MCValue {
+            _ = operand;
+            // Here you can specialize this instruction if it makes sense to, otherwise the default
+            // will call isNull and invert the result.
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement iserr for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO call isErr and invert the result", .{}),
             }
         }
 
-        fn genIsErrPtr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
-            return self.fail(inst.base.src, "TODO load the operand and call genIsErr", .{});
+        fn genIsNull(self: *Self, inst: Air.Inst.Index) !MCValue {
+            if (self.liveness.isUnused(inst))
+                return MCValue.dead;
+            const inst_datas = self.air.instructions.items(.data);
+            const operand = try self.resolveInst(inst_datas[inst].un_op);
+            return self.isNull(operand);
         }
 
-        fn genIsNonErr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
-            switch (arch) {
-                else => return self.fail(inst.base.src, "TODO implement is_non_err for {}", .{self.target.cpu.arch}),
-            }
+        fn genIsNullPtr(self: *Self, inst: Air.Inst.Index) !MCValue {
+            if (self.liveness.isUnused(inst))
+                return MCValue.dead;
+            const inst_datas = self.air.instructions.items(.data);
+            const operand_ptr = try self.resolveInst(inst_datas[inst].un_op);
+            const operand: MCValue = blk: {
+                if (self.reuseOperand(inst, 0, operand_ptr)) {
+                    // The MCValue that holds the pointer can be re-used as the value.
+                    break :blk operand_ptr;
+                } else {
+                    break :blk try self.allocRegOrMem(inst, true);
+                }
+            };
+            try self.load(operand, ptr);
+            return self.isNull(operand);
         }
 
-        fn genIsNonErrPtr(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
-            return self.fail(inst.base.src, "TODO load the operand and call genIsNonErr", .{});
+        fn genIsNonNull(self: *Self, inst: Air.Inst.Index) !MCValue {
+            if (self.liveness.isUnused(inst))
+                return MCValue.dead;
+            const inst_datas = self.air.instructions.items(.data);
+            const operand = try self.resolveInst(inst_datas[inst].un_op);
+            return self.isNonNull(operand);
         }
 
-        fn genLoop(self: *Self, inst: *ir.Inst.Loop) !MCValue {
+        fn genIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !MCValue {
+            if (self.liveness.isUnused(inst))
+                return MCValue.dead;
+            const inst_datas = self.air.instructions.items(.data);
+            const operand_ptr = try self.resolveInst(inst_datas[inst].un_op);
+            const operand: MCValue = blk: {
+                if (self.reuseOperand(inst, 0, operand_ptr)) {
+                    // The MCValue that holds the pointer can be re-used as the value.
+                    break :blk operand_ptr;
+                } else {
+                    break :blk try self.allocRegOrMem(inst, true);
+                }
+            };
+            try self.load(operand, ptr);
+            return self.isNonNull(operand);
+        }
+
+        fn genIsErr(self: *Self, inst: Air.Inst.Index) !MCValue {
+            if (self.liveness.isUnused(inst))
+                return MCValue.dead;
+            const inst_datas = self.air.instructions.items(.data);
+            const operand = try self.resolveInst(inst_datas[inst].un_op);
+            return self.isErr(operand);
+        }
+
+        fn genIsErrPtr(self: *Self, inst: Air.Inst.Index) !MCValue {
+            if (self.liveness.isUnused(inst))
+                return MCValue.dead;
+            const inst_datas = self.air.instructions.items(.data);
+            const operand_ptr = try self.resolveInst(inst_datas[inst].un_op);
+            const operand: MCValue = blk: {
+                if (self.reuseOperand(inst, 0, operand_ptr)) {
+                    // The MCValue that holds the pointer can be re-used as the value.
+                    break :blk operand_ptr;
+                } else {
+                    break :blk try self.allocRegOrMem(inst, true);
+                }
+            };
+            try self.load(operand, ptr);
+            return self.isErr(operand);
+        }
+
+        fn genIsNonErr(self: *Self, inst: Air.Inst.Index) !MCValue {
+            if (self.liveness.isUnused(inst))
+                return MCValue.dead;
+            const inst_datas = self.air.instructions.items(.data);
+            const operand = try self.resolveInst(inst_datas[inst].un_op);
+            return self.isNonErr(operand);
+        }
+
+        fn genIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !MCValue {
+            if (self.liveness.isUnused(inst))
+                return MCValue.dead;
+            const inst_datas = self.air.instructions.items(.data);
+            const operand_ptr = try self.resolveInst(inst_datas[inst].un_op);
+            const operand: MCValue = blk: {
+                if (self.reuseOperand(inst, 0, operand_ptr)) {
+                    // The MCValue that holds the pointer can be re-used as the value.
+                    break :blk operand_ptr;
+                } else {
+                    break :blk try self.allocRegOrMem(inst, true);
+                }
+            };
+            try self.load(operand, ptr);
+            return self.isNonErr(operand);
+        }
+
+        fn genLoop(self: *Self, inst: Air.Inst.Index) !MCValue {
             // A loop is a setup to be able to jump back to the beginning.
+            const inst_datas = self.air.instructions.items(.data);
+            const loop = self.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
+            const body = self.air.extra[loop.end..][0..loop.data.body_len];
             const start_index = self.code.items.len;
-            try self.genBody(inst.body);
-            try self.jump(inst.base.src, start_index);
+            try self.genBody(body);
+            try self.jump(start_index);
             return MCValue.unreach;
         }
 
         /// Send control flow to the `index` of `self.code`.
-        fn jump(self: *Self, src: LazySrcLoc, index: usize) !void {
+        fn jump(self: *Self, index: usize) !void {
             switch (arch) {
                 .i386, .x86_64 => {
                     try self.code.ensureCapacity(self.code.items.len + 5);
@@ -3108,21 +3227,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     if (math.cast(i26, @intCast(i32, index) - @intCast(i32, self.code.items.len + 8))) |delta| {
                         writeInt(u32, try self.code.addManyAsArray(4), Instruction.b(.al, delta).toU32());
                     } else |_| {
-                        return self.fail(src, "TODO: enable larger branch offset", .{});
+                        return self.fail("TODO: enable larger branch offset", .{});
                     }
                 },
                 .aarch64, .aarch64_be, .aarch64_32 => {
                     if (math.cast(i28, @intCast(i32, index) - @intCast(i32, self.code.items.len + 8))) |delta| {
                         writeInt(u32, try self.code.addManyAsArray(4), Instruction.b(delta).toU32());
                     } else |_| {
-                        return self.fail(src, "TODO: enable larger branch offset", .{});
+                        return self.fail("TODO: enable larger branch offset", .{});
                     }
                 },
-                else => return self.fail(src, "TODO implement jump for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement jump for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genBlock(self: *Self, inst: *ir.Inst.Block) !MCValue {
+        fn genBlock(self: *Self, inst: Air.Inst.Index) !MCValue {
             try self.blocks.putNoClobber(self.gpa, inst, .{
                 // A block is a setup to be able to jump to the end.
                 .relocs = .{},
@@ -3136,20 +3255,24 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             const block_data = self.blocks.getPtr(inst).?;
             defer block_data.relocs.deinit(self.gpa);
 
-            try self.genBody(inst.body);
+            const ty_pl = self.air.instructions.items(.data).ty_pl;
+            const extra = self.air.extraData(Air.Block, ty_pl.payload);
+            const body = self.air.extra[extra.end..][0..extra.data.body_len];
+            try self.genBody(body);
 
-            for (block_data.relocs.items) |reloc| try self.performReloc(inst.base.src, reloc);
+            for (block_data.relocs.items) |reloc| try self.performReloc(reloc);
 
             return @bitCast(MCValue, block_data.mcv);
         }
 
-        fn genSwitch(self: *Self, inst: *ir.Inst.SwitchBr) !MCValue {
+        fn genSwitch(self: *Self, inst: Air.Inst.Index) !MCValue {
+            _ = inst;
             switch (arch) {
-                else => return self.fail(inst.base.src, "TODO genSwitch for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO genSwitch for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn performReloc(self: *Self, src: LazySrcLoc, reloc: Reloc) !void {
+        fn performReloc(self: *Self, reloc: Reloc) !void {
             switch (reloc) {
                 .rel32 => |pos| {
                     const amt = self.code.items.len - (pos + 4);
@@ -3160,7 +3283,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     // best place to elide jumps will be in semantic analysis, by inlining blocks that only
                     // only have 1 break instruction.
                     const s32_amt = math.cast(i32, amt) catch
-                        return self.fail(src, "unable to perform relocation: jump too far", .{});
+                        return self.fail("unable to perform relocation: jump too far", .{});
                     mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt);
                 },
                 .arm_branch => |info| {
@@ -3170,7 +3293,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             if (math.cast(i26, amt)) |delta| {
                                 writeInt(u32, self.code.items[info.pos..][0..4], Instruction.b(info.cond, delta).toU32());
                             } else |_| {
-                                return self.fail(src, "TODO: enable larger branch offset", .{});
+                                return self.fail("TODO: enable larger branch offset", .{});
                             }
                         },
                         else => unreachable, // attempting to perfrom an ARM relocation on a non-ARM target arch
@@ -3179,41 +3302,39 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
-        fn genBrBlockFlat(self: *Self, inst: *ir.Inst.BrBlockFlat) !MCValue {
+        fn genBrBlockFlat(self: *Self, inst: Air.Inst.Index) !MCValue {
             try self.genBody(inst.body);
             const last = inst.body.instructions[inst.body.instructions.len - 1];
-            return self.br(inst.base.src, inst.block, last);
-        }
-
-        fn genBr(self: *Self, inst: *ir.Inst.Br) !MCValue {
-            return self.br(inst.base.src, inst.block, inst.operand);
+            return self.br(inst.block, last);
         }
 
-        fn genBrVoid(self: *Self, inst: *ir.Inst.BrVoid) !MCValue {
-            return self.brVoid(inst.base.src, inst.block);
+        fn genBr(self: *Self, inst: Air.Inst.Index) !MCValue {
+            return self.br(inst.block, inst.operand);
         }
 
-        fn genBoolOp(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
-            if (inst.base.isUnused())
+        fn genBoolOp(self: *Self, inst: Air.Inst.Index) !MCValue {
+            if (self.liveness.isUnused(inst))
                 return MCValue.dead;
+            const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+            const air_tags = self.air.instructions.items(.tag);
             switch (arch) {
-                .x86_64 => switch (inst.base.tag) {
+                .x86_64 => switch (air_tags[inst]) {
                     // lhs AND rhs
-                    .bool_and => return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs),
+                    .bool_and => return try self.genX8664BinMath(inst, bin_op.lhs, bin_op.rhs),
                     // lhs OR rhs
-                    .bool_or => return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs),
+                    .bool_or => return try self.genX8664BinMath(inst, bin_op.lhs, bin_op.rhs),
                     else => unreachable, // Not a boolean operation
                 },
-                .arm, .armeb => switch (inst.base.tag) {
-                    .bool_and => return try self.genArmBinOp(&inst.base, inst.lhs, inst.rhs, .bool_and),
-                    .bool_or => return try self.genArmBinOp(&inst.base, inst.lhs, inst.rhs, .bool_or),
+                .arm, .armeb => switch (air_tags[inst]) {
+                    .bool_and => return try self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .bool_and),
+                    .bool_or => return try self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .bool_or),
                     else => unreachable, // Not a boolean operation
                 },
-                else => return self.fail(inst.base.src, "TODO implement boolean operations for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement boolean operations for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn br(self: *Self, src: LazySrcLoc, block: *ir.Inst.Block, operand: *ir.Inst) !MCValue {
+        fn br(self: *Self, block: Air.Inst.Index, operand: Air.Inst.Index) !MCValue {
             const block_data = self.blocks.getPtr(block).?;
 
             if (operand.ty.hasCodeGenBits()) {
@@ -3222,13 +3343,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 if (block_mcv == .none) {
                     block_data.mcv = operand_mcv;
                 } else {
-                    try self.setRegOrMem(src, block.base.ty, block_mcv, operand_mcv);
+                    try self.setRegOrMem(block.base.ty, block_mcv, operand_mcv);
                 }
             }
-            return self.brVoid(src, block);
+            return self.brVoid(block);
         }
 
-        fn brVoid(self: *Self, src: LazySrcLoc, block: *ir.Inst.Block) !MCValue {
+        fn brVoid(self: *Self, block: Air.Inst.Index) !MCValue {
             const block_data = self.blocks.getPtr(block).?;
 
             // Emit a jump with a relocation. It will be patched up after the block ends.
@@ -3252,43 +3373,43 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         },
                     });
                 },
-                else => return self.fail(src, "TODO implement brvoid for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement brvoid for {}", .{self.target.cpu.arch}),
             }
             return .none;
         }
 
-        fn genAsm(self: *Self, inst: *ir.Inst.Assembly) !MCValue {
-            if (!inst.is_volatile and inst.base.isUnused())
+        fn genAsm(self: *Self, inst: Air.Inst.Index) !MCValue {
+            if (!inst.is_volatile and self.liveness.isUnused(inst))
                 return MCValue.dead;
             switch (arch) {
                 .arm, .armeb => {
                     for (inst.inputs) |input, i| {
                         if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
-                            return self.fail(inst.base.src, "unrecognized asm input constraint: '{s}'", .{input});
+                            return self.fail("unrecognized asm input constraint: '{s}'", .{input});
                         }
                         const reg_name = input[1 .. input.len - 1];
                         const reg = parseRegName(reg_name) orelse
-                            return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
+                            return self.fail("unrecognized register: '{s}'", .{reg_name});
 
                         const arg = inst.args[i];
                         const arg_mcv = try self.resolveInst(arg);
                         try self.register_manager.getReg(reg, null);
-                        try self.genSetReg(inst.base.src, arg.ty, reg, arg_mcv);
+                        try self.genSetReg(arg.ty, reg, arg_mcv);
                     }
 
                     if (mem.eql(u8, inst.asm_source, "svc #0")) {
                         writeInt(u32, try self.code.addManyAsArray(4), Instruction.svc(.al, 0).toU32());
                     } else {
-                        return self.fail(inst.base.src, "TODO implement support for more arm assembly instructions", .{});
+                        return self.fail("TODO implement support for more arm assembly instructions", .{});
                     }
 
                     if (inst.output_constraint) |output| {
                         if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
-                            return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output});
+                            return self.fail("unrecognized asm output constraint: '{s}'", .{output});
                         }
                         const reg_name = output[2 .. output.len - 1];
                         const reg = parseRegName(reg_name) orelse
-                            return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
+                            return self.fail("unrecognized register: '{s}'", .{reg_name});
                         return MCValue{ .register = reg };
                     } else {
                         return MCValue.none;
@@ -3297,16 +3418,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .aarch64 => {
                     for (inst.inputs) |input, i| {
                         if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
-                            return self.fail(inst.base.src, "unrecognized asm input constraint: '{s}'", .{input});
+                            return self.fail("unrecognized asm input constraint: '{s}'", .{input});
                         }
                         const reg_name = input[1 .. input.len - 1];
                         const reg = parseRegName(reg_name) orelse
-                            return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
+                            return self.fail("unrecognized register: '{s}'", .{reg_name});
 
                         const arg = inst.args[i];
                         const arg_mcv = try self.resolveInst(arg);
                         try self.register_manager.getReg(reg, null);
-                        try self.genSetReg(inst.base.src, arg.ty, reg, arg_mcv);
+                        try self.genSetReg(arg.ty, reg, arg_mcv);
                     }
 
                     if (mem.eql(u8, inst.asm_source, "svc #0")) {
@@ -3314,16 +3435,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     } else if (mem.eql(u8, inst.asm_source, "svc #0x80")) {
                         mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.svc(0x80).toU32());
                     } else {
-                        return self.fail(inst.base.src, "TODO implement support for more aarch64 assembly instructions", .{});
+                        return self.fail("TODO implement support for more aarch64 assembly instructions", .{});
                     }
 
                     if (inst.output_constraint) |output| {
                         if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
-                            return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output});
+                            return self.fail("unrecognized asm output constraint: '{s}'", .{output});
                         }
                         const reg_name = output[2 .. output.len - 1];
                         const reg = parseRegName(reg_name) orelse
-                            return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
+                            return self.fail("unrecognized register: '{s}'", .{reg_name});
                         return MCValue{ .register = reg };
                     } else {
                         return MCValue.none;
@@ -3332,31 +3453,31 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .riscv64 => {
                     for (inst.inputs) |input, i| {
                         if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
-                            return self.fail(inst.base.src, "unrecognized asm input constraint: '{s}'", .{input});
+                            return self.fail("unrecognized asm input constraint: '{s}'", .{input});
                         }
                         const reg_name = input[1 .. input.len - 1];
                         const reg = parseRegName(reg_name) orelse
-                            return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
+                            return self.fail("unrecognized register: '{s}'", .{reg_name});
 
                         const arg = inst.args[i];
                         const arg_mcv = try self.resolveInst(arg);
                         try self.register_manager.getReg(reg, null);
-                        try self.genSetReg(inst.base.src, arg.ty, reg, arg_mcv);
+                        try self.genSetReg(arg.ty, reg, arg_mcv);
                     }
 
                     if (mem.eql(u8, inst.asm_source, "ecall")) {
                         mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ecall.toU32());
                     } else {
-                        return self.fail(inst.base.src, "TODO implement support for more riscv64 assembly instructions", .{});
+                        return self.fail("TODO implement support for more riscv64 assembly instructions", .{});
                     }
 
                     if (inst.output_constraint) |output| {
                         if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
-                            return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output});
+                            return self.fail("unrecognized asm output constraint: '{s}'", .{output});
                         }
                         const reg_name = output[2 .. output.len - 1];
                         const reg = parseRegName(reg_name) orelse
-                            return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
+                            return self.fail("unrecognized register: '{s}'", .{reg_name});
                         return MCValue{ .register = reg };
                     } else {
                         return MCValue.none;
@@ -3365,16 +3486,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .x86_64, .i386 => {
                     for (inst.inputs) |input, i| {
                         if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
-                            return self.fail(inst.base.src, "unrecognized asm input constraint: '{s}'", .{input});
+                            return self.fail("unrecognized asm input constraint: '{s}'", .{input});
                         }
                         const reg_name = input[1 .. input.len - 1];
                         const reg = parseRegName(reg_name) orelse
-                            return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
+                            return self.fail("unrecognized register: '{s}'", .{reg_name});
 
                         const arg = inst.args[i];
                         const arg_mcv = try self.resolveInst(arg);
                         try self.register_manager.getReg(reg, null);
-                        try self.genSetReg(inst.base.src, arg.ty, reg, arg_mcv);
+                        try self.genSetReg(arg.ty, reg, arg_mcv);
                     }
 
                     {
@@ -3385,68 +3506,68 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             } else if (mem.indexOf(u8, ins, "push")) |_| {
                                 const arg = ins[4..];
                                 if (mem.indexOf(u8, arg, "$")) |l| {
-                                    const n = std.fmt.parseInt(u8, ins[4 + l + 1 ..], 10) catch return self.fail(inst.base.src, "TODO implement more inline asm int parsing", .{});
+                                    const n = std.fmt.parseInt(u8, ins[4 + l + 1 ..], 10) catch return self.fail("TODO implement more inline asm int parsing", .{});
                                     try self.code.appendSlice(&.{ 0x6a, n });
                                 } else if (mem.indexOf(u8, arg, "%%")) |l| {
                                     const reg_name = ins[4 + l + 2 ..];
                                     const reg = parseRegName(reg_name) orelse
-                                        return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
+                                        return self.fail("unrecognized register: '{s}'", .{reg_name});
                                     const low_id: u8 = reg.low_id();
                                     if (reg.isExtended()) {
                                         try self.code.appendSlice(&.{ 0x41, 0b1010000 | low_id });
                                     } else {
                                         try self.code.append(0b1010000 | low_id);
                                     }
-                                } else return self.fail(inst.base.src, "TODO more push operands", .{});
+                                } else return self.fail("TODO more push operands", .{});
                             } else if (mem.indexOf(u8, ins, "pop")) |_| {
                                 const arg = ins[3..];
                                 if (mem.indexOf(u8, arg, "%%")) |l| {
                                     const reg_name = ins[3 + l + 2 ..];
                                     const reg = parseRegName(reg_name) orelse
-                                        return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
+                                        return self.fail("unrecognized register: '{s}'", .{reg_name});
                                     const low_id: u8 = reg.low_id();
                                     if (reg.isExtended()) {
                                         try self.code.appendSlice(&.{ 0x41, 0b1011000 | low_id });
                                     } else {
                                         try self.code.append(0b1011000 | low_id);
                                     }
-                                } else return self.fail(inst.base.src, "TODO more pop operands", .{});
+                                } else return self.fail("TODO more pop operands", .{});
                             } else {
-                                return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{});
+                                return self.fail("TODO implement support for more x86 assembly instructions", .{});
                             }
                         }
                     }
 
                     if (inst.output_constraint) |output| {
                         if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
-                            return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output});
+                            return self.fail("unrecognized asm output constraint: '{s}'", .{output});
                         }
                         const reg_name = output[2 .. output.len - 1];
                         const reg = parseRegName(reg_name) orelse
-                            return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
+                            return self.fail("unrecognized register: '{s}'", .{reg_name});
                         return MCValue{ .register = reg };
                     } else {
                         return MCValue.none;
                     }
                 },
-                else => return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{}),
+                else => return self.fail("TODO implement inline asm support for more architectures", .{}),
             }
         }
 
         /// Sets the value without any modifications to register allocation metadata or stack allocation metadata.
-        fn setRegOrMem(self: *Self, src: LazySrcLoc, ty: Type, loc: MCValue, val: MCValue) !void {
+        fn setRegOrMem(self: *Self, ty: Type, loc: MCValue, val: MCValue) !void {
             switch (loc) {
                 .none => return,
-                .register => |reg| return self.genSetReg(src, ty, reg, val),
-                .stack_offset => |off| return self.genSetStack(src, ty, off, val),
+                .register => |reg| return self.genSetReg(ty, reg, val),
+                .stack_offset => |off| return self.genSetStack(ty, off, val),
                 .memory => {
-                    return self.fail(src, "TODO implement setRegOrMem for memory", .{});
+                    return self.fail("TODO implement setRegOrMem for memory", .{});
                 },
                 else => unreachable,
             }
         }
 
-        fn genSetStack(self: *Self, src: LazySrcLoc, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void {
+        fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void {
             switch (arch) {
                 .arm, .armeb => switch (mcv) {
                     .dead => unreachable,
@@ -3458,28 +3579,28 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             return; // The already existing value will do just fine.
                         // TODO Upgrade this to a memset call when we have that available.
                         switch (ty.abiSize(self.target.*)) {
-                            1 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaa }),
-                            2 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaa }),
-                            4 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
-                            8 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
-                            else => return self.fail(src, "TODO implement memset", .{}),
+                            1 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaa }),
+                            2 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaa }),
+                            4 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
+                            8 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
+                            else => return self.fail("TODO implement memset", .{}),
                         }
                     },
                     .compare_flags_unsigned => |op| {
                         _ = op;
-                        return self.fail(src, "TODO implement set stack variable with compare flags value (unsigned)", .{});
+                        return self.fail("TODO implement set stack variable with compare flags value (unsigned)", .{});
                     },
                     .compare_flags_signed => |op| {
                         _ = op;
-                        return self.fail(src, "TODO implement set stack variable with compare flags value (signed)", .{});
+                        return self.fail("TODO implement set stack variable with compare flags value (signed)", .{});
                     },
                     .immediate => {
-                        const reg = try self.copyToTmpRegister(src, ty, mcv);
-                        return self.genSetStack(src, ty, stack_offset, MCValue{ .register = reg });
+                        const reg = try self.copyToTmpRegister(ty, mcv);
+                        return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
                     },
                     .embedded_in_code => |code_offset| {
                         _ = code_offset;
-                        return self.fail(src, "TODO implement set stack variable from embedded_in_code", .{});
+                        return self.fail("TODO implement set stack variable from embedded_in_code", .{});
                     },
                     .register => |reg| {
                         const abi_size = ty.abiSize(self.target.*);
@@ -3489,7 +3610,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             1, 4 => {
                                 const offset = if (math.cast(u12, adj_off)) |imm| blk: {
                                     break :blk Instruction.Offset.imm(imm);
-                                } else |_| Instruction.Offset.reg(try self.copyToTmpRegister(src, Type.initTag(.u32), MCValue{ .immediate = adj_off }), 0);
+                                } else |_| Instruction.Offset.reg(try self.copyToTmpRegister(Type.initTag(.u32), MCValue{ .immediate = adj_off }), 0);
                                 const str = switch (abi_size) {
                                     1 => Instruction.strb,
                                     4 => Instruction.str,
@@ -3504,26 +3625,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             2 => {
                                 const offset = if (adj_off <= math.maxInt(u8)) blk: {
                                     break :blk Instruction.ExtraLoadStoreOffset.imm(@intCast(u8, adj_off));
-                                } else Instruction.ExtraLoadStoreOffset.reg(try self.copyToTmpRegister(src, Type.initTag(.u32), MCValue{ .immediate = adj_off }));
+                                } else Instruction.ExtraLoadStoreOffset.reg(try self.copyToTmpRegister(Type.initTag(.u32), MCValue{ .immediate = adj_off }));
 
                                 writeInt(u32, try self.code.addManyAsArray(4), Instruction.strh(.al, reg, .fp, .{
                                     .offset = offset,
                                     .positive = false,
                                 }).toU32());
                             },
-                            else => return self.fail(src, "TODO implement storing other types abi_size={}", .{abi_size}),
+                            else => return self.fail("TODO implement storing other types abi_size={}", .{abi_size}),
                         }
                     },
                     .memory => |vaddr| {
                         _ = vaddr;
-                        return self.fail(src, "TODO implement set stack variable from memory vaddr", .{});
+                        return self.fail("TODO implement set stack variable from memory vaddr", .{});
                     },
                     .stack_offset => |off| {
                         if (stack_offset == off)
                             return; // Copy stack variable to itself; nothing to do.
 
-                        const reg = try self.copyToTmpRegister(src, ty, mcv);
-                        return self.genSetStack(src, ty, stack_offset, MCValue{ .register = reg });
+                        const reg = try self.copyToTmpRegister(ty, mcv);
+                        return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
                     },
                 },
                 .x86_64 => switch (mcv) {
@@ -3536,34 +3657,34 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             return; // The already existing value will do just fine.
                         // TODO Upgrade this to a memset call when we have that available.
                         switch (ty.abiSize(self.target.*)) {
-                            1 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaa }),
-                            2 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaa }),
-                            4 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
-                            8 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
-                            else => return self.fail(src, "TODO implement memset", .{}),
+                            1 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaa }),
+                            2 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaa }),
+                            4 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
+                            8 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
+                            else => return self.fail("TODO implement memset", .{}),
                         }
                     },
                     .compare_flags_unsigned => |op| {
                         _ = op;
-                        return self.fail(src, "TODO implement set stack variable with compare flags value (unsigned)", .{});
+                        return self.fail("TODO implement set stack variable with compare flags value (unsigned)", .{});
                     },
                     .compare_flags_signed => |op| {
                         _ = op;
-                        return self.fail(src, "TODO implement set stack variable with compare flags value (signed)", .{});
+                        return self.fail("TODO implement set stack variable with compare flags value (signed)", .{});
                     },
                     .immediate => |x_big| {
                         const abi_size = ty.abiSize(self.target.*);
                         const adj_off = stack_offset + abi_size;
                         if (adj_off > 128) {
-                            return self.fail(src, "TODO implement set stack variable with large stack offset", .{});
+                            return self.fail("TODO implement set stack variable with large stack offset", .{});
                         }
                         try self.code.ensureCapacity(self.code.items.len + 8);
                         switch (abi_size) {
                             1 => {
-                                return self.fail(src, "TODO implement set abi_size=1 stack variable with immediate", .{});
+                                return self.fail("TODO implement set abi_size=1 stack variable with immediate", .{});
                             },
                             2 => {
-                                return self.fail(src, "TODO implement set abi_size=2 stack variable with immediate", .{});
+                                return self.fail("TODO implement set abi_size=2 stack variable with immediate", .{});
                             },
                             4 => {
                                 const x = @intCast(u32, x_big);
@@ -3596,22 +3717,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 self.code.appendSliceAssumeCapacity(buf[0..4]);
                             },
                             else => {
-                                return self.fail(src, "TODO implement set abi_size=large stack variable with immediate", .{});
+                                return self.fail("TODO implement set abi_size=large stack variable with immediate", .{});
                             },
                         }
                     },
                     .embedded_in_code => {
                         // TODO this and `.stack_offset` below need to get improved to support types greater than
                         // register size, and do general memcpy
-                        const reg = try self.copyToTmpRegister(src, ty, mcv);
-                        return self.genSetStack(src, ty, stack_offset, MCValue{ .register = reg });
+                        const reg = try self.copyToTmpRegister(ty, mcv);
+                        return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
                     },
                     .register => |reg| {
                         try self.genX8664ModRMRegToStack(src, ty, stack_offset, reg, 0x89);
                     },
                     .memory => |vaddr| {
                         _ = vaddr;
-                        return self.fail(src, "TODO implement set stack variable from memory vaddr", .{});
+                        return self.fail("TODO implement set stack variable from memory vaddr", .{});
                     },
                     .stack_offset => |off| {
                         // TODO this and `.embedded_in_code` above need to get improved to support types greater than
@@ -3620,8 +3741,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         if (stack_offset == off)
                             return; // Copy stack variable to itself; nothing to do.
 
-                        const reg = try self.copyToTmpRegister(src, ty, mcv);
-                        return self.genSetStack(src, ty, stack_offset, MCValue{ .register = reg });
+                        const reg = try self.copyToTmpRegister(ty, mcv);
+                        return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
                     },
                 },
                 .aarch64, .aarch64_be, .aarch64_32 => switch (mcv) {
@@ -3634,28 +3755,28 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             return; // The already existing value will do just fine.
                         // TODO Upgrade this to a memset call when we have that available.
                         switch (ty.abiSize(self.target.*)) {
-                            1 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaa }),
-                            2 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaa }),
-                            4 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
-                            8 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
-                            else => return self.fail(src, "TODO implement memset", .{}),
+                            1 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaa }),
+                            2 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaa }),
+                            4 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
+                            8 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
+                            else => return self.fail("TODO implement memset", .{}),
                         }
                     },
                     .compare_flags_unsigned => |op| {
                         _ = op;
-                        return self.fail(src, "TODO implement set stack variable with compare flags value (unsigned)", .{});
+                        return self.fail("TODO implement set stack variable with compare flags value (unsigned)", .{});
                     },
                     .compare_flags_signed => |op| {
                         _ = op;
-                        return self.fail(src, "TODO implement set stack variable with compare flags value (signed)", .{});
+                        return self.fail("TODO implement set stack variable with compare flags value (signed)", .{});
                     },
                     .immediate => {
-                        const reg = try self.copyToTmpRegister(src, ty, mcv);
-                        return self.genSetStack(src, ty, stack_offset, MCValue{ .register = reg });
+                        const reg = try self.copyToTmpRegister(ty, mcv);
+                        return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
                     },
                     .embedded_in_code => |code_offset| {
                         _ = code_offset;
-                        return self.fail(src, "TODO implement set stack variable from embedded_in_code", .{});
+                        return self.fail("TODO implement set stack variable from embedded_in_code", .{});
                     },
                     .register => |reg| {
                         const abi_size = ty.abiSize(self.target.*);
@@ -3666,7 +3787,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 const offset = if (math.cast(i9, adj_off)) |imm|
                                     Instruction.LoadStoreOffset.imm_post_index(-imm)
                                 else |_|
-                                    Instruction.LoadStoreOffset.reg(try self.copyToTmpRegister(src, Type.initTag(.u64), MCValue{ .immediate = adj_off }));
+                                    Instruction.LoadStoreOffset.reg(try self.copyToTmpRegister(Type.initTag(.u64), MCValue{ .immediate = adj_off }));
                                 const rn: Register = switch (arch) {
                                     .aarch64, .aarch64_be => .x29,
                                     .aarch64_32 => .w29,
@@ -3683,26 +3804,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                     .offset = offset,
                                 }).toU32());
                             },
-                            else => return self.fail(src, "TODO implement storing other types abi_size={}", .{abi_size}),
+                            else => return self.fail("TODO implement storing other types abi_size={}", .{abi_size}),
                         }
                     },
                     .memory => |vaddr| {
                         _ = vaddr;
-                        return self.fail(src, "TODO implement set stack variable from memory vaddr", .{});
+                        return self.fail("TODO implement set stack variable from memory vaddr", .{});
                     },
                     .stack_offset => |off| {
                         if (stack_offset == off)
                             return; // Copy stack variable to itself; nothing to do.
 
-                        const reg = try self.copyToTmpRegister(src, ty, mcv);
-                        return self.genSetStack(src, ty, stack_offset, MCValue{ .register = reg });
+                        const reg = try self.copyToTmpRegister(ty, mcv);
+                        return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
                     },
                 },
-                else => return self.fail(src, "TODO implement getSetStack for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement getSetStack for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genSetReg(self: *Self, src: LazySrcLoc, ty: Type, reg: Register, mcv: MCValue) InnerError!void {
+        fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void {
             switch (arch) {
                 .arm, .armeb => switch (mcv) {
                     .dead => unreachable,
@@ -3713,7 +3834,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         if (!self.wantSafety())
                             return; // The already existing value will do just fine.
                         // Write the debug undefined value.
-                        return self.genSetReg(src, ty, reg, .{ .immediate = 0xaaaaaaaa });
+                        return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaa });
                     },
                     .compare_flags_unsigned,
                     .compare_flags_signed,
@@ -3732,7 +3853,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         writeInt(u32, try self.code.addManyAsArray(4), Instruction.mov(condition, reg, one).toU32());
                     },
                     .immediate => |x| {
-                        if (x > math.maxInt(u32)) return self.fail(src, "ARM registers are 32-bit wide", .{});
+                        if (x > math.maxInt(u32)) return self.fail("ARM registers are 32-bit wide", .{});
 
                         if (Instruction.Operand.fromU32(@intCast(u32, x))) |op| {
                             writeInt(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, reg, op).toU32());
@@ -3778,7 +3899,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .memory => |addr| {
                         // The value is in memory at a hard-coded address.
                         // If the type is a pointer, it means the pointer address is at this memory location.
-                        try self.genSetReg(src, ty, reg, .{ .immediate = addr });
+                        try self.genSetReg(ty, reg, .{ .immediate = addr });
                         writeInt(u32, try self.code.addManyAsArray(4), Instruction.ldr(.al, reg, reg, .{ .offset = Instruction.Offset.none }).toU32());
                     },
                     .stack_offset => |unadjusted_off| {
@@ -3790,7 +3911,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             1, 4 => {
                                 const offset = if (adj_off <= math.maxInt(u12)) blk: {
                                     break :blk Instruction.Offset.imm(@intCast(u12, adj_off));
-                                } else Instruction.Offset.reg(try self.copyToTmpRegister(src, Type.initTag(.u32), MCValue{ .immediate = adj_off }), 0);
+                                } else Instruction.Offset.reg(try self.copyToTmpRegister(Type.initTag(.u32), MCValue{ .immediate = adj_off }), 0);
                                 const ldr = switch (abi_size) {
                                     1 => Instruction.ldrb,
                                     4 => Instruction.ldr,
@@ -3805,17 +3926,17 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             2 => {
                                 const offset = if (adj_off <= math.maxInt(u8)) blk: {
                                     break :blk Instruction.ExtraLoadStoreOffset.imm(@intCast(u8, adj_off));
-                                } else Instruction.ExtraLoadStoreOffset.reg(try self.copyToTmpRegister(src, Type.initTag(.u32), MCValue{ .immediate = adj_off }));
+                                } else Instruction.ExtraLoadStoreOffset.reg(try self.copyToTmpRegister(Type.initTag(.u32), MCValue{ .immediate = adj_off }));
 
                                 writeInt(u32, try self.code.addManyAsArray(4), Instruction.ldrh(.al, reg, .fp, .{
                                     .offset = offset,
                                     .positive = false,
                                 }).toU32());
                             },
-                            else => return self.fail(src, "TODO a type of size {} is not allowed in a register", .{abi_size}),
+                            else => return self.fail("TODO a type of size {} is not allowed in a register", .{abi_size}),
                         }
                     },
-                    else => return self.fail(src, "TODO implement getSetReg for arm {}", .{mcv}),
+                    else => return self.fail("TODO implement getSetReg for arm {}", .{mcv}),
                 },
                 .aarch64 => switch (mcv) {
                     .dead => unreachable,
@@ -3827,8 +3948,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             return; // The already existing value will do just fine.
                         // Write the debug undefined value.
                         switch (reg.size()) {
-                            32 => return self.genSetReg(src, ty, reg, .{ .immediate = 0xaaaaaaaa }),
-                            64 => return self.genSetReg(src, ty, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
+                            32 => return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaa }),
+                            64 => return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
                             else => unreachable, // unexpected register size
                         }
                     },
@@ -3876,7 +3997,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                     .size = 4,
                                 });
                             } else {
-                                return self.fail(src, "TODO implement genSetReg for PIE GOT indirection on this platform", .{});
+                                return self.fail("TODO implement genSetReg for PIE GOT indirection on this platform", .{});
                             }
                             mem.writeIntLittle(
                                 u32,
@@ -3893,7 +4014,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         } else {
                             // The value is in memory at a hard-coded address.
                             // If the type is a pointer, it means the pointer address is at this memory location.
-                            try self.genSetReg(src, Type.initTag(.usize), reg, .{ .immediate = addr });
+                            try self.genSetReg(Type.initTag(.usize), reg, .{ .immediate = addr });
                             mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ .register = .{ .rn = reg } }).toU32());
                         }
                     },
@@ -3911,7 +4032,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         const offset = if (math.cast(i9, adj_off)) |imm|
                             Instruction.LoadStoreOffset.imm_post_index(-imm)
                         else |_|
-                            Instruction.LoadStoreOffset.reg(try self.copyToTmpRegister(src, Type.initTag(.u64), MCValue{ .immediate = adj_off }));
+                            Instruction.LoadStoreOffset.reg(try self.copyToTmpRegister(Type.initTag(.u64), MCValue{ .immediate = adj_off }));
 
                         switch (abi_size) {
                             1, 2 => {
@@ -3931,10 +4052,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                     .offset = offset,
                                 } }).toU32());
                             },
-                            else => return self.fail(src, "TODO implement genSetReg other types abi_size={}", .{abi_size}),
+                            else => return self.fail("TODO implement genSetReg other types abi_size={}", .{abi_size}),
                         }
                     },
-                    else => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}),
+                    else => return self.fail("TODO implement genSetReg for aarch64 {}", .{mcv}),
                 },
                 .riscv64 => switch (mcv) {
                     .dead => unreachable,
@@ -3945,7 +4066,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         if (!self.wantSafety())
                             return; // The already existing value will do just fine.
                         // Write the debug undefined value.
-                        return self.genSetReg(src, ty, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa });
+                        return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa });
                     },
                     .immediate => |unsigned_x| {
                         const x = @bitCast(i64, unsigned_x);
@@ -3965,19 +4086,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         }
                         // li rd, immediate
                         // "Myriad sequences"
-                        return self.fail(src, "TODO genSetReg 33-64 bit immediates for riscv64", .{}); // glhf
+                        return self.fail("TODO genSetReg 33-64 bit immediates for riscv64", .{}); // glhf
                     },
                     .memory => |addr| {
                         // The value is in memory at a hard-coded address.
                         // If the type is a pointer, it means the pointer address is at this memory location.
-                        try self.genSetReg(src, ty, reg, .{ .immediate = addr });
+                        try self.genSetReg(ty, reg, .{ .immediate = addr });
 
                         mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ld(reg, 0, reg).toU32());
                         // LOAD imm=[i12 offset = 0], rs1 =
 
                         // return self.fail("TODO implement genSetReg memory for riscv64");
                     },
-                    else => return self.fail(src, "TODO implement getSetReg for riscv64 {}", .{mcv}),
+                    else => return self.fail("TODO implement getSetReg for riscv64 {}", .{mcv}),
                 },
                 .x86_64 => switch (mcv) {
                     .dead => unreachable,
@@ -3989,10 +4110,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             return; // The already existing value will do just fine.
                         // Write the debug undefined value.
                         switch (reg.size()) {
-                            8 => return self.genSetReg(src, ty, reg, .{ .immediate = 0xaa }),
-                            16 => return self.genSetReg(src, ty, reg, .{ .immediate = 0xaaaa }),
-                            32 => return self.genSetReg(src, ty, reg, .{ .immediate = 0xaaaaaaaa }),
-                            64 => return self.genSetReg(src, ty, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
+                            8 => return self.genSetReg(ty, reg, .{ .immediate = 0xaa }),
+                            16 => return self.genSetReg(ty, reg, .{ .immediate = 0xaaaa }),
+                            32 => return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaa }),
+                            64 => return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
                             else => unreachable,
                         }
                     },
@@ -4019,7 +4140,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     },
                     .compare_flags_signed => |op| {
                         _ = op;
-                        return self.fail(src, "TODO set register with compare flags value (signed)", .{});
+                        return self.fail("TODO set register with compare flags value (signed)", .{});
                     },
                     .immediate => |x| {
                         // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit
@@ -4152,7 +4273,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                     .size = 4,
                                 });
                             } else {
-                                return self.fail(src, "TODO implement genSetReg for PIE GOT indirection on this platform", .{});
+                                return self.fail("TODO implement genSetReg for PIE GOT indirection on this platform", .{});
                             }
 
                             // MOV reg, [reg]
@@ -4208,7 +4329,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 assert(id3 != 4 and id3 != 5);
 
                                 // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue.
-                                try self.genSetReg(src, ty, reg, MCValue{ .immediate = x });
+                                try self.genSetReg(ty, reg, MCValue{ .immediate = x });
 
                                 // Now, the register contains the address of the value to load into it
                                 // Currently, we're only allowing 64-bit registers, so we need the `REX.W 8B /r` variant.
@@ -4231,7 +4352,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         const abi_size = ty.abiSize(self.target.*);
                         const off = unadjusted_off + abi_size;
                         if (off < std.math.minInt(i32) or off > std.math.maxInt(i32)) {
-                            return self.fail(src, "stack offset too large", .{});
+                            return self.fail("stack offset too large", .{});
                         }
                         const ioff = -@intCast(i32, off);
                         const encoder = try X8664Encoder.init(self.code, 3);
@@ -4251,21 +4372,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         }
                     },
                 },
-                else => return self.fail(src, "TODO implement getSetReg for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement getSetReg for {}", .{self.target.cpu.arch}),
             }
         }
 
-        fn genPtrToInt(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
-            // no-op
-            return self.resolveInst(inst.operand);
+        fn genPtrToInt(self: *Self, inst: Air.Inst.Index) !MCValue {
+            const inst_datas = self.air.instructions.items(.data);
+            return self.resolveInst(inst_datas[inst].un_op);
         }
 
-        fn genBitCast(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
-            const operand = try self.resolveInst(inst.operand);
-            return operand;
+        fn genBitCast(self: *Self, inst: Air.Inst.Index) !MCValue {
+            const inst_datas = self.air.instructions.items(.data);
+            return self.resolveInst(inst_datas[inst].ty_op.operand);
         }
 
-        fn resolveInst(self: *Self, inst: *ir.Inst) !MCValue {
+        fn resolveInst(self: *Self, inst: Air.Inst.Index) !MCValue {
             // If the type has no codegen bits, no need to store it.
             if (!inst.ty.hasCodeGenBits())
                 return MCValue.none;
@@ -4283,7 +4404,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return self.getResolvedInstValue(inst);
         }
 
-        fn getResolvedInstValue(self: *Self, inst: *ir.Inst) MCValue {
+        fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue {
             // Treat each stack item as a "layer" on top of the previous one.
             var i: usize = self.branch_stack.items.len;
             while (true) {
@@ -4300,7 +4421,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         /// A potential opportunity for future optimization here would be keeping track
         /// of the fact that the instruction is available both as an immediate
         /// and as a register.
-        fn limitImmediateType(self: *Self, inst: *ir.Inst, comptime T: type) !MCValue {
+        fn limitImmediateType(self: *Self, inst: Air.Inst.Index, comptime T: type) !MCValue {
             const mcv = try self.resolveInst(inst);
             const ti = @typeInfo(T).Int;
             switch (mcv) {
@@ -4308,7 +4429,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     // This immediate is unsigned.
                     const U = std.meta.Int(.unsigned, ti.bits - @boolToInt(ti.signedness == .signed));
                     if (imm >= math.maxInt(U)) {
-                        return MCValue{ .register = try self.copyToTmpRegister(inst.src, Type.initTag(.usize), mcv) };
+                        return MCValue{ .register = try self.copyToTmpRegister(Type.initTag(.usize), mcv) };
                     }
                 },
                 else => {},
@@ -4334,7 +4455,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         _ = slice_len;
                         _ = ptr_imm;
                         // We need more general support for const data being stored in memory to make this work.
-                        return self.fail(src, "TODO codegen for const slices", .{});
+                        return self.fail("TODO codegen for const slices", .{});
                     },
                     else => {
                         if (typed_value.val.castTag(.decl_ref)) |payload| {
@@ -4360,19 +4481,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes;
                                 return MCValue{ .memory = got_addr };
                             } else {
-                                return self.fail(src, "TODO codegen non-ELF const Decl pointer", .{});
+                                return self.fail("TODO codegen non-ELF const Decl pointer", .{});
                             }
                         }
                         if (typed_value.val.tag() == .int_u64) {
                             return MCValue{ .immediate = typed_value.val.toUnsignedInt() };
                         }
-                        return self.fail(src, "TODO codegen more kinds of const pointers", .{});
+                        return self.fail("TODO codegen more kinds of const pointers", .{});
                     },
                 },
                 .Int => {
                     const info = typed_value.ty.intInfo(self.target.*);
                     if (info.bits > ptr_bits or info.signedness == .signed) {
-                        return self.fail(src, "TODO const int bigger than ptr and signed int", .{});
+                        return self.fail("TODO const int bigger than ptr and signed int", .{});
                     }
                     return MCValue{ .immediate = typed_value.val.toUnsignedInt() };
                 },
@@ -4394,9 +4515,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     } else if (typed_value.ty.abiSize(self.target.*) == 1) {
                         return MCValue{ .immediate = @boolToInt(typed_value.val.isNull()) };
                     }
-                    return self.fail(src, "TODO non pointer optionals", .{});
+                    return self.fail("TODO non pointer optionals", .{});
                 },
-                else => return self.fail(src, "TODO implement const of type '{}'", .{typed_value.ty}),
+                else => return self.fail("TODO implement const of type '{}'", .{typed_value.ty}),
             }
         }
 
@@ -4413,7 +4534,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         };
 
         /// Caller must call `CallMCValues.deinit`.
-        fn resolveCallingConventionValues(self: *Self, src: LazySrcLoc, fn_ty: Type) !CallMCValues {
+        fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues {
             const cc = fn_ty.fnCallingConvention();
             const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
             defer self.gpa.free(param_types);
@@ -4482,7 +4603,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             result.stack_byte_count = next_stack_offset;
                             result.stack_align = 16;
                         },
-                        else => return self.fail(src, "TODO implement function parameters for {} on x86_64", .{cc}),
+                        else => return self.fail("TODO implement function parameters for {} on x86_64", .{cc}),
                     }
                 },
                 .arm, .armeb => {
@@ -4509,10 +4630,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                         result.args[i] = .{ .register = c_abi_int_param_regs[ncrn] };
                                         ncrn += 1;
                                     } else {
-                                        return self.fail(src, "TODO MCValues with multiple registers", .{});
+                                        return self.fail("TODO MCValues with multiple registers", .{});
                                     }
                                 } else if (ncrn < 4 and nsaa == 0) {
-                                    return self.fail(src, "TODO MCValues split between registers and stack", .{});
+                                    return self.fail("TODO MCValues split between registers and stack", .{});
                                 } else {
                                     ncrn = 4;
                                     if (ty.abiAlignment(self.target.*) == 8)
@@ -4526,7 +4647,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             result.stack_byte_count = nsaa;
                             result.stack_align = 4;
                         },
-                        else => return self.fail(src, "TODO implement function parameters for {} on arm", .{cc}),
+                        else => return self.fail("TODO implement function parameters for {} on arm", .{cc}),
                     }
                 },
                 .aarch64 => {
@@ -4557,10 +4678,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                         result.args[i] = .{ .register = c_abi_int_param_regs[ncrn] };
                                         ncrn += 1;
                                     } else {
-                                        return self.fail(src, "TODO MCValues with multiple registers", .{});
+                                        return self.fail("TODO MCValues with multiple registers", .{});
                                     }
                                 } else if (ncrn < 8 and nsaa == 0) {
-                                    return self.fail(src, "TODO MCValues split between registers and stack", .{});
+                                    return self.fail("TODO MCValues split between registers and stack", .{});
                                 } else {
                                     ncrn = 8;
                                     // TODO Apple allows the arguments on the stack to be non-8-byte aligned provided
@@ -4579,11 +4700,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             result.stack_byte_count = nsaa;
                             result.stack_align = 16;
                         },
-                        else => return self.fail(src, "TODO implement function parameters for {} on aarch64", .{cc}),
+                        else => return self.fail("TODO implement function parameters for {} on aarch64", .{cc}),
                     }
                 },
                 else => if (param_types.len != 0)
-                    return self.fail(src, "TODO implement codegen parameters for {}", .{self.target.cpu.arch}),
+                    return self.fail("TODO implement codegen parameters for {}", .{self.target.cpu.arch}),
             }
 
             if (ret_ty.zigTypeTag() == .NoReturn) {
@@ -4598,7 +4719,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         const aliased_reg = registerAlias(c_abi_int_return_regs[0], ret_ty_size);
                         result.return_value = .{ .register = aliased_reg };
                     },
-                    else => return self.fail(src, "TODO implement function return values for {}", .{cc}),
+                    else => return self.fail("TODO implement function return values for {}", .{cc}),
                 },
                 .arm, .armeb => switch (cc) {
                     .Naked => unreachable,
@@ -4607,10 +4728,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         if (ret_ty_size <= 4) {
                             result.return_value = .{ .register = c_abi_int_return_regs[0] };
                         } else {
-                            return self.fail(src, "TODO support more return types for ARM backend", .{});
+                            return self.fail("TODO support more return types for ARM backend", .{});
                         }
                     },
-                    else => return self.fail(src, "TODO implement function return values for {}", .{cc}),
+                    else => return self.fail("TODO implement function return values for {}", .{cc}),
                 },
                 .aarch64 => switch (cc) {
                     .Naked => unreachable,
@@ -4619,12 +4740,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         if (ret_ty_size <= 8) {
                             result.return_value = .{ .register = c_abi_int_return_regs[0] };
                         } else {
-                            return self.fail(src, "TODO support more return types for ARM backend", .{});
+                            return self.fail("TODO support more return types for ARM backend", .{});
                         }
                     },
-                    else => return self.fail(src, "TODO implement function return values for {}", .{cc}),
+                    else => return self.fail("TODO implement function return values for {}", .{cc}),
                 },
-                else => return self.fail(src, "TODO implement codegen return values for {}", .{self.target.cpu.arch}),
+                else => return self.fail("TODO implement codegen return values for {}", .{self.target.cpu.arch}),
             }
             return result;
         }
src/Liveness.zig
@@ -74,6 +74,26 @@ pub fn analyze(gpa: *Allocator, air: Air) Allocator.Error!Liveness {
     };
 }
 
+pub fn isUnused(l: Liveness, inst: Air.Inst.Index) bool {
+    const usize_index = (inst * bpi) / @bitSizeOf(usize);
+    const mask = @as(usize, 1) << ((inst % (@bitSizeOf(usize) / bpi)) * bpi + (bpi - 1));
+    return (l.tomb_bits[usize_index] & mask) != 0;
+}
+
+pub fn operandDies(l: Liveness, inst: Air.Inst.Index, operand: OperandInt) bool {
+    assert(operand < bpi - 1);
+    const usize_index = (inst * bpi) / @bitSizeOf(usize);
+    const mask = @as(usize, 1) << ((inst % (@bitSizeOf(usize) / bpi)) * bpi + operand);
+    return (l.tomb_bits[usize_index] & mask) != 0;
+}
+
+pub fn clearOperandDeath(l: *Liveness, inst: Air.Inst.Index, operand: OperandInt) void {
+    assert(operand < bpi - 1);
+    const usize_index = (inst * bpi) / @bitSizeOf(usize);
+    const mask = @as(usize, 1) << ((inst % (@bitSizeOf(usize) / bpi)) * bpi + operand);
+    l.tomb_bits[usize_index] |= mask;
+}
+
 pub fn deinit(l: *Liveness, gpa: *Allocator) void {
     gpa.free(l.tomb_bits);
     gpa.free(l.extra);
@@ -83,6 +103,7 @@ pub fn deinit(l: *Liveness, gpa: *Allocator) void {
 /// How many tomb bits per AIR instruction.
 const bpi = 4;
 const Bpi = std.meta.Int(.unsigned, bpi);
+const OperandInt = std.math.Log2Int(Bpi);
 
 /// In-progress data; on successful analysis converted into `Liveness`.
 const Analysis = struct {
src/register_manager.zig
@@ -147,14 +147,14 @@ pub fn RegisterManager(
                             self.markRegUsed(reg);
                         } else {
                             const spilled_inst = self.registers[index].?;
-                            try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
+                            try self.getFunction().spillInstruction(reg, spilled_inst);
                         }
                         self.registers[index] = inst;
                     } else {
                         // Don't track the register
                         if (!self.isRegFree(reg)) {
                             const spilled_inst = self.registers[index].?;
-                            try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
+                            try self.getFunction().spillInstruction(reg, spilled_inst);
                             self.freeReg(reg);
                         }
                     }
@@ -184,7 +184,7 @@ pub fn RegisterManager(
                     // stack allocation.
                     const spilled_inst = self.registers[index].?;
                     self.registers[index] = tracked_inst;
-                    try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
+                    try self.getFunction().spillInstruction(reg, spilled_inst);
                 } else {
                     self.getRegAssumeFree(reg, tracked_inst);
                 }
@@ -193,7 +193,7 @@ pub fn RegisterManager(
                     // Move the instruction that was previously there to a
                     // stack allocation.
                     const spilled_inst = self.registers[index].?;
-                    try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
+                    try self.getFunction().spillInstruction(reg, spilled_inst);
                     self.freeReg(reg);
                 }
             }
@@ -264,8 +264,7 @@ fn MockFunction(comptime Register: type) type {
             self.spilled.deinit(self.allocator);
         }
 
-        pub fn spillInstruction(self: *Self, src: LazySrcLoc, reg: Register, inst: *ir.Inst) !void {
-            _ = src;
+        pub fn spillInstruction(self: *Self, reg: Register, inst: *ir.Inst) !void {
             _ = inst;
             try self.spilled.append(self.allocator, reg);
         }
BRANCH_TODO
@@ -1,48 +1,10 @@
  * be sure to test debug info of parameters
 
 
-    pub fn isUnused(self: Inst) bool {
-        return (self.deaths & (1 << unreferenced_bit_index)) != 0;
-    }
-
-    pub fn operandDies(self: Inst, index: DeathsBitIndex) bool {
-        assert(index < deaths_bits);
-        return @truncate(u1, self.deaths >> index) != 0;
-    }
-
-    pub fn clearOperandDeath(self: *Inst, index: DeathsBitIndex) void {
-        assert(index < deaths_bits);
-        self.deaths &= ~(@as(DeathsInt, 1) << index);
-    }
-
     pub fn specialOperandDeaths(self: Inst) bool {
         return (self.deaths & (1 << deaths_bits)) != 0;
     }
 
-    pub fn operandCount(base: *Inst) usize {
-        inline for (@typeInfo(Tag).Enum.fields) |field| {
-            const tag = @intToEnum(Tag, field.value);
-            if (tag == base.tag) {
-                return @fieldParentPtr(tag.Type(), "base", base).operandCount();
-            }
-        }
-        unreachable;
-    }
-
-    pub fn getOperand(base: *Inst, index: usize) ?*Inst {
-        inline for (@typeInfo(Tag).Enum.fields) |field| {
-            const tag = @intToEnum(Tag, field.value);
-            if (tag == base.tag) {
-                return @fieldParentPtr(tag.Type(), "base", base).getOperand(index);
-            }
-        }
-        unreachable;
-    }
-
-    pub fn Args(comptime T: type) type {
-        return std.meta.fieldInfo(T, .args).field_type;
-    }
-
     /// Returns `null` if runtime-known.
     /// Should be called by codegen, not by Sema. Sema functions should call
     /// `resolvePossiblyUndefinedValue` or `resolveDefinedValue` instead.