Commit b17c8c5424

Luuk de Gram <luuk@degram.dev>
2022-10-12 21:18:56
wasm: reference count locals
By reference counting the locals, we can ensure that when we free a local, no local will be reused while it still has references pointing to it. This prevents misscompilations. The compiler will also panic if we free a local more than we reference it, introducing extra safety to ensure they match up.
1 parent b9b20b1
Changed files (1)
src
arch
src/arch/wasm/CodeGen.zig
@@ -32,8 +32,13 @@ const WValue = union(enum) {
     none: void,
     /// The value lives on top of the stack
     stack: void,
-    /// Index of the local variable
-    local: u32,
+    /// Index of the local
+    local: struct {
+        /// Contains the index to the local
+        value: u32,
+        /// The amount of instructions referencing this `WValue`
+        references: u32,
+    },
     /// An immediate 32bit value
     imm32: u32,
     /// An immediate 64bit value
@@ -60,7 +65,12 @@ const WValue = union(enum) {
     function_index: u32,
     /// Offset from the bottom of the virtual stack, with the offset
     /// pointing to where the value lives.
-    stack_offset: u32,
+    stack_offset: struct {
+        /// Contains the actual value of the offset
+        value: u32,
+        /// The amount of instructions referencing this `WValue`
+        references: u32,
+    },
 
     /// Returns the offset from the bottom of the stack. This is useful when
     /// we use the load or store instruction to ensure we retrieve the value
@@ -70,7 +80,7 @@ const WValue = union(enum) {
     /// loads and stores without requiring checks everywhere.
     fn offset(self: WValue) u32 {
         switch (self) {
-            .stack_offset => |stack_offset| return stack_offset,
+            .stack_offset => |stack_offset| return stack_offset.value,
             else => return 0,
         }
     }
@@ -81,9 +91,9 @@ const WValue = union(enum) {
     fn toLocal(value: WValue, gen: *Self, ty: Type) InnerError!WValue {
         switch (value) {
             .stack => {
-                const local = try gen.allocLocal(ty);
-                try gen.addLabel(.local_set, local.local);
-                return local;
+                const new_local = try gen.allocLocal(ty);
+                try gen.addLabel(.local_set, new_local.local.value);
+                return new_local;
             },
             .local, .stack_offset => return value,
             else => unreachable,
@@ -95,7 +105,7 @@ const WValue = union(enum) {
     /// The valtype of the local is deducted by using the index of the given `WValue`.
     fn free(value: *WValue, gen: *Self) void {
         if (value.* != .local) return;
-        const local_value = value.local;
+        const local_value = value.local.value;
         const reserved = gen.args.len + @boolToInt(gen.return_value != .none) + 2; // 2 for stack locals
         if (local_value < reserved) return; // reserved locals may never be re-used.
 
@@ -107,7 +117,7 @@ const WValue = union(enum) {
             .f32 => gen.free_locals_f32.append(gen.gpa, local_value) catch return,
             .f64 => gen.free_locals_f64.append(gen.gpa, local_value) catch return,
         }
-        value.* = WValue{ .none = {} };
+        value.* = undefined;
     }
 };
 
@@ -761,7 +771,9 @@ const BigTomb = struct {
             bt.gen.values.putAssumeCapacityNoClobber(Air.indexToRef(bt.inst), result);
         }
 
-        bt.gen.air_bookkeeping += 1;
+        if (builtin.mode == .Debug) {
+            bt.gen.air_bookkeeping += 1;
+        }
     }
 };
 
@@ -883,7 +895,7 @@ fn genBlockType(ty: Type, target: std.Target) u8 {
 fn emitWValue(self: *Self, value: WValue) InnerError!void {
     switch (value) {
         .none, .stack => {}, // no-op
-        .local => |idx| try self.addLabel(.local_get, idx),
+        .local => |idx| try self.addLabel(.local_get, idx.value),
         .imm32 => |val| try self.addImm32(@bitCast(i32, val)),
         .imm64 => |val| try self.addImm64(val),
         .float32 => |val| try self.addInst(.{ .tag = .f32_const, .data = .{ .float32 = val } }),
@@ -907,16 +919,16 @@ fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
     const valtype = typeToValtype(ty, self.target);
     switch (valtype) {
         .i32 => if (self.free_locals_i32.popOrNull()) |index| {
-            return WValue{ .local = index };
+            return WValue{ .local = .{ .value = index, .references = 1 } };
         },
         .i64 => if (self.free_locals_i64.popOrNull()) |index| {
-            return WValue{ .local = index };
+            return WValue{ .local = .{ .value = index, .references = 1 } };
         },
         .f32 => if (self.free_locals_f32.popOrNull()) |index| {
-            return WValue{ .local = index };
+            return WValue{ .local = .{ .value = index, .references = 1 } };
         },
         .f64 => if (self.free_locals_f64.popOrNull()) |index| {
-            return WValue{ .local = index };
+            return WValue{ .local = .{ .value = index, .references = 1 } };
         },
     }
     // no local was free to be re-used, so allocate a new local instead
@@ -929,7 +941,7 @@ fn ensureAllocLocal(self: *Self, ty: Type) InnerError!WValue {
     try self.locals.append(self.gpa, genValtype(ty, self.target));
     const initial_index = self.local_index;
     self.local_index += 1;
-    return WValue{ .local = initial_index };
+    return WValue{ .local = .{ .value = initial_index, .references = 1 } };
 }
 
 /// Generates a `wasm.Type` from a given function type.
@@ -1059,7 +1071,7 @@ fn genFunc(self: *Self) InnerError!void {
         // load stack pointer
         try prologue.append(.{ .tag = .global_get, .data = .{ .label = 0 } });
         // store stack pointer so we can restore it when we return from the function
-        try prologue.append(.{ .tag = .local_tee, .data = .{ .label = self.initial_stack_value.local } });
+        try prologue.append(.{ .tag = .local_tee, .data = .{ .label = self.initial_stack_value.local.value } });
         // get the total stack size
         const aligned_stack = std.mem.alignForwardGeneric(u32, self.stack_size, self.stack_alignment);
         try prologue.append(.{ .tag = .i32_const, .data = .{ .imm32 = @intCast(i32, aligned_stack) } });
@@ -1070,7 +1082,7 @@ fn genFunc(self: *Self) InnerError!void {
         // Bitwise-and the value to get the new stack pointer to ensure the pointers are aligned with the abi alignment
         try prologue.append(.{ .tag = .i32_and, .data = .{ .tag = {} } });
         // store the current stack pointer as the bottom, which will be used to calculate all stack pointer offsets
-        try prologue.append(.{ .tag = .local_tee, .data = .{ .label = self.bottom_stack_value.local } });
+        try prologue.append(.{ .tag = .local_tee, .data = .{ .label = self.bottom_stack_value.local.value } });
         // Store the current stack pointer value into the global stack pointer so other function calls will
         // start from this value instead and not overwrite the current stack.
         try prologue.append(.{ .tag = .global_set, .data = .{ .label = 0 } });
@@ -1141,7 +1153,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
     if (firstParamSRet(fn_info.cc, fn_info.return_type, self.target)) {
         // the sret arg will be passed as first argument, therefore we
         // set the `return_value` before allocating locals for regular args.
-        result.return_value = .{ .local = self.local_index };
+        result.return_value = .{ .local = .{ .value = self.local_index, .references = 1 } };
         self.local_index += 1;
     }
 
@@ -1152,7 +1164,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
                     continue;
                 }
 
-                try args.append(.{ .local = self.local_index });
+                try args.append(.{ .local = .{ .value = self.local_index, .references = 1 } });
                 self.local_index += 1;
             }
         },
@@ -1161,7 +1173,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
                 const ty_classes = abi.classifyType(ty, self.target);
                 for (ty_classes) |class| {
                     if (class == .none) continue;
-                    try args.append(.{ .local = self.local_index });
+                    try args.append(.{ .local = .{ .value = self.local_index, .references = 1 } });
                     self.local_index += 1;
                 }
             }
@@ -1254,14 +1266,14 @@ fn lowerToStack(self: *Self, value: WValue) !void {
     switch (value) {
         .stack_offset => |offset| {
             try self.emitWValue(value);
-            if (offset > 0) {
+            if (offset.value > 0) {
                 switch (self.arch()) {
                     .wasm32 => {
-                        try self.addImm32(@bitCast(i32, offset));
+                        try self.addImm32(@bitCast(i32, offset.value));
                         try self.addTag(.i32_add);
                     },
                     .wasm64 => {
-                        try self.addImm64(offset);
+                        try self.addImm64(offset.value);
                         try self.addTag(.i64_add);
                     },
                     else => unreachable,
@@ -1323,7 +1335,7 @@ fn allocStack(self: *Self, ty: Type) !WValue {
     const offset = std.mem.alignForwardGeneric(u32, self.stack_size, abi_align);
     defer self.stack_size = offset + abi_size;
 
-    return WValue{ .stack_offset = offset };
+    return WValue{ .stack_offset = .{ .value = offset, .references = 1 } };
 }
 
 /// From a given AIR instruction generates a pointer to the stack where
@@ -1356,7 +1368,7 @@ fn allocStackPtr(self: *Self, inst: Air.Inst.Index) !WValue {
     const offset = std.mem.alignForwardGeneric(u32, self.stack_size, abi_alignment);
     defer self.stack_size = offset + abi_size;
 
-    return WValue{ .stack_offset = offset };
+    return WValue{ .stack_offset = .{ .value = offset, .references = 1 } };
 }
 
 /// From given zig bitsize, returns the wasm bitsize
@@ -1475,7 +1487,7 @@ fn memcpy(self: *Self, dst: WValue, src: WValue, len: WValue) !void {
                     },
                     else => unreachable,
                 }
-                try self.addLabel(.local_set, offset.local);
+                try self.addLabel(.local_set, offset.local.value);
                 try self.addLabel(.br, 0); // jump to start of loop
             }
             try self.endBlock(); // close off loop block
@@ -1568,7 +1580,7 @@ fn buildPointerOffset(self: *Self, ptr_value: WValue, offset: u64, action: enum
             else => unreachable,
         }
     }
-    try self.addLabel(.local_set, result_ptr.local);
+    try self.addLabel(.local_set, result_ptr.local.value);
     return result_ptr;
 }
 
@@ -1984,14 +1996,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
             // TODO: Make this less fragile and optimize
         } else if (fn_ty.fnInfo().cc == .C and ret_ty.zigTypeTag() == .Struct or ret_ty.zigTypeTag() == .Union) {
             const result_local = try self.allocLocal(ret_ty);
-            try self.addLabel(.local_set, result_local.local);
+            try self.addLabel(.local_set, result_local.local.value);
             const scalar_type = abi.scalarType(ret_ty, self.target);
             const result = try self.allocStack(scalar_type);
             try self.store(result, result_local, scalar_type, 0);
             break :result_value result;
         } else {
             const result_local = try self.allocLocal(ret_ty);
-            try self.addLabel(.local_set, result_local.local);
+            try self.addLabel(.local_set, result_local.local.value);
             break :result_value result_local;
         }
     };
@@ -2175,7 +2187,7 @@ fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!void {
         .dwarf => |dwarf| {
             // TODO: Get the original arg index rather than wasm arg index
             const name = self.mod_fn.getParamName(self.bin_file.base.options.module.?, arg_index);
-            const leb_size = link.File.Wasm.getULEB128Size(arg.local);
+            const leb_size = link.File.Wasm.getULEB128Size(arg.local.value);
             const dbg_info = &dwarf.dbg_info;
             try dbg_info.ensureUnusedCapacity(3 + leb_size + 5 + name.len + 1);
             // wasm locations are encoded as follow:
@@ -2189,7 +2201,7 @@ fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!void {
                 std.dwarf.OP.WASM_location,
                 std.dwarf.OP.WASM_local,
             });
-            leb.writeULEB128(dbg_info.writer(), arg.local) catch unreachable;
+            leb.writeULEB128(dbg_info.writer(), arg.local.value) catch unreachable;
             try self.addDbgInfoTypeReloc(arg_ty);
             dbg_info.appendSliceAssumeCapacity(name);
             dbg_info.appendAssumeCapacity(0);
@@ -2869,7 +2881,7 @@ fn airBr(self: *Self, inst: Air.Inst.Index) InnerError!void {
         try self.lowerToStack(operand);
 
         if (block.value != .none) {
-            try self.addLabel(.local_set, block.value.local);
+            try self.addLabel(.local_set, block.value.local.value);
         }
     }
 
@@ -2893,7 +2905,7 @@ fn airNot(self: *Self, inst: Air.Inst.Index) InnerError!void {
             try self.emitWValue(operand);
             try self.addTag(.i32_eqz);
             const not_tmp = try self.allocLocal(operand_ty);
-            try self.addLabel(.local_set, not_tmp.local);
+            try self.addLabel(.local_set, not_tmp.local.value);
             break :result not_tmp;
         } else {
             const operand_bits = operand_ty.intInfo(self.target).bits;
@@ -2985,7 +2997,7 @@ fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u32) InnerEr
 fn structFieldPtr(self: *Self, struct_ptr: WValue, offset: u32) InnerError!WValue {
     switch (struct_ptr) {
         .stack_offset => |stack_offset| {
-            return WValue{ .stack_offset = stack_offset + offset };
+            return WValue{ .stack_offset = .{ .value = stack_offset.value + offset, .references = 1 } };
         },
         else => return self.buildPointerOffset(struct_ptr, offset, .new),
     }
@@ -3011,7 +3023,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!void {
         if (isByRef(field_ty, self.target)) {
             switch (operand) {
                 .stack_offset => |stack_offset| {
-                    break :result WValue{ .stack_offset = stack_offset + offset };
+                    break :result WValue{ .stack_offset = .{ .value = stack_offset.value + offset, .references = 1 } };
                 },
                 else => break :result try self.buildPointerOffset(operand, offset, .new),
             }
@@ -3214,7 +3226,7 @@ fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!v
         try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
 
         const is_err_tmp = try self.allocLocal(Type.i32);
-        try self.addLabel(.local_set, is_err_tmp.local);
+        try self.addLabel(.local_set, is_err_tmp.local.value);
         break :result is_err_tmp;
     };
     self.finishAir(inst, result, &.{un_op});
@@ -3578,7 +3590,7 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) InnerError!void {
     try self.addTag(.i32_add);
 
     const result_ptr = try self.allocLocal(elem_ty);
-    try self.addLabel(.local_set, result_ptr.local);
+    try self.addLabel(.local_set, result_ptr.local.value);
 
     const result = if (!isByRef(elem_ty, self.target)) result: {
         const elem_val = try self.load(result_ptr, elem_ty, 0);
@@ -3608,7 +3620,7 @@ fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     try self.addTag(.i32_add);
 
     const result = try self.allocLocal(Type.i32);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     self.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
 }
 
@@ -3715,7 +3727,7 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) InnerError!void {
 
     const elem_result = val: {
         var result = try self.allocLocal(elem_ty);
-        try self.addLabel(.local_set, result.local);
+        try self.addLabel(.local_set, result.local.value);
         if (isByRef(elem_ty, self.target)) {
             break :val result;
         }
@@ -3753,7 +3765,7 @@ fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     try self.addTag(.i32_add);
 
     const result = try self.allocLocal(Type.i32);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     self.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
 }
 
@@ -3781,7 +3793,7 @@ fn airPtrBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!void {
     try self.addTag(Mir.Inst.Tag.fromOpcode(bin_opcode));
 
     const result = try self.allocLocal(Type.usize);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     self.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
 }
 
@@ -3874,7 +3886,7 @@ fn memset(self: *Self, ptr: WValue, len: WValue, value: WValue) InnerError!void
                 .wasm64 => try self.addTag(.i64_add),
                 else => unreachable,
             }
-            try self.addLabel(.local_set, offset.local);
+            try self.addLabel(.local_set, offset.local.value);
             try self.addLabel(.br, 0); // jump to start of loop
             try self.endBlock();
             try self.endBlock();
@@ -3900,7 +3912,7 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) InnerError!void {
 
     const elem_result = val: {
         var result = try self.allocLocal(Type.usize);
-        try self.addLabel(.local_set, result.local);
+        try self.addLabel(.local_set, result.local.value);
 
         if (isByRef(elem_ty, self.target)) {
             break :val result;
@@ -3961,7 +3973,7 @@ fn airIntToFloat(self: *Self, inst: Air.Inst.Index) InnerError!void {
     try self.addTag(Mir.Inst.Tag.fromOpcode(op));
 
     const result = try self.allocLocal(dest_ty);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     self.finishAir(inst, result, &.{ty_op.operand});
 }
 
@@ -4107,7 +4119,7 @@ fn airWasmMemorySize(self: *Self, inst: Air.Inst.Index) InnerError!void {
 
     const result = try self.allocLocal(self.air.typeOfIndex(inst));
     try self.addLabel(.memory_size, pl_op.payload);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     self.finishAir(inst, result, &.{pl_op.operand});
 }
 
@@ -4119,7 +4131,7 @@ fn airWasmMemoryGrow(self: *Self, inst: Air.Inst.Index) !void {
     const result = try self.allocLocal(self.air.typeOfIndex(inst));
     try self.emitWValue(operand);
     try self.addLabel(.memory_grow, pl_op.payload);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     self.finishAir(inst, result, &.{pl_op.operand});
 }
 
@@ -4148,7 +4160,7 @@ fn cmpOptionals(self: *Self, lhs: WValue, rhs: WValue, operand_ty: Type, op: std
     try self.addLabel(.br_if, 0);
 
     try self.addImm32(1);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     try self.endBlock();
 
     try self.emitWValue(result);
@@ -4363,10 +4375,10 @@ fn airFieldParentPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
 
     const result = if (field_offset != 0) result: {
         const base = try self.buildPointerOffset(field_ptr, 0, .new);
-        try self.addLabel(.local_get, base.local);
+        try self.addLabel(.local_get, base.local.value);
         try self.addImm32(@bitCast(i32, @intCast(u32, field_offset)));
         try self.addTag(.i32_sub);
-        try self.addLabel(.local_set, base.local);
+        try self.addLabel(.local_set, base.local.value);
         break :result base;
     } else field_ptr;
 
@@ -4425,7 +4437,7 @@ fn airPopcount(self: *Self, inst: Air.Inst.Index) InnerError!void {
     }
 
     const result = try self.allocLocal(result_ty);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     self.finishAir(inst, result, &.{ty_op.operand});
 }
 
@@ -4467,7 +4479,7 @@ fn airErrorName(self: *Self, inst: Air.Inst.Index) InnerError!void {
     }
 
     const result_ptr = try self.allocLocal(Type.usize);
-    try self.addLabel(.local_set, result_ptr.local);
+    try self.addLabel(.local_set, result_ptr.local.value);
     self.finishAir(inst, result_ptr, &.{un_op});
 }
 
@@ -4714,7 +4726,7 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!void {
             const shr = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr);
             const wrap = try self.intcast(shr, new_ty, lhs_ty);
             _ = try self.cmp(wrap, zero, lhs_ty, .neq);
-            try self.addLabel(.local_set, overflow_bit.local);
+            try self.addLabel(.local_set, overflow_bit.local.value);
             break :blk try self.intcast(bin_op, new_ty, lhs_ty);
         } else {
             const down_cast = try (try self.intcast(bin_op, new_ty, lhs_ty)).toLocal(self, lhs_ty);
@@ -4724,7 +4736,7 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!void {
             const shr_res = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr);
             const down_shr_res = try self.intcast(shr_res, new_ty, lhs_ty);
             _ = try self.cmp(down_shr_res, shr, lhs_ty, .neq);
-            try self.addLabel(.local_set, overflow_bit.local);
+            try self.addLabel(.local_set, overflow_bit.local.value);
             break :blk down_cast;
         }
     } else if (int_info.signedness == .signed) blk: {
@@ -4733,7 +4745,7 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!void {
         const bin_op = try (try self.binOp(lhs_abs, rhs_abs, lhs_ty, .mul)).toLocal(self, lhs_ty);
         const mul_abs = try self.signAbsValue(bin_op, lhs_ty);
         _ = try self.cmp(mul_abs, bin_op, lhs_ty, .neq);
-        try self.addLabel(.local_set, overflow_bit.local);
+        try self.addLabel(.local_set, overflow_bit.local.value);
         break :blk try self.wrapOperand(bin_op, lhs_ty);
     } else blk: {
         var bin_op = try (try self.binOp(lhs, rhs, lhs_ty, .mul)).toLocal(self, lhs_ty);
@@ -4744,7 +4756,7 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!void {
             WValue{ .imm64 = int_info.bits };
         const shr = try self.binOp(bin_op, shift_imm, lhs_ty, .shr);
         _ = try self.cmp(shr, zero, lhs_ty, .neq);
-        try self.addLabel(.local_set, overflow_bit.local);
+        try self.addLabel(.local_set, overflow_bit.local.value);
         break :blk try self.wrapOperand(bin_op, lhs_ty);
     };
     var bin_op_local = try bin_op.toLocal(self, lhs_ty);
@@ -4785,7 +4797,7 @@ fn airMaxMin(self: *Self, inst: Air.Inst.Index, op: enum { max, min }) InnerErro
     // store result in local
     const result_ty = if (isByRef(ty, self.target)) Type.u32 else ty;
     const result = try self.allocLocal(result_ty);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     self.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
 }
 
@@ -4872,7 +4884,7 @@ fn airClz(self: *Self, inst: Air.Inst.Index) InnerError!void {
     }
 
     const result = try self.allocLocal(result_ty);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     self.finishAir(inst, result, &.{ty_op.operand});
 }
 
@@ -4937,7 +4949,7 @@ fn airCtz(self: *Self, inst: Air.Inst.Index) InnerError!void {
     }
 
     const result = try self.allocLocal(result_ty);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     self.finishAir(inst, result, &.{ty_op.operand});
 }
 
@@ -4958,7 +4970,7 @@ fn airDbgVar(self: *Self, inst: Air.Inst.Index, is_ptr: bool) !void {
     try dbg_info.append(@enumToInt(link.File.Dwarf.AbbrevKind.variable));
     switch (operand) {
         .local => |local| {
-            const leb_size = link.File.Wasm.getULEB128Size(local);
+            const leb_size = link.File.Wasm.getULEB128Size(local.value);
             try dbg_info.ensureUnusedCapacity(2 + leb_size);
             // wasm locals are encoded as follow:
             // DW_OP_WASM_location wasm-op
@@ -4970,7 +4982,7 @@ fn airDbgVar(self: *Self, inst: Air.Inst.Index, is_ptr: bool) !void {
                 std.dwarf.OP.WASM_location,
                 std.dwarf.OP.WASM_local,
             });
-            leb.writeULEB128(dbg_info.writer(), local) catch unreachable;
+            leb.writeULEB128(dbg_info.writer(), local.value) catch unreachable;
         },
         else => {}, // TODO
     }
@@ -5179,7 +5191,7 @@ fn airDivFloor(self: *Self, inst: Air.Inst.Index) InnerError!void {
         const div_result = try self.allocLocal(ty);
         // leave on stack
         _ = try self.binOp(lhs_res, rhs_res, ty, .div);
-        try self.addLabel(.local_tee, div_result.local);
+        try self.addLabel(.local_tee, div_result.local.value);
         _ = try self.cmp(lhs_res, zero, ty, .lt);
         _ = try self.cmp(rhs_res, zero, ty, .lt);
         switch (wasm_bits) {
@@ -5232,7 +5244,7 @@ fn airDivFloor(self: *Self, inst: Air.Inst.Index) InnerError!void {
     }
 
     const result = try self.allocLocal(ty);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     self.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
 }
 
@@ -5257,7 +5269,7 @@ fn divSigned(self: *Self, lhs: WValue, rhs: WValue, ty: Type) InnerError!WValue
     try self.addTag(.i32_div_s);
 
     const result = try self.allocLocal(ty);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     return result;
 }
 
@@ -5323,7 +5335,7 @@ fn airCeilFloorTrunc(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!void
     }
 
     const result = try self.allocLocal(ty);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     self.finishAir(inst, result, &.{un_op});
 }
 
@@ -5374,7 +5386,7 @@ fn airSatBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!void {
 
     try self.addTag(.select);
     const result = try self.allocLocal(ty);
-    try self.addLabel(.local_set, result.local);
+    try self.addLabel(.local_set, result.local.value);
     return self.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
 }
 
@@ -5412,13 +5424,13 @@ fn signedSat(self: *Self, lhs_operand: WValue, rhs_operand: WValue, ty: Type, op
         try self.emitWValue(max_wvalue);
         _ = try self.cmp(bin_result, max_wvalue, ty, .lt);
         try self.addTag(.select);
-        try self.addLabel(.local_set, bin_result.local); // re-use local
+        try self.addLabel(.local_set, bin_result.local.value); // re-use local
 
         try self.emitWValue(bin_result);
         try self.emitWValue(min_wvalue);
         _ = try self.cmp(bin_result, min_wvalue, ty, .gt);
         try self.addTag(.select);
-        try self.addLabel(.local_set, bin_result.local); // re-use local
+        try self.addLabel(.local_set, bin_result.local.value); // re-use local
         return (try self.wrapOperand(bin_result, ty)).toLocal(self, ty);
     } else {
         const zero = switch (wasm_bits) {
@@ -5436,7 +5448,7 @@ fn signedSat(self: *Self, lhs_operand: WValue, rhs_operand: WValue, ty: Type, op
         const cmp_bin_result = try self.cmp(bin_result, lhs, ty, .lt);
         _ = try self.binOp(cmp_zero_result, cmp_bin_result, Type.u32, .xor); // comparisons always return i32, so provide u32 as type to xor.
         try self.addTag(.select);
-        try self.addLabel(.local_set, bin_result.local); // re-use local
+        try self.addLabel(.local_set, bin_result.local.value); // re-use local
         return bin_result;
     }
 }
@@ -5489,7 +5501,7 @@ fn airShlSat(self: *Self, inst: Air.Inst.Index) InnerError!void {
         try self.emitWValue(shl);
         _ = try self.cmp(lhs, shr, ty, .neq);
         try self.addTag(.select);
-        try self.addLabel(.local_set, result.local);
+        try self.addLabel(.local_set, result.local.value);
         break :outer_blk;
     } else {
         const shift_size = wasm_bits - int_info.bits;
@@ -5534,12 +5546,12 @@ fn airShlSat(self: *Self, inst: Air.Inst.Index) InnerError!void {
         try self.emitWValue(shl);
         _ = try self.cmp(shl_res, shr, ty, .neq);
         try self.addTag(.select);
-        try self.addLabel(.local_set, result.local);
+        try self.addLabel(.local_set, result.local.value);
         var shift_result = try self.binOp(result, shift_value, ty, .shr);
         if (is_signed) {
             shift_result = try self.wrapOperand(shift_result, ty);
         }
-        try self.addLabel(.local_set, result.local);
+        try self.addLabel(.local_set, result.local.value);
     }
 
     return self.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });