Commit 4a0f38bb76

Andrew Kelley <andrew@ziglang.org>
2021-07-19 07:26:36
stage2: update LLVM backend to new AIR memory layout
Also fix compile errors when not using -Dskip-non-native
1 parent 33aab2c
src/codegen/c.zig
@@ -974,6 +974,9 @@ fn airArg(o: *Object) CValue {
 
 fn airLoad(o: *Object, inst: Air.Inst.Index) !CValue {
     const ty_op = o.air.instructions.items(.data)[inst].ty_op;
+    const is_volatile = o.air.typeOf(ty_op.operand).isVolatilePtr();
+    if (!is_volatile and o.liveness.isUnused(inst))
+        return CValue.none;
     const inst_ty = o.air.typeOfIndex(inst);
     const operand = try o.resolveInst(ty_op.operand);
     const writer = o.writer();
src/codegen/llvm.zig
@@ -10,7 +10,7 @@ const math = std.math;
 const Module = @import("../Module.zig");
 const TypedValue = @import("../TypedValue.zig");
 const Air = @import("../Air.zig");
-const Inst = ir.Inst;
+const Liveness = @import("../Liveness.zig");
 
 const Value = @import("../value.zig").Value;
 const Type = @import("../type.zig").Type;
@@ -355,6 +355,7 @@ pub const DeclGen = struct {
             builder.positionBuilderAtEnd(entry_block);
 
             var fg: FuncGen = .{
+                .gpa = self.gpa,
                 .dg = self,
                 .builder = builder,
                 .args = args,
@@ -593,29 +594,29 @@ pub const DeclGen = struct {
 };
 
 pub const FuncGen = struct {
+    gpa: *Allocator,
     dg: *DeclGen,
 
     builder: *const llvm.Builder,
 
-    /// This stores the LLVM values used in a function, such that they can be
-    /// referred to in other instructions. This table is cleared before every function is generated.
-    /// TODO: Change this to a stack of Branch. Currently we store all the values from all the blocks
-    /// in here, however if a block ends, the instructions can be thrown away.
-    func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.Value),
+    /// This stores the LLVM values used in a function, such that they can be referred to
+    /// in other instructions. This table is cleared before every function is generated.
+    func_inst_table: std.AutoHashMapUnmanaged(Air.Inst.Index, *const llvm.Value),
 
-    /// These fields are used to refer to the LLVM value of the function paramaters in an Arg instruction.
+    /// These fields are used to refer to the LLVM value of the function paramaters
+    /// in an Arg instruction.
     args: []*const llvm.Value,
     arg_index: usize,
 
     entry_block: *const llvm.BasicBlock,
-    /// This fields stores the last alloca instruction, such that we can append more alloca instructions
-    /// to the top of the function.
+    /// This fields stores the last alloca instruction, such that we can append
+    /// more alloca instructions to the top of the function.
     latest_alloca_inst: ?*const llvm.Value,
 
     llvm_func: *const llvm.Value,
 
     /// This data structure is used to implement breaking to blocks.
-    blocks: std.AutoHashMapUnmanaged(*Inst.Block, struct {
+    blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
         parent_bb: *const llvm.BasicBlock,
         break_bbs: *BreakBasicBlocks,
         break_vals: *BreakValues,
@@ -626,9 +627,9 @@ pub const FuncGen = struct {
 
     fn deinit(self: *FuncGen) void {
         self.builder.dispose();
-        self.func_inst_table.deinit(self.gpa());
-        self.gpa().free(self.args);
-        self.blocks.deinit(self.gpa());
+        self.func_inst_table.deinit(self.gpa);
+        self.gpa.free(self.args);
+        self.blocks.deinit(self.gpa);
     }
 
     fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } {
@@ -644,13 +645,9 @@ pub const FuncGen = struct {
         return self.dg.object.context;
     }
 
-    fn gpa(self: *FuncGen) *Allocator {
-        return self.dg.gpa;
-    }
-
-    fn resolveInst(self: *FuncGen, inst: *ir.Inst) !*const llvm.Value {
-        if (inst.value()) |val| {
-            return self.dg.genTypedValue(.{ .ty = inst.ty, .val = val }, self);
+    fn resolveInst(self: *FuncGen, inst: Air.Inst.Ref) !*const llvm.Value {
+        if (self.air.value(inst)) |val| {
+            return self.dg.genTypedValue(.{ .ty = self.air.typeOf(inst), .val = val }, self);
         }
         if (self.func_inst_table.get(inst)) |value| return value;
 
@@ -658,51 +655,57 @@ pub const FuncGen = struct {
     }
 
     fn genBody(self: *FuncGen, body: ir.Body) error{ OutOfMemory, CodegenFail }!void {
+        const air_tags = self.air.instructions.items(.tag);
         for (body.instructions) |inst| {
-            const opt_value = switch (inst.tag) {
-                .add => try self.genAdd(inst.castTag(.add).?),
-                .alloc => try self.genAlloc(inst.castTag(.alloc).?),
-                .arg => try self.genArg(inst.castTag(.arg).?),
-                .bitcast => try self.genBitCast(inst.castTag(.bitcast).?),
-                .block => try self.genBlock(inst.castTag(.block).?),
-                .br => try self.genBr(inst.castTag(.br).?),
-                .breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?),
-                .br_void => try self.genBrVoid(inst.castTag(.br_void).?),
-                .call => try self.genCall(inst.castTag(.call).?),
-                .cmp_eq => try self.genCmp(inst.castTag(.cmp_eq).?, .eq),
-                .cmp_gt => try self.genCmp(inst.castTag(.cmp_gt).?, .gt),
-                .cmp_gte => try self.genCmp(inst.castTag(.cmp_gte).?, .gte),
-                .cmp_lt => try self.genCmp(inst.castTag(.cmp_lt).?, .lt),
-                .cmp_lte => try self.genCmp(inst.castTag(.cmp_lte).?, .lte),
-                .cmp_neq => try self.genCmp(inst.castTag(.cmp_neq).?, .neq),
-                .condbr => try self.genCondBr(inst.castTag(.condbr).?),
-                .intcast => try self.genIntCast(inst.castTag(.intcast).?),
-                .is_non_null => try self.genIsNonNull(inst.castTag(.is_non_null).?, false),
-                .is_non_null_ptr => try self.genIsNonNull(inst.castTag(.is_non_null_ptr).?, true),
-                .is_null => try self.genIsNull(inst.castTag(.is_null).?, false),
-                .is_null_ptr => try self.genIsNull(inst.castTag(.is_null_ptr).?, true),
-                .load => try self.genLoad(inst.castTag(.load).?),
-                .loop => try self.genLoop(inst.castTag(.loop).?),
-                .not => try self.genNot(inst.castTag(.not).?),
-                .ret => try self.genRet(inst.castTag(.ret).?),
-                .retvoid => self.genRetVoid(inst.castTag(.retvoid).?),
-                .store => try self.genStore(inst.castTag(.store).?),
-                .sub => try self.genSub(inst.castTag(.sub).?),
-                .unreach => self.genUnreach(inst.castTag(.unreach).?),
-                .optional_payload => try self.genOptionalPayload(inst.castTag(.optional_payload).?, false),
-                .optional_payload_ptr => try self.genOptionalPayload(inst.castTag(.optional_payload_ptr).?, true),
+            const opt_value = switch (air_tags[inst]) {
+                .add => try self.airAdd(inst),
+                .sub => try self.airSub(inst),
+
+                .cmp_eq => try self.airCmp(inst, .eq),
+                .cmp_gt => try self.airCmp(inst, .gt),
+                .cmp_gte => try self.airCmp(inst, .gte),
+                .cmp_lt => try self.airCmp(inst, .lt),
+                .cmp_lte => try self.airCmp(inst, .lte),
+                .cmp_neq => try self.airCmp(inst, .neq),
+
+                .is_non_null => try self.airIsNonNull(inst, false),
+                .is_non_null_ptr => try self.airIsNonNull(inst, true),
+                .is_null => try self.airIsNull(inst, false),
+                .is_null_ptr => try self.airIsNull(inst, true),
+
+                .alloc => try self.airAlloc(inst),
+                .arg => try self.airArg(inst),
+                .bitcast => try self.airBitCast(inst),
+                .block => try self.airBlock(inst),
+                .br => try self.airBr(inst),
+                .breakpoint => try self.airBreakpoint(inst),
+                .call => try self.airCall(inst),
+                .cond_br => try self.airCondBr(inst),
+                .intcast => try self.airIntCast(inst),
+                .load => try self.airLoad(inst),
+                .loop => try self.airLoop(inst),
+                .not => try self.airNot(inst),
+                .ret => try self.airRet(inst),
+                .store => try self.airStore(inst),
+                .unreach => self.airUnreach(inst),
+                .optional_payload => try self.airOptionalPayload(inst, false),
+                .optional_payload_ptr => try self.airOptionalPayload(inst, true),
                 .dbg_stmt => blk: {
                     // TODO: implement debug info
                     break :blk null;
                 },
-                else => |tag| return self.todo("implement TZIR instruction: {}", .{tag}),
+                else => |tag| return self.todo("implement AIR instruction: {}", .{tag}),
             };
-            if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa(), inst, val);
+            if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa, inst, val);
         }
     }
 
-    fn genCall(self: *FuncGen, inst: *Inst.Call) !?*const llvm.Value {
-        if (inst.func.value()) |func_value| {
+    fn airCall(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        const pl_op = self.air.instructions.items(.data)[inst].pl_op;
+        const extra = self.air.extraData(Air.Call, pl_op.payload);
+        const args = @bitCast([]const Air.Inst.Ref, self.air.extra[extra.end..][0..extra.data.args_len]);
+
+        if (self.air.value(pl_op.operand)) |func_value| {
             const fn_decl = if (func_value.castTag(.extern_fn)) |extern_fn|
                 extern_fn.data
             else if (func_value.castTag(.function)) |func_payload|
@@ -714,12 +717,10 @@ pub const FuncGen = struct {
             const zig_fn_type = fn_decl.ty;
             const llvm_fn = try self.dg.resolveLLVMFunction(fn_decl);
 
-            const num_args = inst.args.len;
-
-            const llvm_param_vals = try self.gpa().alloc(*const llvm.Value, num_args);
-            defer self.gpa().free(llvm_param_vals);
+            const llvm_param_vals = try self.gpa.alloc(*const llvm.Value, args.len);
+            defer self.gpa.free(llvm_param_vals);
 
-            for (inst.args) |arg, i| {
+            for (args) |arg, i| {
                 llvm_param_vals[i] = try self.resolveInst(arg);
             }
 
@@ -727,8 +728,8 @@ pub const FuncGen = struct {
             //       Do we need that?
             const call = self.builder.buildCall(
                 llvm_fn,
-                if (num_args == 0) null else llvm_param_vals.ptr,
-                @intCast(c_uint, num_args),
+                if (args.len == 0) null else llvm_param_vals.ptr,
+                @intCast(c_uint, args.len),
                 "",
             );
 
@@ -746,31 +747,31 @@ pub const FuncGen = struct {
         }
     }
 
-    fn genRetVoid(self: *FuncGen, inst: *Inst.NoOp) ?*const llvm.Value {
-        _ = inst;
-        _ = self.builder.buildRetVoid();
-        return null;
-    }
-
-    fn genRet(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
-        if (!inst.operand.ty.hasCodeGenBits()) {
-            // TODO: in astgen these instructions should turn into `retvoid` instructions.
+    fn airRet(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        const un_op = self.air.instructions.items(.data)[inst].un_op;
+        if (!self.air.typeOf(un_op).hasCodeGenBits()) {
             _ = self.builder.buildRetVoid();
             return null;
         }
-        _ = self.builder.buildRet(try self.resolveInst(inst.operand));
+        const operand = try self.resolveInst(un_op);
+        _ = self.builder.buildRet(operand);
         return null;
     }
 
-    fn genCmp(self: *FuncGen, inst: *Inst.BinOp, op: math.CompareOperator) !?*const llvm.Value {
-        const lhs = try self.resolveInst(inst.lhs);
-        const rhs = try self.resolveInst(inst.rhs);
+    fn airCmp(self: *FuncGen, inst: Air.Inst.Index, op: math.CompareOperator) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const lhs = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        const inst_ty = self.air.typeOfIndex(inst);
 
-        if (!inst.base.ty.isInt())
-            if (inst.base.ty.tag() != .bool)
-                return self.todo("implement 'genCmp' for type {}", .{inst.base.ty});
+        if (!inst_ty.isInt())
+            if (inst_ty.tag() != .bool)
+                return self.todo("implement 'airCmp' for type {}", .{inst_ty});
 
-        const is_signed = inst.base.ty.isSignedInt();
+        const is_signed = inst_ty.isSignedInt();
         const operation = switch (op) {
             .eq => .EQ,
             .neq => .NE,
@@ -783,32 +784,36 @@ pub const FuncGen = struct {
         return self.builder.buildICmp(operation, lhs, rhs, "");
     }
 
-    fn genBlock(self: *FuncGen, inst: *Inst.Block) !?*const llvm.Value {
+    fn airBlock(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+        const extra = self.air.extraData(Air.Block, ty_pl.payload);
+        const body = self.air.extra[extra.end..][0..extra.data.body_len];
         const parent_bb = self.context().createBasicBlock("Block");
 
         // 5 breaks to a block seems like a reasonable default.
-        var break_bbs = try BreakBasicBlocks.initCapacity(self.gpa(), 5);
-        var break_vals = try BreakValues.initCapacity(self.gpa(), 5);
-        try self.blocks.putNoClobber(self.gpa(), inst, .{
+        var break_bbs = try BreakBasicBlocks.initCapacity(self.gpa, 5);
+        var break_vals = try BreakValues.initCapacity(self.gpa, 5);
+        try self.blocks.putNoClobber(self.gpa, inst, .{
             .parent_bb = parent_bb,
             .break_bbs = &break_bbs,
             .break_vals = &break_vals,
         });
         defer {
             assert(self.blocks.remove(inst));
-            break_bbs.deinit(self.gpa());
-            break_vals.deinit(self.gpa());
+            break_bbs.deinit(self.gpa);
+            break_vals.deinit(self.gpa);
         }
 
-        try self.genBody(inst.body);
+        try self.genBody(body);
 
         self.llvm_func.appendExistingBasicBlock(parent_bb);
         self.builder.positionBuilderAtEnd(parent_bb);
 
         // If the block does not return a value, we dont have to create a phi node.
-        if (!inst.base.ty.hasCodeGenBits()) return null;
+        const inst_ty = self.air.typeOfIndex(inst);
+        if (!inst_ty.hasCodeGenBits()) return null;
 
-        const phi_node = self.builder.buildPhi(try self.dg.getLLVMType(inst.base.ty), "");
+        const phi_node = self.builder.buildPhi(try self.dg.getLLVMType(inst_ty), "");
         phi_node.addIncoming(
             break_vals.items.ptr,
             break_bbs.items.ptr,
@@ -817,35 +822,30 @@ pub const FuncGen = struct {
         return phi_node;
     }
 
-    fn genBr(self: *FuncGen, inst: *Inst.Br) !?*const llvm.Value {
-        var block = self.blocks.get(inst.block).?;
+    fn airBr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        const branch = self.air.instructions.items(.data)[inst].br;
+        const block = self.blocks.get(branch.block_inst).?;
 
         // If the break doesn't break a value, then we don't have to add
         // the values to the lists.
-        if (!inst.operand.ty.hasCodeGenBits()) {
-            // TODO: in astgen these instructions should turn into `br_void` instructions.
-            _ = self.builder.buildBr(block.parent_bb);
-        } else {
-            const val = try self.resolveInst(inst.operand);
+        if (self.air.typeOf(branch.result).hasCodeGenBits()) {
+            const val = try self.resolveInst(branch.result);
 
             // For the phi node, we need the basic blocks and the values of the
             // break instructions.
-            try block.break_bbs.append(self.gpa(), self.builder.getInsertBlock());
-            try block.break_vals.append(self.gpa(), val);
-
-            _ = self.builder.buildBr(block.parent_bb);
+            try block.break_bbs.append(self.gpa, self.builder.getInsertBlock());
+            try block.break_vals.append(self.gpa, val);
         }
-        return null;
-    }
-
-    fn genBrVoid(self: *FuncGen, inst: *Inst.BrVoid) !?*const llvm.Value {
-        var block = self.blocks.get(inst.block).?;
         _ = self.builder.buildBr(block.parent_bb);
         return null;
     }
 
-    fn genCondBr(self: *FuncGen, inst: *Inst.CondBr) !?*const llvm.Value {
-        const condition_value = try self.resolveInst(inst.condition);
+    fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        const pl_op = self.air.instructions.items(.data)[inst].pl_op;
+        const cond = try self.resolveInst(pl_op.operand);
+        const extra = self.air.extraData(Air.CondBr, pl_op.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 then_block = self.context().appendBasicBlock(self.llvm_func, "Then");
         const else_block = self.context().appendBasicBlock(self.llvm_func, "Else");
@@ -854,38 +854,51 @@ pub const FuncGen = struct {
             defer self.builder.positionBuilderAtEnd(prev_block);
 
             self.builder.positionBuilderAtEnd(then_block);
-            try self.genBody(inst.then_body);
+            try self.genBody(then_body);
 
             self.builder.positionBuilderAtEnd(else_block);
-            try self.genBody(inst.else_body);
+            try self.genBody(else_body);
         }
-        _ = self.builder.buildCondBr(condition_value, then_block, else_block);
+        _ = self.builder.buildCondBr(cond, then_block, else_block);
         return null;
     }
 
-    fn genLoop(self: *FuncGen, inst: *Inst.Loop) !?*const llvm.Value {
+    fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+        const loop = self.air.extraData(Air.Block, ty_pl.payload);
+        const body = self.air.extra[loop.end..][0..loop.data.body_len];
         const loop_block = self.context().appendBasicBlock(self.llvm_func, "Loop");
         _ = self.builder.buildBr(loop_block);
 
         self.builder.positionBuilderAtEnd(loop_block);
-        try self.genBody(inst.body);
+        try self.genBody(body);
 
         _ = self.builder.buildBr(loop_block);
         return null;
     }
 
-    fn genNot(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
-        return self.builder.buildNot(try self.resolveInst(inst.operand), "");
+    fn airNot(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const un_op = self.air.instructions.items(.data)[inst].un_op;
+        const operand = try self.resolveInst(un_op);
+
+        return self.builder.buildNot(operand, "");
     }
 
-    fn genUnreach(self: *FuncGen, inst: *Inst.NoOp) ?*const llvm.Value {
+    fn airUnreach(self: *FuncGen, inst: Air.Inst.Index) ?*const llvm.Value {
         _ = inst;
         _ = self.builder.buildUnreachable();
         return null;
     }
 
-    fn genIsNonNull(self: *FuncGen, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value {
-        const operand = try self.resolveInst(inst.operand);
+    fn airIsNonNull(self: *FuncGen, inst: Air.Inst.Index, operand_is_ptr: bool) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const un_op = self.air.instructions.items(.data)[inst].un_op;
+        const operand = try self.resolveInst(un_op);
 
         if (operand_is_ptr) {
             const index_type = self.context().intType(32);
@@ -901,12 +914,23 @@ pub const FuncGen = struct {
         }
     }
 
-    fn genIsNull(self: *FuncGen, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value {
-        return self.builder.buildNot((try self.genIsNonNull(inst, operand_is_ptr)).?, "");
+    fn airIsNull(self: *FuncGen, inst: Air.Inst.Index, operand_is_ptr: bool) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        return self.builder.buildNot((try self.airIsNonNull(inst, operand_is_ptr)).?, "");
     }
 
-    fn genOptionalPayload(self: *FuncGen, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value {
-        const operand = try self.resolveInst(inst.operand);
+    fn airOptionalPayload(
+        self: *FuncGen,
+        inst: Air.Inst.Index,
+        operand_is_ptr: bool,
+    ) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+        const operand = try self.resolveInst(ty_op.operand);
 
         if (operand_is_ptr) {
             const index_type = self.context().intType(32);
@@ -922,61 +946,83 @@ pub const FuncGen = struct {
         }
     }
 
-    fn genAdd(self: *FuncGen, inst: *Inst.BinOp) !?*const llvm.Value {
-        const lhs = try self.resolveInst(inst.lhs);
-        const rhs = try self.resolveInst(inst.rhs);
+    fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const lhs = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        const inst_ty = self.air.typeOfIndex(inst);
 
-        if (!inst.base.ty.isInt())
-            return self.todo("implement 'genAdd' for type {}", .{inst.base.ty});
+        if (!inst_ty.isInt())
+            return self.todo("implement 'airAdd' for type {}", .{inst_ty});
 
-        return if (inst.base.ty.isSignedInt())
+        return if (inst_ty.isSignedInt())
             self.builder.buildNSWAdd(lhs, rhs, "")
         else
             self.builder.buildNUWAdd(lhs, rhs, "");
     }
 
-    fn genSub(self: *FuncGen, inst: *Inst.BinOp) !?*const llvm.Value {
-        const lhs = try self.resolveInst(inst.lhs);
-        const rhs = try self.resolveInst(inst.rhs);
+    fn airSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const lhs = try self.resolveInst(bin_op.lhs);
+        const rhs = try self.resolveInst(bin_op.rhs);
+        const inst_ty = self.air.typeOfIndex(inst);
 
-        if (!inst.base.ty.isInt())
-            return self.todo("implement 'genSub' for type {}", .{inst.base.ty});
+        if (!inst_ty.isInt())
+            return self.todo("implement 'airSub' for type {}", .{inst_ty});
 
-        return if (inst.base.ty.isSignedInt())
+        return if (inst_ty.isSignedInt())
             self.builder.buildNSWSub(lhs, rhs, "")
         else
             self.builder.buildNUWSub(lhs, rhs, "");
     }
 
-    fn genIntCast(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
-        const val = try self.resolveInst(inst.operand);
+    fn airIntCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+        const operand = try self.resolveInst(ty_op.operand);
+        const inst_ty = self.air.typeOfIndex(inst);
 
-        const signed = inst.base.ty.isSignedInt();
+        const signed = inst_ty.isSignedInt();
         // TODO: Should we use intcast here or just a simple bitcast?
         //       LLVM does truncation vs bitcast (+signed extension) in the intcast depending on the sizes
-        return self.builder.buildIntCast2(val, try self.dg.getLLVMType(inst.base.ty), llvm.Bool.fromBool(signed), "");
+        return self.builder.buildIntCast2(operand, try self.dg.getLLVMType(inst_ty), llvm.Bool.fromBool(signed), "");
     }
 
-    fn genBitCast(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
-        const val = try self.resolveInst(inst.operand);
-        const dest_type = try self.dg.getLLVMType(inst.base.ty);
+    fn airBitCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
+
+        const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+        const operand = try self.resolveInst(ty_op.operand);
+        const inst_ty = self.air.typeOfIndex(inst);
+        const dest_type = try self.dg.getLLVMType(inst_ty);
 
-        return self.builder.buildBitCast(val, dest_type, "");
+        return self.builder.buildBitCast(operand, dest_type, "");
     }
 
-    fn genArg(self: *FuncGen, inst: *Inst.Arg) !?*const llvm.Value {
+    fn airArg(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         const arg_val = self.args[self.arg_index];
         self.arg_index += 1;
 
-        const ptr_val = self.buildAlloca(try self.dg.getLLVMType(inst.base.ty));
+        const inst_ty = self.air.typeOfIndex(inst);
+        const ptr_val = self.buildAlloca(try self.dg.getLLVMType(inst_ty));
         _ = self.builder.buildStore(arg_val, ptr_val);
         return self.builder.buildLoad(ptr_val, "");
     }
 
-    fn genAlloc(self: *FuncGen, inst: *Inst.NoOp) !?*const llvm.Value {
+    fn airAlloc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        if (self.liveness.isUnused(inst))
+            return null;
         // buildAlloca expects the pointee type, not the pointer type, so assert that
         // a Payload.PointerSimple is passed to the alloc instruction.
-        const pointee_type = inst.base.ty.castPointer().?.data;
+        const inst_ty = self.air.typeOfIndex(inst);
+        const pointee_type = inst_ty.castPointer().?.data;
 
         // TODO: figure out a way to get the name of the var decl.
         // TODO: set alignment and volatile
@@ -1007,19 +1053,26 @@ pub const FuncGen = struct {
         return val;
     }
 
-    fn genStore(self: *FuncGen, inst: *Inst.BinOp) !?*const llvm.Value {
-        const val = try self.resolveInst(inst.rhs);
-        const ptr = try self.resolveInst(inst.lhs);
-        _ = self.builder.buildStore(val, ptr);
+    fn airStore(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+        const dest_ptr = try self.resolveInst(bin_op.lhs);
+        const src_operand = try self.resolveInst(bin_op.rhs);
+        // TODO set volatile on this store properly
+        _ = self.builder.buildStore(src_operand, dest_ptr);
         return null;
     }
 
-    fn genLoad(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
-        const ptr_val = try self.resolveInst(inst.operand);
-        return self.builder.buildLoad(ptr_val, "");
+    fn airLoad(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+        const is_volatile = self.air.typeOf(ty_op.operand).isVolatilePtr();
+        if (!is_volatile and self.liveness.isUnused(inst))
+            return null;
+        const ptr = try self.resolveInst(ty_op.operand);
+        // TODO set volatile on this load properly
+        return self.builder.buildLoad(ptr, "");
     }
 
-    fn genBreakpoint(self: *FuncGen, inst: *Inst.NoOp) !?*const llvm.Value {
+    fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         _ = inst;
         const llvn_fn = self.getIntrinsic("llvm.debugtrap");
         _ = self.builder.buildCall(llvn_fn, null, 0, "");
src/codegen/spirv.zig
@@ -13,6 +13,7 @@ const Type = @import("../type.zig").Type;
 const Value = @import("../value.zig").Value;
 const LazySrcLoc = Module.LazySrcLoc;
 const Air = @import("../Air.zig");
+const Liveness = @import("../Liveness.zig");
 
 pub const Word = u32;
 pub const ResultId = u32;
@@ -247,6 +248,7 @@ pub const DeclGen = struct {
         return .{
             .spv = spv,
             .air = undefined,
+            .liveness = undefined,
             .args = std.ArrayList(ResultId).init(spv.gpa),
             .next_arg_index = undefined,
             .inst_results = InstMap.init(spv.gpa),
@@ -259,11 +261,12 @@ pub const DeclGen = struct {
     }
 
     /// Generate the code for `decl`. If a reportable error occured during code generation,
-    /// a message is returned by this function. Callee owns the memory. If this function returns such
-    /// a reportable error, it is valid to be called again for a different decl.
-    pub fn gen(self: *DeclGen, decl: *Decl, air: Air) !?*Module.ErrorMsg {
+    /// a message is returned by this function. Callee owns the memory. If this function
+    /// returns such a reportable error, it is valid to be called again for a different decl.
+    pub fn gen(self: *DeclGen, decl: *Decl, air: Air, liveness: Liveness) !?*Module.ErrorMsg {
         // Reset internal resources, we don't want to re-allocate these.
-        self.air = &air;
+        self.air = air;
+        self.liveness = liveness;
         self.args.items.len = 0;
         self.next_arg_index = 0;
         self.inst_results.clearRetainingCapacity();
@@ -297,12 +300,12 @@ pub const DeclGen = struct {
         return error.AnalysisFail;
     }
 
-    fn resolve(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
-        if (inst.value()) |val| {
-            return self.genConstant(inst.ty, val);
+    fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !ResultId {
+        if (self.air.value(inst)) |val| {
+            return self.genConstant(self.air.typeOf(inst), val);
         }
-
-        return self.inst_results.get(inst).?; // Instruction does not dominate all uses!
+        const index = Air.refToIndex(inst).?;
+        return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.
     }
 
     fn beginSPIRVBlock(self: *DeclGen, label_id: ResultId) !void {
@@ -663,40 +666,40 @@ pub const DeclGen = struct {
         const air_tags = self.air.instructions.items(.tag);
         const result_id = switch (air_tags[inst]) {
             // zig fmt: off
-            .add, .addwrap => try self.genArithOp(inst, .{.OpFAdd, .OpIAdd, .OpIAdd}),
-            .sub, .subwrap => try self.genArithOp(inst, .{.OpFSub, .OpISub, .OpISub}),
-            .mul, .mulwrap => try self.genArithOp(inst, .{.OpFMul, .OpIMul, .OpIMul}),
-            .div           => try self.genArithOp(inst, .{.OpFDiv, .OpSDiv, .OpUDiv}),
-
-            .bit_and  => try self.genBinOpSimple(inst, .OpBitwiseAnd),
-            .bit_or   => try self.genBinOpSimple(inst, .OpBitwiseOr),
-            .xor      => try self.genBinOpSimple(inst, .OpBitwiseXor),
-            .bool_and => try self.genBinOpSimple(inst, .OpLogicalAnd),
-            .bool_or  => try self.genBinOpSimple(inst, .OpLogicalOr),
-
-            .not => try self.genNot(inst),
-
-            .cmp_eq  => try self.genCmp(inst, .{.OpFOrdEqual,            .OpLogicalEqual,      .OpIEqual}),
-            .cmp_neq => try self.genCmp(inst, .{.OpFOrdNotEqual,         .OpLogicalNotEqual,   .OpINotEqual}),
-            .cmp_gt  => try self.genCmp(inst, .{.OpFOrdGreaterThan,      .OpSGreaterThan,      .OpUGreaterThan}),
-            .cmp_gte => try self.genCmp(inst, .{.OpFOrdGreaterThanEqual, .OpSGreaterThanEqual, .OpUGreaterThanEqual}),
-            .cmp_lt  => try self.genCmp(inst, .{.OpFOrdLessThan,         .OpSLessThan,         .OpULessThan}),
-            .cmp_lte => try self.genCmp(inst, .{.OpFOrdLessThanEqual,    .OpSLessThanEqual,    .OpULessThanEqual}),
-
-            .arg   => self.genArg(),
-            .alloc => try self.genAlloc(inst),
-            .block => (try self.genBlock(inst)) orelse return,
-            .load  => try self.genLoad(inst),
-
-            .br         => return self.genBr(inst),
+            .add, .addwrap => try self.airArithOp(inst, .{.OpFAdd, .OpIAdd, .OpIAdd}),
+            .sub, .subwrap => try self.airArithOp(inst, .{.OpFSub, .OpISub, .OpISub}),
+            .mul, .mulwrap => try self.airArithOp(inst, .{.OpFMul, .OpIMul, .OpIMul}),
+            .div           => try self.airArithOp(inst, .{.OpFDiv, .OpSDiv, .OpUDiv}),
+
+            .bit_and  => try self.airBinOpSimple(inst, .OpBitwiseAnd),
+            .bit_or   => try self.airBinOpSimple(inst, .OpBitwiseOr),
+            .xor      => try self.airBinOpSimple(inst, .OpBitwiseXor),
+            .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd),
+            .bool_or  => try self.airBinOpSimple(inst, .OpLogicalOr),
+
+            .not => try self.airNot(inst),
+
+            .cmp_eq  => try self.airCmp(inst, .{.OpFOrdEqual,            .OpLogicalEqual,      .OpIEqual}),
+            .cmp_neq => try self.airCmp(inst, .{.OpFOrdNotEqual,         .OpLogicalNotEqual,   .OpINotEqual}),
+            .cmp_gt  => try self.airCmp(inst, .{.OpFOrdGreaterThan,      .OpSGreaterThan,      .OpUGreaterThan}),
+            .cmp_gte => try self.airCmp(inst, .{.OpFOrdGreaterThanEqual, .OpSGreaterThanEqual, .OpUGreaterThanEqual}),
+            .cmp_lt  => try self.airCmp(inst, .{.OpFOrdLessThan,         .OpSLessThan,         .OpULessThan}),
+            .cmp_lte => try self.airCmp(inst, .{.OpFOrdLessThanEqual,    .OpSLessThanEqual,    .OpULessThanEqual}),
+
+            .arg   => self.airArg(),
+            .alloc => try self.airAlloc(inst),
+            .block => (try self.airBlock(inst)) orelse return,
+            .load  => try self.airLoad(inst),
+
+            .br         => return self.airBr(inst),
             .breakpoint => return,
-            .cond_br    => return self.genCondBr(inst),
+            .cond_br    => return self.airCondBr(inst),
             .constant   => unreachable,
-            .dbg_stmt   => return self.genDbgStmt(inst),
-            .loop       => return self.genLoop(inst),
-            .ret        => return self.genRet(inst),
-            .store      => return self.genStore(inst),
-            .unreach    => return self.genUnreach(),
+            .dbg_stmt   => return self.airDbgStmt(inst),
+            .loop       => return self.airLoop(inst),
+            .ret        => return self.airRet(inst),
+            .store      => return self.airStore(inst),
+            .unreach    => return self.airUnreach(),
             // zig fmt: on
 
             else => |tag| return self.fail("TODO: SPIR-V backend: implement AIR tag {s}", .{
@@ -707,21 +710,22 @@ pub const DeclGen = struct {
         try self.inst_results.putNoClobber(inst, result_id);
     }
 
-    fn genBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, opcode: Opcode) !ResultId {
+    fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, opcode: Opcode) !ResultId {
         const bin_op = self.air.instructions.items(.data)[inst].bin_op;
         const lhs_id = try self.resolve(bin_op.lhs);
         const rhs_id = try self.resolve(bin_op.rhs);
         const result_id = self.spv.allocResultId();
+        const result_type_id = try self.genType(self.air.typeOfIndex(inst));
         try writeInstruction(&self.code, opcode, &[_]Word{
             result_type_id, result_id, lhs_id, rhs_id,
         });
         return result_id;
     }
 
-    fn genArithOp(self: *DeclGen, inst: Air.Inst.Index, ops: [3]Opcode) !ResultId {
+    fn airArithOp(self: *DeclGen, inst: Air.Inst.Index, ops: [3]Opcode) !ResultId {
         // LHS and RHS are guaranteed to have the same type, and AIR guarantees
         // the result to be the same as the LHS and RHS, which matches SPIR-V.
-        const ty = self.air.getType(inst);
+        const ty = self.air.typeOfIndex(inst);
         const bin_op = self.air.instructions.items(.data)[inst].bin_op;
         const lhs_id = try self.resolve(bin_op.lhs);
         const rhs_id = try self.resolve(bin_op.rhs);
@@ -729,8 +733,8 @@ pub const DeclGen = struct {
         const result_id = self.spv.allocResultId();
         const result_type_id = try self.genType(ty);
 
-        assert(self.air.getType(bin_op.lhs).eql(ty));
-        assert(self.air.getType(bin_op.rhs).eql(ty));
+        assert(self.air.typeOf(bin_op.lhs).eql(ty));
+        assert(self.air.typeOf(bin_op.rhs).eql(ty));
 
         // Binary operations are generally applicable to both scalar and vector operations
         // in SPIR-V, but int and float versions of operations require different opcodes.
@@ -744,8 +748,8 @@ pub const DeclGen = struct {
                 return self.fail("TODO: SPIR-V backend: binary operations for strange integers", .{});
             },
             .integer => switch (info.signedness) {
-                .signed => 1,
-                .unsigned => 2,
+                .signed => @as(usize, 1),
+                .unsigned => @as(usize, 2),
             },
             .float => 0,
             else => unreachable,
@@ -759,14 +763,14 @@ pub const DeclGen = struct {
         return result_id;
     }
 
-    fn genCmp(self: *DeclGen, inst: Air.Inst.Index, ops: [3]Opcode) !ResultId {
+    fn airCmp(self: *DeclGen, inst: Air.Inst.Index, ops: [3]Opcode) !ResultId {
         const bin_op = self.air.instructions.items(.data)[inst].bin_op;
         const lhs_id = try self.resolve(bin_op.lhs);
         const rhs_id = try self.resolve(bin_op.rhs);
         const result_id = self.spv.allocResultId();
         const result_type_id = try self.genType(Type.initTag(.bool));
-        const op_ty = self.air.getType(bin_op.lhs);
-        assert(op_ty.eql(self.air.getType(bin_op.rhs)));
+        const op_ty = self.air.typeOf(bin_op.lhs);
+        assert(op_ty.eql(self.air.typeOf(bin_op.rhs)));
 
         // Comparisons are generally applicable to both scalar and vector operations in SPIR-V,
         // but int and float versions of operations require different opcodes.
@@ -782,10 +786,9 @@ pub const DeclGen = struct {
             .float => 0,
             .bool => 1,
             .integer => switch (info.signedness) {
-                .signed => 1,
-                .unsigned => 2,
+                .signed => @as(usize, 1),
+                .unsigned => @as(usize, 2),
             },
-            else => unreachable,
         };
         const opcode = ops[opcode_index];
 
@@ -793,7 +796,7 @@ pub const DeclGen = struct {
         return result_id;
     }
 
-    fn genNot(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
+    fn airNot(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
         const ty_op = self.air.instructions.items(.data)[inst].ty_op;
         const operand_id = try self.resolve(ty_op.operand);
         const result_id = self.spv.allocResultId();
@@ -803,8 +806,8 @@ pub const DeclGen = struct {
         return result_id;
     }
 
-    fn genAlloc(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
-        const ty = self.air.getType(inst);
+    fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
+        const ty = self.air.typeOfIndex(inst);
         const storage_class = spec.StorageClass.Function;
         const result_type_id = try self.genPointerType(ty, storage_class);
         const result_id = self.spv.allocResultId();
@@ -816,12 +819,12 @@ pub const DeclGen = struct {
         return result_id;
     }
 
-    fn genArg(self: *DeclGen) ResultId {
+    fn airArg(self: *DeclGen) ResultId {
         defer self.next_arg_index += 1;
         return self.args.items[self.next_arg_index];
     }
 
-    fn genBlock(self: *DeclGen, inst: Air.Inst.Index) !?ResultId {
+    fn airBlock(self: *DeclGen, inst: Air.Inst.Index) !?ResultId {
         // In IR, a block doesn't really define an entry point like a block, but more like a scope that breaks can jump out of and
         // "return" a value from. This cannot be directly modelled in SPIR-V, so in a block instruction, we're going to split up
         // the current block by first generating the code of the block, then a label, and then generate the rest of the current
@@ -841,7 +844,7 @@ pub const DeclGen = struct {
             incoming_blocks.deinit(self.spv.gpa);
         }
 
-        const ty = self.air.getType(inst);
+        const ty = self.air.typeOfIndex(inst);
         const inst_datas = self.air.instructions.items(.data);
         const extra = self.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
         const body = self.air.extra[extra.end..][0..extra.data.body_len];
@@ -872,10 +875,10 @@ pub const DeclGen = struct {
         return result_id;
     }
 
-    fn genBr(self: *DeclGen, inst: Air.Inst.Index) !void {
+    fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void {
         const br = self.air.instructions.items(.data)[inst].br;
         const block = self.blocks.get(br.block_inst).?;
-        const operand_ty = self.air.getType(br.operand);
+        const operand_ty = self.air.typeOf(br.operand);
 
         if (operand_ty.hasCodeGenBits()) {
             const operand_id = try self.resolve(br.operand);
@@ -886,7 +889,7 @@ pub const DeclGen = struct {
         try writeInstruction(&self.code, .OpBranch, &[_]Word{block.label_id});
     }
 
-    fn genCondBr(self: *DeclGen, inst: *Inst.CondBr) !void {
+    fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {
         const pl_op = self.air.instructions.items(.data)[inst].pl_op;
         const cond_br = self.air.extraData(Air.CondBr, pl_op.payload);
         const then_body = self.air.extra[cond_br.end..][0..cond_br.data.then_body_len];
@@ -912,16 +915,16 @@ pub const DeclGen = struct {
         try self.genBody(else_body);
     }
 
-    fn genDbgStmt(self: *DeclGen, inst: Air.Inst.Index) !void {
+    fn airDbgStmt(self: *DeclGen, inst: Air.Inst.Index) !void {
         const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;
         const src_fname_id = try self.spv.resolveSourceFileName(self.decl);
         try writeInstruction(&self.code, .OpLine, &[_]Word{ src_fname_id, dbg_stmt.line, dbg_stmt.column });
     }
 
-    fn genLoad(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
+    fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
         const ty_op = self.air.instructions.items(.data)[inst].ty_op;
         const operand_id = try self.resolve(ty_op.operand);
-        const ty = self.air.getType(inst);
+        const ty = self.air.typeOfIndex(inst);
 
         const result_type_id = try self.genType(ty);
         const result_id = self.spv.allocResultId();
@@ -936,8 +939,9 @@ pub const DeclGen = struct {
         return result_id;
     }
 
-    fn genLoop(self: *DeclGen, inst: Air.Inst.Index) !void {
-        const loop = self.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
+    fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void {
+        const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+        const loop = self.air.extraData(Air.Block, ty_pl.payload);
         const body = self.air.extra[loop.end..][0..loop.data.body_len];
         const loop_label_id = self.spv.allocResultId();
 
@@ -952,9 +956,9 @@ pub const DeclGen = struct {
         try writeInstruction(&self.code, .OpBranch, &[_]Word{loop_label_id});
     }
 
-    fn genRet(self: *DeclGen, inst: Air.Inst.Index) !void {
-        const operand = inst_datas[inst].un_op;
-        const operand_ty = self.air.getType(operand);
+    fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {
+        const operand = self.air.instructions.items(.data)[inst].un_op;
+        const operand_ty = self.air.typeOf(operand);
         if (operand_ty.hasCodeGenBits()) {
             const operand_id = try self.resolve(operand);
             try writeInstruction(&self.code, .OpReturnValue, &[_]Word{operand_id});
@@ -963,11 +967,11 @@ pub const DeclGen = struct {
         }
     }
 
-    fn genStore(self: *DeclGen, inst: Air.Inst.Index) !void {
+    fn airStore(self: *DeclGen, inst: Air.Inst.Index) !void {
         const bin_op = self.air.instructions.items(.data)[inst].bin_op;
         const dst_ptr_id = try self.resolve(bin_op.lhs);
         const src_val_id = try self.resolve(bin_op.rhs);
-        const lhs_ty = self.air.getType(bin_op.lhs);
+        const lhs_ty = self.air.typeOf(bin_op.lhs);
 
         const operands = if (lhs_ty.isVolatilePtr())
             &[_]Word{ dst_ptr_id, src_val_id, @bitCast(u32, spec.MemoryAccess{ .Volatile = true }) }
@@ -977,7 +981,7 @@ pub const DeclGen = struct {
         try writeInstruction(&self.code, .OpStore, operands);
     }
 
-    fn genUnreach(self: *DeclGen) !void {
+    fn airUnreach(self: *DeclGen) !void {
         try writeInstruction(&self.code, .OpUnreachable, &[_]Word{});
     }
 };
src/codegen/wasm.zig
@@ -774,7 +774,7 @@ pub const Context = struct {
                         }
                     }
                     return Result{ .externally_managed = payload.data };
-                } else return self.fail(.{ .node_offset = 0 }, "TODO implement gen for more kinds of arrays", .{});
+                } else return self.fail("TODO implement gen for more kinds of arrays", .{});
             },
             .Int => {
                 const info = typed_value.ty.intInfo(self.target);
@@ -783,9 +783,9 @@ pub const Context = struct {
                     try self.code.append(@intCast(u8, int_byte));
                     return Result.appended;
                 }
-                return self.fail(.{ .node_offset = 0 }, "TODO: Implement codegen for int type: '{}'", .{typed_value.ty});
+                return self.fail("TODO: Implement codegen for int type: '{}'", .{typed_value.ty});
             },
-            else => |tag| return self.fail(.{ .node_offset = 0 }, "TODO: Implement zig type codegen for type: '{s}'", .{tag}),
+            else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
         }
     }
 
@@ -883,7 +883,7 @@ pub const Context = struct {
     }
 
     fn genAlloc(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
-        const elem_type = self.air.getType(inst).elemType();
+        const elem_type = self.air.typeOfIndex(inst).elemType();
         return self.allocLocal(elem_type);
     }
 
src/link/Coff.zig
@@ -657,11 +657,16 @@ fn writeOffsetTableEntry(self: *Coff, index: usize) !void {
 }
 
 pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
-    if (build_options.skip_non_native and builtin.object_format != .coff and builtin.object_format != .pe) {
+    if (build_options.skip_non_native and
+        builtin.object_format != .coff and
+        builtin.object_format != .pe)
+    {
         @panic("Attempted to compile for object format that was disabled by build configuration");
     }
     if (build_options.have_llvm) {
-        if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(module, func, air, liveness);
+        if (self.llvm_object) |llvm_object| {
+            return llvm_object.updateFunc(module, func, air, liveness);
+        }
     }
     const tracy = trace(@src());
     defer tracy.end();
@@ -669,6 +674,7 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live
     var code_buffer = std.ArrayList(u8).init(self.base.allocator);
     defer code_buffer.deinit();
 
+    const decl = func.owner_decl;
     const res = try codegen.generateFunction(
         &self.base,
         decl.srcLoc(),
@@ -679,7 +685,6 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live
         .none,
     );
     const code = switch (res) {
-        .externally_managed => |x| x,
         .appended => code_buffer.items,
         .fail => |em| {
             decl.analysis = .codegen_failure;
@@ -725,10 +730,10 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void {
         },
     };
 
-    return self.finishUpdateDecl(module, func.owner_decl, code);
+    return self.finishUpdateDecl(module, decl, code);
 }
 
-fn finishUpdateDecl(self: *Coff, decl: *Module.Decl, code: []const u8) !void {
+fn finishUpdateDecl(self: *Coff, module: *Module, decl: *Module.Decl, code: []const u8) !void {
     const required_alignment = decl.ty.abiAlignment(self.base.options.target);
     const curr_size = decl.link.coff.size;
     if (curr_size != 0) {
src/link/MachO.zig
@@ -1150,9 +1150,13 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv
     var code_buffer = std.ArrayList(u8).init(self.base.allocator);
     defer code_buffer.deinit();
 
-    var debug_buffers = if (self.d_sym) |*ds| try ds.initDeclDebugBuffers(self.base.allocator, module, decl) else null;
+    var debug_buffers_buf: DebugSymbols.DeclDebugBuffers = undefined;
+    const debug_buffers = if (self.d_sym) |*ds| blk: {
+        debug_buffers_buf = try ds.initDeclDebugBuffers(self.base.allocator, module, decl);
+        break :blk &debug_buffers_buf;
+    } else null;
     defer {
-        if (debug_buffers) |*dbg| {
+        if (debug_buffers) |dbg| {
             dbg.dbg_line_buffer.deinit();
             dbg.dbg_info_buffer.deinit();
             var it = dbg.dbg_info_type_relocs.valueIterator();
@@ -1163,7 +1167,7 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv
         }
     }
 
-    const res = if (debug_buffers) |*dbg|
+    const res = if (debug_buffers) |dbg|
         try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .{
             .dwarf = .{
                 .dbg_line = &dbg.dbg_line_buffer,
@@ -1172,9 +1176,109 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv
             },
         })
     else
-        try codegen.generateSymbol(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .none);
+        try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .none);
+    switch (res) {
+        .appended => {},
+        .fail => |em| {
+            // Clear any PIE fixups for this decl.
+            self.pie_fixups.shrinkRetainingCapacity(0);
+            // Clear any stub fixups for this decl.
+            self.stub_fixups.shrinkRetainingCapacity(0);
+            decl.analysis = .codegen_failure;
+            try module.failed_decls.put(module.gpa, decl, em);
+            return;
+        },
+    }
+    const symbol = try self.placeDecl(decl, code_buffer.items.len);
+
+    // Calculate displacements to target addr (if any).
+    while (self.pie_fixups.popOrNull()) |fixup| {
+        assert(fixup.size == 4);
+        const this_addr = symbol.n_value + fixup.offset;
+        const target_addr = fixup.target_addr;
+
+        switch (self.base.options.target.cpu.arch) {
+            .x86_64 => {
+                const displacement = try math.cast(u32, target_addr - this_addr - 4);
+                mem.writeIntLittle(u32, code_buffer.items[fixup.offset..][0..4], displacement);
+            },
+            .aarch64 => {
+                // TODO optimize instruction based on jump length (use ldr(literal) + nop if possible).
+                {
+                    const inst = code_buffer.items[fixup.offset..][0..4];
+                    const parsed = mem.bytesAsValue(meta.TagPayload(
+                        aarch64.Instruction,
+                        aarch64.Instruction.pc_relative_address,
+                    ), inst);
+                    const this_page = @intCast(i32, this_addr >> 12);
+                    const target_page = @intCast(i32, target_addr >> 12);
+                    const pages = @bitCast(u21, @intCast(i21, target_page - this_page));
+                    parsed.immhi = @truncate(u19, pages >> 2);
+                    parsed.immlo = @truncate(u2, pages);
+                }
+                {
+                    const inst = code_buffer.items[fixup.offset + 4 ..][0..4];
+                    const parsed = mem.bytesAsValue(meta.TagPayload(
+                        aarch64.Instruction,
+                        aarch64.Instruction.load_store_register,
+                    ), inst);
+                    const narrowed = @truncate(u12, target_addr);
+                    const offset = try math.divExact(u12, narrowed, 8);
+                    parsed.offset = offset;
+                }
+            },
+            else => unreachable, // unsupported target architecture
+        }
+    }
+
+    // Resolve stubs (if any)
+    const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
+    const stubs = text_segment.sections.items[self.stubs_section_index.?];
+    for (self.stub_fixups.items) |fixup| {
+        const stub_addr = stubs.addr + fixup.symbol * stubs.reserved2;
+        const text_addr = symbol.n_value + fixup.start;
+        switch (self.base.options.target.cpu.arch) {
+            .x86_64 => {
+                assert(stub_addr >= text_addr + fixup.len);
+                const displacement = try math.cast(u32, stub_addr - text_addr - fixup.len);
+                const placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)];
+                mem.writeIntSliceLittle(u32, placeholder, displacement);
+            },
+            .aarch64 => {
+                assert(stub_addr >= text_addr);
+                const displacement = try math.cast(i28, stub_addr - text_addr);
+                const placeholder = code_buffer.items[fixup.start..][0..fixup.len];
+                mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(displacement).toU32());
+            },
+            else => unreachable, // unsupported target architecture
+        }
+        if (!fixup.already_defined) {
+            try self.writeStub(fixup.symbol);
+            try self.writeStubInStubHelper(fixup.symbol);
+            try self.writeLazySymbolPointer(fixup.symbol);
+
+            self.rebase_info_dirty = true;
+            self.lazy_binding_info_dirty = true;
+        }
+    }
+    self.stub_fixups.shrinkRetainingCapacity(0);
+
+    try self.writeCode(symbol, code_buffer.items);
 
-    return self.finishUpdateDecl(module, decl, res);
+    if (debug_buffers) |db| {
+        try self.d_sym.?.commitDeclDebugInfo(
+            self.base.allocator,
+            module,
+            decl,
+            db,
+            self.base.options.target,
+        );
+    }
+
+    // Since we updated the vaddr and the size, each corresponding export symbol also
+    // needs to be updated.
+    const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
+    try self.updateDeclExports(module, decl, decl_exports);
 }
 
 pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
@@ -1194,9 +1298,13 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
     var code_buffer = std.ArrayList(u8).init(self.base.allocator);
     defer code_buffer.deinit();
 
-    var debug_buffers = if (self.d_sym) |*ds| try ds.initDeclDebugBuffers(self.base.allocator, module, decl) else null;
+    var debug_buffers_buf: DebugSymbols.DeclDebugBuffers = undefined;
+    const debug_buffers = if (self.d_sym) |*ds| blk: {
+        debug_buffers_buf = try ds.initDeclDebugBuffers(self.base.allocator, module, decl);
+        break :blk &debug_buffers_buf;
+    } else null;
     defer {
-        if (debug_buffers) |*dbg| {
+        if (debug_buffers) |dbg| {
             dbg.dbg_line_buffer.deinit();
             dbg.dbg_info_buffer.deinit();
             var it = dbg.dbg_info_type_relocs.valueIterator();
@@ -1207,7 +1315,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
         }
     }
 
-    const res = if (debug_buffers) |*dbg|
+    const res = if (debug_buffers) |dbg|
         try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
             .ty = decl.ty,
             .val = decl.val,
@@ -1224,33 +1332,37 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
             .val = decl.val,
         }, &code_buffer, .none);
 
-    return self.finishUpdateDecl(module, decl, res);
-}
-
-fn finishUpdateDecl(self: *MachO, module: *Module, decl: *Module.Decl, res: codegen.Result) !void {
     const code = switch (res) {
         .externally_managed => |x| x,
         .appended => code_buffer.items,
         .fail => |em| {
-            // Clear any PIE fixups for this decl.
-            self.pie_fixups.shrinkRetainingCapacity(0);
-            // Clear any stub fixups for this decl.
-            self.stub_fixups.shrinkRetainingCapacity(0);
             decl.analysis = .codegen_failure;
             try module.failed_decls.put(module.gpa, decl, em);
             return;
         },
     };
+    const symbol = try self.placeDecl(decl, code.len);
+    assert(self.pie_fixups.items.len == 0);
+    assert(self.stub_fixups.items.len == 0);
 
+    try self.writeCode(symbol, code);
+
+    // Since we updated the vaddr and the size, each corresponding export symbol also
+    // needs to be updated.
+    const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
+    try self.updateDeclExports(module, decl, decl_exports);
+}
+
+fn placeDecl(self: *MachO, decl: *Module.Decl, code_len: usize) !*macho.nlist_64 {
     const required_alignment = decl.ty.abiAlignment(self.base.options.target);
     assert(decl.link.macho.local_sym_index != 0); // Caller forgot to call allocateDeclIndexes()
     const symbol = &self.locals.items[decl.link.macho.local_sym_index];
 
     if (decl.link.macho.size != 0) {
         const capacity = decl.link.macho.capacity(self.*);
-        const need_realloc = code.len > capacity or !mem.isAlignedGeneric(u64, symbol.n_value, required_alignment);
+        const need_realloc = code_len > capacity or !mem.isAlignedGeneric(u64, symbol.n_value, required_alignment);
         if (need_realloc) {
-            const vaddr = try self.growTextBlock(&decl.link.macho, code.len, required_alignment);
+            const vaddr = try self.growTextBlock(&decl.link.macho, code_len, required_alignment);
 
             log.debug("growing {s} and moving from 0x{x} to 0x{x}", .{ decl.name, symbol.n_value, vaddr });
 
@@ -1265,10 +1377,10 @@ fn finishUpdateDecl(self: *MachO, module: *Module, decl: *Module.Decl, res: code
             }
 
             symbol.n_value = vaddr;
-        } else if (code.len < decl.link.macho.size) {
-            self.shrinkTextBlock(&decl.link.macho, code.len);
+        } else if (code_len < decl.link.macho.size) {
+            self.shrinkTextBlock(&decl.link.macho, code_len);
         }
-        decl.link.macho.size = code.len;
+        decl.link.macho.size = code_len;
 
         const new_name = try std.fmt.allocPrint(self.base.allocator, "_{s}", .{mem.spanZ(decl.name)});
         defer self.base.allocator.free(new_name);
@@ -1286,7 +1398,7 @@ fn finishUpdateDecl(self: *MachO, module: *Module, decl: *Module.Decl, res: code
         defer self.base.allocator.free(decl_name);
 
         const name_str_index = try self.makeString(decl_name);
-        const addr = try self.allocateTextBlock(&decl.link.macho, code.len, required_alignment);
+        const addr = try self.allocateTextBlock(&decl.link.macho, code_len, required_alignment);
 
         log.debug("allocated text block for {s} at 0x{x}", .{ decl_name, addr });
 
@@ -1311,96 +1423,15 @@ fn finishUpdateDecl(self: *MachO, module: *Module, decl: *Module.Decl, res: code
         try self.writeOffsetTableEntry(decl.link.macho.offset_table_index);
     }
 
-    // Calculate displacements to target addr (if any).
-    while (self.pie_fixups.popOrNull()) |fixup| {
-        assert(fixup.size == 4);
-        const this_addr = symbol.n_value + fixup.offset;
-        const target_addr = fixup.target_addr;
-
-        switch (self.base.options.target.cpu.arch) {
-            .x86_64 => {
-                const displacement = try math.cast(u32, target_addr - this_addr - 4);
-                mem.writeIntLittle(u32, code_buffer.items[fixup.offset..][0..4], displacement);
-            },
-            .aarch64 => {
-                // TODO optimize instruction based on jump length (use ldr(literal) + nop if possible).
-                {
-                    const inst = code_buffer.items[fixup.offset..][0..4];
-                    var parsed = mem.bytesAsValue(meta.TagPayload(
-                        aarch64.Instruction,
-                        aarch64.Instruction.pc_relative_address,
-                    ), inst);
-                    const this_page = @intCast(i32, this_addr >> 12);
-                    const target_page = @intCast(i32, target_addr >> 12);
-                    const pages = @bitCast(u21, @intCast(i21, target_page - this_page));
-                    parsed.immhi = @truncate(u19, pages >> 2);
-                    parsed.immlo = @truncate(u2, pages);
-                }
-                {
-                    const inst = code_buffer.items[fixup.offset + 4 ..][0..4];
-                    var parsed = mem.bytesAsValue(meta.TagPayload(
-                        aarch64.Instruction,
-                        aarch64.Instruction.load_store_register,
-                    ), inst);
-                    const narrowed = @truncate(u12, target_addr);
-                    const offset = try math.divExact(u12, narrowed, 8);
-                    parsed.offset = offset;
-                }
-            },
-            else => unreachable, // unsupported target architecture
-        }
-    }
-
-    // Resolve stubs (if any)
-    const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
-    const stubs = text_segment.sections.items[self.stubs_section_index.?];
-    for (self.stub_fixups.items) |fixup| {
-        const stub_addr = stubs.addr + fixup.symbol * stubs.reserved2;
-        const text_addr = symbol.n_value + fixup.start;
-        switch (self.base.options.target.cpu.arch) {
-            .x86_64 => {
-                assert(stub_addr >= text_addr + fixup.len);
-                const displacement = try math.cast(u32, stub_addr - text_addr - fixup.len);
-                var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)];
-                mem.writeIntSliceLittle(u32, placeholder, displacement);
-            },
-            .aarch64 => {
-                assert(stub_addr >= text_addr);
-                const displacement = try math.cast(i28, stub_addr - text_addr);
-                var placeholder = code_buffer.items[fixup.start..][0..fixup.len];
-                mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(displacement).toU32());
-            },
-            else => unreachable, // unsupported target architecture
-        }
-        if (!fixup.already_defined) {
-            try self.writeStub(fixup.symbol);
-            try self.writeStubInStubHelper(fixup.symbol);
-            try self.writeLazySymbolPointer(fixup.symbol);
-
-            self.rebase_info_dirty = true;
-            self.lazy_binding_info_dirty = true;
-        }
-    }
-    self.stub_fixups.shrinkRetainingCapacity(0);
+    return symbol;
+}
 
+fn writeCode(self: *MachO, symbol: *macho.nlist_64, code: []const u8) !void {
+    const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
     const text_section = text_segment.sections.items[self.text_section_index.?];
     const section_offset = symbol.n_value - text_section.addr;
     const file_offset = text_section.offset + section_offset;
     try self.base.file.?.pwriteAll(code, file_offset);
-
-    if (debug_buffers) |*db| {
-        try self.d_sym.?.commitDeclDebugInfo(
-            self.base.allocator,
-            module,
-            decl,
-            db,
-            self.base.options.target,
-        );
-    }
-
-    // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
-    const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
-    try self.updateDeclExports(module, decl, decl_exports);
 }
 
 pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {
src/link/SpirV.zig
@@ -51,7 +51,12 @@ base: link.File,
 /// This linker backend does not try to incrementally link output SPIR-V code.
 /// Instead, it tracks all declarations in this table, and iterates over it
 /// in the flush function.
-decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, void) = .{},
+decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, DeclGenContext) = .{},
+
+const DeclGenContext = struct {
+    air: Air,
+    liveness: Liveness,
+};
 
 pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
     const spirv = try gpa.create(SpirV);
@@ -181,10 +186,15 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
         var decl_gen = codegen.DeclGen.init(&spv);
         defer decl_gen.deinit();
 
-        for (self.decl_table.keys()) |decl| {
+        var it = self.decl_table.iterator();
+        while (it.next()) |entry| {
+            const decl = entry.key_ptr.*;
             if (!decl.has_tv) continue;
 
-            if (try decl_gen.gen(decl)) |msg| {
+            const air = entry.value_ptr.air;
+            const liveness = entry.value_ptr.liveness;
+
+            if (try decl_gen.gen(decl, air, liveness)) |msg| {
                 try module.failed_decls.put(module.gpa, decl, msg);
                 return; // TODO: Attempt to generate more decls?
             }
src/link/Wasm.zig
@@ -250,6 +250,8 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
 
     var context = codegen.Context{
         .gpa = self.base.allocator,
+        .air = undefined,
+        .liveness = undefined,
         .values = .{},
         .code = fn_data.code.toManaged(self.base.allocator),
         .func_type_data = fn_data.functype.toManaged(self.base.allocator),
src/codegen.zig
@@ -642,7 +642,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
                         try self.dbgSetPrologueEnd();
 
-                        try self.genBody(self.mod_fn.body);
+                        try self.genBody(self.air.getMainBody());
 
                         // Backpatch push callee saved regs
                         var saved_regs = Instruction.RegisterList{
@@ -703,7 +703,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         writeInt(u32, try self.code.addManyAsArray(4), Instruction.ldm(.al, .sp, true, saved_regs).toU32());
                     } else {
                         try self.dbgSetPrologueEnd();
-                        try self.genBody(self.mod_fn.body);
+                        try self.genBody(self.air.getMainBody());
                         try self.dbgSetEpilogueBegin();
                     }
                 },
@@ -727,7 +727,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
                         try self.dbgSetPrologueEnd();
 
-                        try self.genBody(self.mod_fn.body);
+                        try self.genBody(self.air.getMainBody());
 
                         // Backpatch stack offset
                         const stack_end = self.max_end_stack;
@@ -779,13 +779,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         writeInt(u32, try self.code.addManyAsArray(4), Instruction.ret(null).toU32());
                     } else {
                         try self.dbgSetPrologueEnd();
-                        try self.genBody(self.mod_fn.body);
+                        try self.genBody(self.air.getMainBody());
                         try self.dbgSetEpilogueBegin();
                     }
                 },
                 else => {
                     try self.dbgSetPrologueEnd();
-                    try self.genBody(self.mod_fn.body);
+                    try self.genBody(self.air.getMainBody());
                     try self.dbgSetEpilogueBegin();
                 },
             }
@@ -1492,7 +1492,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             };
         }
 
-        fn genArmBinOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: Air.Inst.Ref, op: ir.Inst.Tag) !MCValue {
+        fn genArmBinOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: Air.Inst.Ref, op: Air.Inst.Tag) !MCValue {
             const lhs = try self.resolveInst(op_lhs);
             const rhs = try self.resolveInst(op_rhs);
 
@@ -1514,14 +1514,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             if (reuse_lhs) {
                 // Allocate 0 or 1 registers
                 if (!rhs_is_register and rhs_should_be_register) {
-                    rhs_mcv = MCValue{ .register = try self.register_manager.allocReg(op_rhs, &.{lhs.register}) };
+                    rhs_mcv = MCValue{ .register = try self.register_manager.allocReg(Air.refToIndex(op_rhs).?, &.{lhs.register}) };
                     branch.inst_table.putAssumeCapacity(Air.refToIndex(op_rhs).?, rhs_mcv);
                 }
                 dst_mcv = lhs;
             } else if (reuse_rhs) {
                 // Allocate 0 or 1 registers
                 if (!lhs_is_register and lhs_should_be_register) {
-                    lhs_mcv = MCValue{ .register = try self.register_manager.allocReg(op_lhs, &.{rhs.register}) };
+                    lhs_mcv = MCValue{ .register = try self.register_manager.allocReg(Air.refToIndex(op_lhs).?, &.{rhs.register}) };
                     branch.inst_table.putAssumeCapacity(Air.refToIndex(op_lhs).?, lhs_mcv);
                 }
                 dst_mcv = rhs;
@@ -1542,7 +1542,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         lhs_mcv = dst_mcv;
                     } else {
                         // Move LHS and RHS to register
-                        const regs = try self.register_manager.allocRegs(2, .{ inst, op_rhs }, &.{});
+                        const regs = try self.register_manager.allocRegs(2, .{ inst, Air.refToIndex(op_rhs).? }, &.{});
                         lhs_mcv = MCValue{ .register = regs[0] };
                         rhs_mcv = MCValue{ .register = regs[1] };
                         dst_mcv = lhs_mcv;
@@ -1572,10 +1572,10 @@ 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.ty, lhs_mcv.register, lhs);
+                try self.genSetReg(self.air.typeOf(op_lhs), lhs_mcv.register, lhs);
             }
             if (rhs_mcv == .register and !rhs_is_register) {
-                try self.genSetReg(op_rhs.ty, rhs_mcv.register, rhs);
+                try self.genSetReg(self.air.typeOf(op_rhs), rhs_mcv.register, rhs);
             }
 
             try self.genArmBinOpCode(
@@ -1594,7 +1594,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             lhs_mcv: MCValue,
             rhs_mcv: MCValue,
             swap_lhs_and_rhs: bool,
-            op: ir.Inst.Tag,
+            op: Air.Inst.Tag,
         ) !void {
             assert(lhs_mcv == .register or rhs_mcv == .register);
 
@@ -1665,14 +1665,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             if (reuse_lhs) {
                 // Allocate 0 or 1 registers
                 if (!rhs_is_register) {
-                    rhs_mcv = MCValue{ .register = try self.register_manager.allocReg(op_rhs, &.{lhs.register}) };
+                    rhs_mcv = MCValue{ .register = try self.register_manager.allocReg(Air.refToIndex(op_rhs).?, &.{lhs.register}) };
                     branch.inst_table.putAssumeCapacity(Air.refToIndex(op_rhs).?, rhs_mcv);
                 }
                 dst_mcv = lhs;
             } else if (reuse_rhs) {
                 // Allocate 0 or 1 registers
                 if (!lhs_is_register) {
-                    lhs_mcv = MCValue{ .register = try self.register_manager.allocReg(op_lhs, &.{rhs.register}) };
+                    lhs_mcv = MCValue{ .register = try self.register_manager.allocReg(Air.refToIndex(op_lhs).?, &.{rhs.register}) };
                     branch.inst_table.putAssumeCapacity(Air.refToIndex(op_lhs).?, lhs_mcv);
                 }
                 dst_mcv = rhs;
@@ -1690,7 +1690,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     lhs_mcv = dst_mcv;
                 } else {
                     // Move LHS and RHS to register
-                    const regs = try self.register_manager.allocRegs(2, .{ inst, op_rhs }, &.{});
+                    const regs = try self.register_manager.allocRegs(2, .{ inst, Air.refToIndex(op_rhs).? }, &.{});
                     lhs_mcv = MCValue{ .register = regs[0] };
                     rhs_mcv = MCValue{ .register = regs[1] };
                     dst_mcv = lhs_mcv;
@@ -1701,10 +1701,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.ty, lhs_mcv.register, lhs);
+                try self.genSetReg(self.air.typeOf(op_lhs), lhs_mcv.register, lhs);
             }
             if (!rhs_is_register) {
-                try self.genSetReg(op_rhs.ty, rhs_mcv.register, rhs);
+                try self.genSetReg(self.air.typeOf(op_rhs), rhs_mcv.register, rhs);
             }
 
             writeInt(u32, try self.code.addManyAsArray(4), Instruction.mul(.al, dst_mcv.register, lhs_mcv.register, rhs_mcv.register).toU32());
@@ -2704,9 +2704,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     },
                     .aarch64 => {
                         for (info.args) |mc_arg, arg_i| {
-                            const arg = inst.args[arg_i];
+                            const arg = args[arg_i];
                             const arg_ty = self.air.typeOf(arg);
-                            const arg_mcv = try self.resolveInst(inst.args[arg_i]);
+                            const arg_mcv = try self.resolveInst(args[arg_i]);
 
                             switch (mc_arg) {
                                 .none => continue,
@@ -2733,7 +2733,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 ptr_bits = self.target.cpu.arch.ptrBitWidth();
                                 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
@@ -2899,15 +2899,17 @@ 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, .{ bin_op.rhs, bin_op.lhs }, &.{});
+                            const regs = try self.register_manager.allocRegs(2, .{
+                                Air.refToIndex(bin_op.rhs).?, Air.refToIndex(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(bin_op.rhs, &.{}) };
+                            rhs_mcv = MCValue{ .register = try self.register_manager.allocReg(Air.refToIndex(bin_op.rhs).?, &.{}) };
                         }
                     }
                     if (!lhs_is_register) {
-                        lhs_mcv = MCValue{ .register = try self.register_manager.allocReg(bin_op.lhs, &.{}) };
+                        lhs_mcv = MCValue{ .register = try self.register_manager.allocReg(Air.refToIndex(bin_op.lhs).?, &.{}) };
                     }
 
                     // Move the operands to the newly allocated registers
@@ -3538,7 +3540,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
                         break :result MCValue{ .register = reg };
                     } else {
-                        break :result MCValue.none;
+                        break :result MCValue{ .none = {} };
                     }
                 },
                 .aarch64 => result: {
@@ -3576,7 +3578,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             return self.fail("unrecognized register: '{s}'", .{reg_name});
                         break :result MCValue{ .register = reg };
                     } else {
-                        break :result MCValue.none;
+                        break :result MCValue{ .none = {} };
                     }
                 },
                 .riscv64 => result: {
@@ -3612,7 +3614,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                             return self.fail("unrecognized register: '{s}'", .{reg_name});
                         break :result MCValue{ .register = reg };
                     } else {
-                        break :result MCValue.none;
+                        break :result MCValue{ .none = {} };
                     }
                 },
                 .x86_64, .i386 => result: {