Commit 364e53f3bf

Jakub Konka <kubkon@jakubkonka.com>
2022-03-30 18:29:44
dwarf: emit debug info for local variables on x86_64
Add support for emitting debug info for local variables within a subprogram. This required moving bits responsible for populating the debug info back to `CodeGen` from `Emit` as we require the operand to be resolved at callsite plus we need to know its type. Without enforcing this, we could end up with a `dead` mcv.
1 parent 795f075
Changed files (7)
src/arch/arm/Emit.zig
@@ -417,7 +417,7 @@ fn genArgDbgInfo(self: *Emit, inst: Air.Inst.Index, arg_index: u32) !void {
                 .dwarf => |dw| {
                     const dbg_info = &dw.dbg_info;
                     try dbg_info.ensureUnusedCapacity(3);
-                    dbg_info.appendAssumeCapacity(link.File.Dwarf.abbrev_parameter);
+                    dbg_info.appendAssumeCapacity(@enumToInt(link.File.Dwarf.AbbrevKind.parameter));
                     dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
                         1, // ULEB128 dwarf expression length
                         reg.dwarfLocOp(),
@@ -449,7 +449,7 @@ fn genArgDbgInfo(self: *Emit, inst: Air.Inst.Index, arg_index: u32) !void {
                     };
 
                     const dbg_info = &dw.dbg_info;
-                    try dbg_info.append(link.File.Dwarf.abbrev_parameter);
+                    try dbg_info.append(@enumToInt(link.File.Dwarf.AbbrevKind.parameter));
 
                     // Get length of the LEB128 stack offset
                     var counting_writer = std.io.countingWriter(std.io.null_writer);
src/arch/riscv64/CodeGen.zig
@@ -1574,7 +1574,7 @@ fn genArgDbgInfo(self: *Self, inst: Air.Inst.Index, mcv: MCValue, arg_index: u32
                 .dwarf => |dw| {
                     const dbg_info = &dw.dbg_info;
                     try dbg_info.ensureUnusedCapacity(3);
-                    dbg_info.appendAssumeCapacity(link.File.Dwarf.abbrev_parameter);
+                    dbg_info.appendAssumeCapacity(@enumToInt(link.File.Dwarf.AbbrevKind.parameter));
                     dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
                         1, // ULEB128 dwarf expression length
                         reg.dwarfLocOp(),
src/arch/x86_64/CodeGen.zig
@@ -48,6 +48,7 @@ gpa: Allocator,
 air: Air,
 liveness: Liveness,
 bin_file: *link.File,
+debug_output: DebugInfoOutput,
 target: *const std.Target,
 mod_fn: *const Module.Fn,
 err_msg: ?*ErrorMsg,
@@ -337,6 +338,7 @@ pub fn generate(
         .liveness = liveness,
         .target = &bin_file.options.target,
         .bin_file = bin_file,
+        .debug_output = debug_output,
         .mod_fn = module_fn,
         .err_msg = null,
         .args = undefined, // populated after `resolveCallingConventionValues`
@@ -382,7 +384,6 @@ pub fn generate(
     };
 
     var mir = Mir{
-        .function = &function,
         .instructions = function.mir_instructions.toOwnedSlice(),
         .extra = function.mir_extra.toOwnedSlice(bin_file.allocator),
     };
@@ -391,7 +392,6 @@ pub fn generate(
     var emit = Emit{
         .mir = mir,
         .bin_file = bin_file,
-        .function = &function,
         .debug_output = debug_output,
         .target = &bin_file.options.target,
         .src_loc = src_loc,
@@ -3425,17 +3425,11 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {
     const arg_index = self.arg_index;
     self.arg_index += 1;
 
+    const ty = self.air.typeOfIndex(inst);
     const mcv = self.args[arg_index];
-    const payload = try self.addExtra(Mir.ArgDbgInfo{
-        .air_inst = inst,
-        .arg_index = arg_index,
-        .max_stack = self.max_end_stack,
-    });
-    _ = try self.addInst(.{
-        .tag = .arg_dbg_info,
-        .ops = undefined,
-        .data = .{ .payload = payload },
-    });
+    const name = self.mod_fn.getParamName(arg_index);
+    const name_with_null = name.ptr[0 .. name.len + 1];
+
     if (self.liveness.isUnused(inst))
         return self.finishAirBookkeeping();
 
@@ -3443,10 +3437,46 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {
         switch (mcv) {
             .register => |reg| {
                 self.register_manager.getRegAssumeFree(reg.to64(), inst);
+                switch (self.debug_output) {
+                    .dwarf => |dw| {
+                        const dbg_info = &dw.dbg_info;
+                        try dbg_info.ensureUnusedCapacity(3);
+                        dbg_info.appendAssumeCapacity(@enumToInt(link.File.Dwarf.AbbrevKind.parameter));
+                        dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
+                            1, // ULEB128 dwarf expression length
+                            reg.dwarfLocOp(),
+                        });
+                        try dbg_info.ensureUnusedCapacity(5 + name_with_null.len);
+                        try self.addDbgInfoTypeReloc(ty); // DW.AT.type,  DW.FORM.ref4
+                        dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string
+                    },
+                    .plan9 => {},
+                    .none => {},
+                }
                 break :blk mcv;
             },
             .stack_offset => |off| {
                 const offset = @intCast(i32, self.max_end_stack) - off + 16;
+                switch (self.debug_output) {
+                    .dwarf => |dw| {
+                        const dbg_info = &dw.dbg_info;
+                        try dbg_info.ensureUnusedCapacity(8);
+                        dbg_info.appendAssumeCapacity(@enumToInt(link.File.Dwarf.AbbrevKind.parameter));
+                        const fixup = dbg_info.items.len;
+                        dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
+                            1, // we will backpatch it after we encode the displacement in LEB128
+                            DW.OP.breg6, // .rbp TODO handle -fomit-frame-pointer
+                        });
+                        leb128.writeILEB128(dbg_info.writer(), offset) catch unreachable;
+                        dbg_info.items[fixup] += @intCast(u8, dbg_info.items.len - fixup - 2);
+                        try dbg_info.ensureUnusedCapacity(5 + name_with_null.len);
+                        try self.addDbgInfoTypeReloc(ty); // DW.AT.type,  DW.FORM.ref4
+                        dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string
+
+                    },
+                    .plan9 => {},
+                    .none => {},
+                }
                 break :blk MCValue{ .stack_offset = -offset };
             },
             else => return self.fail("TODO implement arg for {}", .{mcv}),
@@ -3885,13 +3915,99 @@ fn airDbgBlock(self: *Self, inst: Air.Inst.Index) !void {
 
 fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void {
     const pl_op = self.air.instructions.items(.data)[inst].pl_op;
-    const name = self.air.nullTerminatedString(pl_op.payload);
     const operand = pl_op.operand;
-    // TODO emit debug info for this variable
-    _ = name;
+    const ty = self.air.typeOf(operand);
+
+    if (!self.liveness.operandDies(inst, 0)) {
+        const mcv = try self.resolveInst(operand);
+        const name = self.air.nullTerminatedString(pl_op.payload);
+
+        const tag = self.air.instructions.items(.tag)[inst];
+        switch (tag) {
+            .dbg_var_ptr => try self.genVarDbgInfo(ty.childType(), mcv, name),
+            .dbg_var_val => try self.genVarDbgInfo(ty, mcv, name),
+            else => unreachable,
+        }
+    }
+
     return self.finishAir(inst, .dead, .{ operand, .none, .none });
 }
 
+fn genVarDbgInfo(
+    self: *Self,
+    ty: Type,
+    mcv: MCValue,
+    name: [:0]const u8,
+) !void {
+    const name_with_null = name.ptr[0 .. name.len + 1];
+    switch (mcv) {
+        .register => |reg| {
+            switch (self.debug_output) {
+                .dwarf => |dw| {
+                    const dbg_info = &dw.dbg_info;
+                    try dbg_info.ensureUnusedCapacity(3);
+                    dbg_info.appendAssumeCapacity(@enumToInt(link.File.Dwarf.AbbrevKind.variable));
+                    dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
+                        1, // ULEB128 dwarf expression length
+                        reg.dwarfLocOp(),
+                    });
+                    try dbg_info.ensureUnusedCapacity(5 + name_with_null.len);
+                    try self.addDbgInfoTypeReloc(ty); // DW.AT.type,  DW.FORM.ref4
+                    dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string
+                },
+                .plan9 => {},
+                .none => {},
+            }
+        },
+        .ptr_stack_offset, .stack_offset => |off| {
+            switch (self.debug_output) {
+                .dwarf => |dw| {
+                    const dbg_info = &dw.dbg_info;
+                    try dbg_info.ensureUnusedCapacity(8);
+                    dbg_info.appendAssumeCapacity(@enumToInt(link.File.Dwarf.AbbrevKind.variable));
+                    const fixup = dbg_info.items.len;
+                    dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
+                        1, // we will backpatch it after we encode the displacement in LEB128
+                        DW.OP.breg6, // .rbp TODO handle -fomit-frame-pointer
+                    });
+                    leb128.writeILEB128(dbg_info.writer(), -off) catch unreachable;
+                    dbg_info.items[fixup] += @intCast(u8, dbg_info.items.len - fixup - 2);
+                    try dbg_info.ensureUnusedCapacity(5 + name_with_null.len);
+                    try self.addDbgInfoTypeReloc(ty); // DW.AT.type,  DW.FORM.ref4
+                    dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string
+
+                },
+                .plan9 => {},
+                .none => {},
+            }
+        },
+        else => {
+            log.debug("TODO generate debug info for {}", .{mcv});
+        },
+    }
+}
+
+/// Adds a Type to the .debug_info at the current position. The bytes will be populated later,
+/// after codegen for this symbol is done.
+fn addDbgInfoTypeReloc(self: *Self, ty: Type) !void {
+    switch (self.debug_output) {
+        .dwarf => |dw| {
+            assert(ty.hasRuntimeBits());
+            const dbg_info = &dw.dbg_info;
+            const index = dbg_info.items.len;
+            try dbg_info.resize(index + 4); // DW.AT.type,  DW.FORM.ref4
+            const atom = switch (self.bin_file.tag) {
+                .elf => &self.mod_fn.owner_decl.link.elf.dbg_info_atom,
+                .macho => &self.mod_fn.owner_decl.link.macho.dbg_info_atom,
+                else => unreachable,
+            };
+            try dw.addTypeReloc(atom, ty, @intCast(u32, index), null);
+        },
+        .plan9 => {},
+        .none => {},
+    }
+}
+
 fn genCondBrMir(self: *Self, ty: Type, mcv: MCValue) !u32 {
     const abi_size = ty.abiSize(self.target.*);
     switch (mcv) {
@@ -5919,7 +6035,7 @@ fn airMulAdd(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, pl_op.operand });
 }
 
-fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
+pub fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
     // First section of indexes correspond to a set number of constant values.
     const ref_int = @enumToInt(inst);
     if (ref_int < Air.Inst.Ref.typed_value_map.len) {
src/arch/x86_64/Emit.zig
@@ -30,7 +30,6 @@ const Type = @import("../../type.zig").Type;
 
 mir: Mir,
 bin_file: *link.File,
-function: *const CodeGen,
 debug_output: DebugInfoOutput,
 target: *const std.Target,
 err_msg: ?*ErrorMsg = null,
@@ -187,7 +186,6 @@ pub fn lowerMir(emit: *Emit) InnerError!void {
             .dbg_line => try emit.mirDbgLine(inst),
             .dbg_prologue_end => try emit.mirDbgPrologueEnd(inst),
             .dbg_epilogue_begin => try emit.mirDbgEpilogueBegin(inst),
-            .arg_dbg_info => try emit.mirArgDbgInfo(inst),
 
             .push_regs_from_callee_preserved_regs => try emit.mirPushPopRegsFromCalleePreservedRegs(.push, inst),
             .pop_regs_from_callee_preserved_regs => try emit.mirPushPopRegsFromCalleePreservedRegs(.pop, inst),
@@ -1057,92 +1055,6 @@ fn mirDbgEpilogueBegin(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
     }
 }
 
-fn mirArgDbgInfo(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
-    const tag = emit.mir.instructions.items(.tag)[inst];
-    assert(tag == .arg_dbg_info);
-    const payload = emit.mir.instructions.items(.data)[inst].payload;
-    const arg_dbg_info = emit.mir.extraData(Mir.ArgDbgInfo, payload).data;
-    const mcv = emit.mir.function.args[arg_dbg_info.arg_index];
-    try emit.genArgDbgInfo(arg_dbg_info.air_inst, mcv, arg_dbg_info.max_stack, arg_dbg_info.arg_index);
-}
-
-fn genArgDbgInfo(emit: *Emit, inst: Air.Inst.Index, mcv: MCValue, max_stack: u32, arg_index: u32) !void {
-    const ty = emit.mir.function.air.instructions.items(.data)[inst].ty;
-    const name = emit.mir.function.mod_fn.getParamName(arg_index);
-    const name_with_null = name.ptr[0 .. name.len + 1];
-
-    switch (mcv) {
-        .register => |reg| {
-            switch (emit.debug_output) {
-                .dwarf => |dw| {
-                    const dbg_info = &dw.dbg_info;
-                    try dbg_info.ensureUnusedCapacity(3);
-                    dbg_info.appendAssumeCapacity(link.File.Dwarf.abbrev_parameter);
-                    dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
-                        1, // ULEB128 dwarf expression length
-                        reg.dwarfLocOp(),
-                    });
-                    try dbg_info.ensureUnusedCapacity(5 + name_with_null.len);
-                    try emit.addDbgInfoTypeReloc(ty); // DW.AT.type,  DW.FORM.ref4
-                    dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string
-                },
-                .plan9 => {},
-                .none => {},
-            }
-        },
-        .stack_offset => |off| {
-            switch (emit.debug_output) {
-                .dwarf => |dw| {
-                    // we add here +16 like we do in airArg in CodeGen since we refer directly to
-                    // rbp as the start of function frame minus 8 bytes for caller's rbp preserved in the
-                    // prologue, and 8 bytes for return address.
-                    // TODO we need to make this more generic if we don't use rbp as the frame pointer
-                    // for example when -fomit-frame-pointer is set.
-                    const disp = @intCast(i32, max_stack) - off + 16;
-                    const dbg_info = &dw.dbg_info;
-                    try dbg_info.ensureUnusedCapacity(8);
-                    dbg_info.appendAssumeCapacity(link.File.Dwarf.abbrev_parameter);
-                    const fixup = dbg_info.items.len;
-                    dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
-                        1, // we will backpatch it after we encode the displacement in LEB128
-                        DW.OP.breg6, // .rbp TODO handle -fomit-frame-pointer
-                    });
-                    leb128.writeILEB128(dbg_info.writer(), disp) catch unreachable;
-                    dbg_info.items[fixup] += @intCast(u8, dbg_info.items.len - fixup - 2);
-                    try dbg_info.ensureUnusedCapacity(5 + name_with_null.len);
-                    try emit.addDbgInfoTypeReloc(ty); // DW.AT.type,  DW.FORM.ref4
-                    dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string
-
-                },
-                .plan9 => {},
-                .none => {},
-            }
-        },
-        else => {},
-    }
-}
-
-/// Adds a Type to the .debug_info at the current position. The bytes will be populated later,
-/// after codegen for this symbol is done.
-fn addDbgInfoTypeReloc(emit: *Emit, ty: Type) !void {
-    switch (emit.debug_output) {
-        .dwarf => |dw| {
-            assert(ty.hasRuntimeBits());
-            const dbg_info = &dw.dbg_info;
-            const index = dbg_info.items.len;
-            try dbg_info.resize(index + 4); // DW.AT.type,  DW.FORM.ref4
-            const atom = switch (emit.bin_file.tag) {
-                .elf => &emit.function.mod_fn.owner_decl.link.elf.dbg_info_atom,
-                .macho => &emit.function.mod_fn.owner_decl.link.macho.dbg_info_atom,
-                else => unreachable,
-            };
-            try dw.addTypeReloc(atom, ty, @intCast(u32, index), null);
-        },
-        .plan9 => {},
-        .none => {},
-    }
-}
-
 const Tag = enum {
     adc,
     add,
src/arch/x86_64/Mir.zig
@@ -16,7 +16,6 @@ const Air = @import("../../Air.zig");
 const CodeGen = @import("CodeGen.zig");
 const Register = bits.Register;
 
-function: *const CodeGen,
 instructions: std.MultiArrayList(Inst).Slice,
 /// The meaning of this data is determined by `Inst.Tag` value.
 extra: []const u32,
@@ -364,9 +363,6 @@ pub const Inst = struct {
         /// update debug line
         dbg_line,
 
-        /// arg debug info
-        arg_dbg_info,
-
         /// push registers from the callee_preserved_regs
         /// data is the bitfield of which regs to push 
         /// for example on x86_64, the callee_preserved_regs are [_]Register{ .rcx, .rsi, .rdi, .r8, .r9, .r10, .r11 };    };
@@ -453,18 +449,6 @@ pub const DbgLineColumn = struct {
     column: u32,
 };
 
-pub const ArgDbgInfo = struct {
-    air_inst: Air.Inst.Index,
-    arg_index: u32,
-    max_stack: u32,
-};
-
-pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void {
-    mir.instructions.deinit(gpa);
-    gpa.free(mir.extra);
-    mir.* = undefined;
-}
-
 pub const Ops = struct {
     reg1: Register = .none,
     reg2: Register = .none,
@@ -490,6 +474,12 @@ pub const Ops = struct {
     }
 };
 
+pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void {
+    mir.instructions.deinit(gpa);
+    gpa.free(mir.extra);
+    mir.* = undefined;
+}
+
 pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } {
     const fields = std.meta.fields(T);
     var i: usize = index;
src/arch/x86_64/PrintMir.zig
@@ -147,7 +147,7 @@ pub fn printMir(print: *const Print, w: anytype, mir_to_air_map: std.AutoHashMap
 
             .call_extern => try print.mirCallExtern(inst, w),
 
-            .dbg_line, .dbg_prologue_end, .dbg_epilogue_begin, .arg_dbg_info => try w.print("{s}\n", .{@tagName(tag)}),
+            .dbg_line, .dbg_prologue_end, .dbg_epilogue_begin => try w.print("{s}\n", .{@tagName(tag)}),
 
             .push_regs_from_callee_preserved_regs => try print.mirPushPopRegsFromCalleePreservedRegs(.push, inst, w),
             .pop_regs_from_callee_preserved_regs => try print.mirPushPopRegsFromCalleePreservedRegs(.pop, inst, w),
src/link/Dwarf.zig
@@ -148,11 +148,11 @@ pub const DeclState = struct {
         switch (ty.zigTypeTag()) {
             .NoReturn => unreachable,
             .Void => {
-                try dbg_info_buffer.append(abbrev_pad1);
+                try dbg_info_buffer.append(@enumToInt(AbbrevKind.pad1));
             },
             .Bool => {
                 try dbg_info_buffer.appendSlice(&[_]u8{
-                    abbrev_base_type,
+                    @enumToInt(AbbrevKind.base_type),
                     DW.ATE.boolean, // DW.AT.encoding ,  DW.FORM.data1
                     1, // DW.AT.byte_size,  DW.FORM.data1
                     'b', 'o', 'o', 'l', 0, // DW.AT.name,  DW.FORM.string
@@ -161,7 +161,7 @@ pub const DeclState = struct {
             .Int => {
                 const info = ty.intInfo(target);
                 try dbg_info_buffer.ensureUnusedCapacity(12);
-                dbg_info_buffer.appendAssumeCapacity(abbrev_base_type);
+                dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.base_type));
                 // DW.AT.encoding, DW.FORM.data1
                 dbg_info_buffer.appendAssumeCapacity(switch (info.signedness) {
                     .signed => DW.ATE.signed,
@@ -175,7 +175,7 @@ pub const DeclState = struct {
             .Optional => {
                 if (ty.isPtrLikeOptional()) {
                     try dbg_info_buffer.ensureUnusedCapacity(12);
-                    dbg_info_buffer.appendAssumeCapacity(abbrev_base_type);
+                    dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.base_type));
                     // DW.AT.encoding, DW.FORM.data1
                     dbg_info_buffer.appendAssumeCapacity(DW.ATE.address);
                     // DW.AT.byte_size,  DW.FORM.data1
@@ -187,7 +187,7 @@ pub const DeclState = struct {
                     var buf = try arena.create(Type.Payload.ElemType);
                     const payload_ty = ty.optionalChild(buf);
                     // DW.AT.structure_type
-                    try dbg_info_buffer.append(abbrev_struct_type);
+                    try dbg_info_buffer.append(@enumToInt(AbbrevKind.struct_type));
                     // DW.AT.byte_size, DW.FORM.sdata
                     const abi_size = ty.abiSize(target);
                     try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
@@ -195,7 +195,7 @@ pub const DeclState = struct {
                     try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(target)});
                     // DW.AT.member
                     try dbg_info_buffer.ensureUnusedCapacity(7);
-                    dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                    dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.struct_member));
                     // DW.AT.name, DW.FORM.string
                     dbg_info_buffer.appendSliceAssumeCapacity("maybe");
                     dbg_info_buffer.appendAssumeCapacity(0);
@@ -207,7 +207,7 @@ pub const DeclState = struct {
                     try dbg_info_buffer.ensureUnusedCapacity(6);
                     dbg_info_buffer.appendAssumeCapacity(0);
                     // DW.AT.member
-                    dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                    dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.struct_member));
                     // DW.AT.name, DW.FORM.string
                     dbg_info_buffer.appendSliceAssumeCapacity("val");
                     dbg_info_buffer.appendAssumeCapacity(0);
@@ -227,14 +227,14 @@ pub const DeclState = struct {
                     // Slices are structs: struct { .ptr = *, .len = N }
                     // DW.AT.structure_type
                     try dbg_info_buffer.ensureUnusedCapacity(2);
-                    dbg_info_buffer.appendAssumeCapacity(abbrev_struct_type);
+                    dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.struct_type));
                     // DW.AT.byte_size, DW.FORM.sdata
                     dbg_info_buffer.appendAssumeCapacity(@sizeOf(usize) * 2);
                     // DW.AT.name, DW.FORM.string
                     try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(target)});
                     // DW.AT.member
                     try dbg_info_buffer.ensureUnusedCapacity(5);
-                    dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                    dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.struct_member));
                     // DW.AT.name, DW.FORM.string
                     dbg_info_buffer.appendSliceAssumeCapacity("ptr");
                     dbg_info_buffer.appendAssumeCapacity(0);
@@ -248,7 +248,7 @@ pub const DeclState = struct {
                     try dbg_info_buffer.ensureUnusedCapacity(6);
                     dbg_info_buffer.appendAssumeCapacity(0);
                     // DW.AT.member
-                    dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                    dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.struct_member));
                     // DW.AT.name, DW.FORM.string
                     dbg_info_buffer.appendSliceAssumeCapacity("len");
                     dbg_info_buffer.appendAssumeCapacity(0);
@@ -263,7 +263,7 @@ pub const DeclState = struct {
                     dbg_info_buffer.appendAssumeCapacity(0);
                 } else {
                     try dbg_info_buffer.ensureUnusedCapacity(5);
-                    dbg_info_buffer.appendAssumeCapacity(abbrev_ptr_type);
+                    dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.ptr_type));
                     // DW.AT.type, DW.FORM.ref4
                     const index = dbg_info_buffer.items.len;
                     try dbg_info_buffer.resize(index + 4);
@@ -272,7 +272,7 @@ pub const DeclState = struct {
             },
             .Struct => blk: {
                 // DW.AT.structure_type
-                try dbg_info_buffer.append(abbrev_struct_type);
+                try dbg_info_buffer.append(@enumToInt(AbbrevKind.struct_type));
                 // DW.AT.byte_size, DW.FORM.sdata
                 const abi_size = ty.abiSize(target);
                 try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
@@ -285,7 +285,7 @@ pub const DeclState = struct {
                         const fields = ty.tupleFields();
                         for (fields.types) |field, field_index| {
                             // DW.AT.member
-                            try dbg_info_buffer.append(abbrev_struct_member);
+                            try dbg_info_buffer.append(@enumToInt(AbbrevKind.struct_member));
                             // DW.AT.name, DW.FORM.string
                             try dbg_info_buffer.writer().print("{d}\x00", .{field_index});
                             // DW.AT.type, DW.FORM.ref4
@@ -315,7 +315,7 @@ pub const DeclState = struct {
                             const field = fields.get(field_name).?;
                             // DW.AT.member
                             try dbg_info_buffer.ensureUnusedCapacity(field_name.len + 2);
-                            dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                            dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.struct_member));
                             // DW.AT.name, DW.FORM.string
                             dbg_info_buffer.appendSliceAssumeCapacity(field_name);
                             dbg_info_buffer.appendAssumeCapacity(0);
@@ -335,7 +335,7 @@ pub const DeclState = struct {
             },
             .Enum => {
                 // DW.AT.enumeration_type
-                try dbg_info_buffer.append(abbrev_enum_type);
+                try dbg_info_buffer.append(@enumToInt(AbbrevKind.enum_type));
                 // DW.AT.byte_size, DW.FORM.sdata
                 const abi_size = ty.abiSize(target);
                 try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
@@ -355,7 +355,7 @@ pub const DeclState = struct {
                 for (fields.keys()) |field_name, field_i| {
                     // DW.AT.enumerator
                     try dbg_info_buffer.ensureUnusedCapacity(field_name.len + 2 + @sizeOf(u64));
-                    dbg_info_buffer.appendAssumeCapacity(abbrev_enum_variant);
+                    dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.enum_variant));
                     // DW.AT.name, DW.FORM.string
                     dbg_info_buffer.appendSliceAssumeCapacity(field_name);
                     dbg_info_buffer.appendAssumeCapacity(0);
@@ -385,7 +385,7 @@ pub const DeclState = struct {
                 // for untagged unions.
                 if (is_tagged) {
                     // DW.AT.structure_type
-                    try dbg_info_buffer.append(abbrev_struct_type);
+                    try dbg_info_buffer.append(@enumToInt(AbbrevKind.struct_type));
                     // DW.AT.byte_size, DW.FORM.sdata
                     try leb128.writeULEB128(dbg_info_buffer.writer(), layout.abi_size);
                     // DW.AT.name, DW.FORM.string
@@ -395,7 +395,7 @@ pub const DeclState = struct {
 
                     // DW.AT.member
                     try dbg_info_buffer.ensureUnusedCapacity(9);
-                    dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                    dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.struct_member));
                     // DW.AT.name, DW.FORM.string
                     dbg_info_buffer.appendSliceAssumeCapacity("payload");
                     dbg_info_buffer.appendAssumeCapacity(0);
@@ -408,7 +408,7 @@ pub const DeclState = struct {
                 }
 
                 // DW.AT.union_type
-                try dbg_info_buffer.append(abbrev_union_type);
+                try dbg_info_buffer.append(@enumToInt(AbbrevKind.union_type));
                 // DW.AT.byte_size, DW.FORM.sdata,
                 try leb128.writeULEB128(dbg_info_buffer.writer(), layout.payload_size);
                 // DW.AT.name, DW.FORM.string
@@ -423,7 +423,7 @@ pub const DeclState = struct {
                     const field = fields.get(field_name).?;
                     if (!field.ty.hasRuntimeBits()) continue;
                     // DW.AT.member
-                    try dbg_info_buffer.append(abbrev_struct_member);
+                    try dbg_info_buffer.append(@enumToInt(AbbrevKind.struct_member));
                     // DW.AT.name, DW.FORM.string
                     try dbg_info_buffer.writer().print("{s}\x00", .{field_name});
                     // DW.AT.type, DW.FORM.ref4
@@ -439,7 +439,7 @@ pub const DeclState = struct {
                 if (is_tagged) {
                     // DW.AT.member
                     try dbg_info_buffer.ensureUnusedCapacity(5);
-                    dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                    dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.struct_member));
                     // DW.AT.name, DW.FORM.string
                     dbg_info_buffer.appendSliceAssumeCapacity("tag");
                     dbg_info_buffer.appendAssumeCapacity(0);
@@ -471,7 +471,7 @@ pub const DeclState = struct {
                 const payload_off = mem.alignForwardGeneric(u64, error_ty.abiSize(target), abi_align);
 
                 // DW.AT.structure_type
-                try dbg_info_buffer.append(abbrev_struct_type);
+                try dbg_info_buffer.append(@enumToInt(AbbrevKind.struct_type));
                 // DW.AT.byte_size, DW.FORM.sdata
                 try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
                 // DW.AT.name, DW.FORM.string
@@ -480,7 +480,7 @@ pub const DeclState = struct {
 
                 // DW.AT.member
                 try dbg_info_buffer.ensureUnusedCapacity(7);
-                dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.struct_member));
                 // DW.AT.name, DW.FORM.string
                 dbg_info_buffer.appendSliceAssumeCapacity("value");
                 dbg_info_buffer.appendAssumeCapacity(0);
@@ -493,7 +493,7 @@ pub const DeclState = struct {
 
                 // DW.AT.member
                 try dbg_info_buffer.ensureUnusedCapacity(5);
-                dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.struct_member));
                 // DW.AT.name, DW.FORM.string
                 dbg_info_buffer.appendSliceAssumeCapacity("err");
                 dbg_info_buffer.appendAssumeCapacity(0);
@@ -509,7 +509,7 @@ pub const DeclState = struct {
             },
             else => {
                 log.debug("TODO implement .debug_info for type '{}'", .{ty.fmtDebug()});
-                try dbg_info_buffer.append(abbrev_pad1);
+                try dbg_info_buffer.append(@enumToInt(AbbrevKind.pad1));
             },
         }
     }
@@ -550,18 +550,21 @@ pub const SrcFn = struct {
 
 pub const PtrWidth = enum { p32, p64 };
 
-pub const abbrev_compile_unit = 1;
-pub const abbrev_subprogram = 2;
-pub const abbrev_subprogram_retvoid = 3;
-pub const abbrev_base_type = 4;
-pub const abbrev_ptr_type = 5;
-pub const abbrev_struct_type = 6;
-pub const abbrev_struct_member = 7;
-pub const abbrev_enum_type = 8;
-pub const abbrev_enum_variant = 9;
-pub const abbrev_union_type = 10;
-pub const abbrev_pad1 = 11;
-pub const abbrev_parameter = 12;
+pub const AbbrevKind = enum(u8) {
+    compile_unit = 1,
+    subprogram,
+    subprogram_retvoid,
+    base_type,
+    ptr_type,
+    struct_type,
+    struct_member,
+    enum_type,
+    enum_variant,
+    union_type,
+    pad1,
+    parameter,
+    variable,
+};
 
 /// The reloc offset for the virtual address of a function in its Line Number Program.
 /// Size is a virtual address integer.
@@ -670,9 +673,9 @@ pub fn initDeclState(self: *Dwarf, decl: *Module.Decl) !DeclState {
             const fn_ret_type = decl.ty.fnReturnType();
             const fn_ret_has_bits = fn_ret_type.hasRuntimeBits();
             if (fn_ret_has_bits) {
-                dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram);
+                dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.subprogram));
             } else {
-                dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram_retvoid);
+                dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.subprogram_retvoid));
             }
             // These get overwritten after generating the machine code. These values are
             // "relocations" and have to be in this fixed place so that functions can be
@@ -926,7 +929,7 @@ pub fn commitDeclState(
         else => unreachable,
     };
 
-    {
+    if (decl_state.abbrev_table.items.len > 0) {
         // Now we emit the .debug_info types of the Decl. These will count towards the size of
         // the buffer, so we have to do it before computing the offset, and we can't perform the actual
         // relocations yet.
@@ -1244,14 +1247,14 @@ pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void {
     // These are LEB encoded but since the values are all less than 127
     // we can simply append these bytes.
     const abbrev_buf = [_]u8{
-        abbrev_compile_unit, DW.TAG.compile_unit, DW.CHILDREN.yes, // header
-        DW.AT.stmt_list,     DW.FORM.sec_offset,  DW.AT.low_pc,
-        DW.FORM.addr,        DW.AT.high_pc,       DW.FORM.addr,
-        DW.AT.name,          DW.FORM.strp,        DW.AT.comp_dir,
-        DW.FORM.strp,        DW.AT.producer,      DW.FORM.strp,
-        DW.AT.language,      DW.FORM.data2,       0,
+        @enumToInt(AbbrevKind.compile_unit), DW.TAG.compile_unit, DW.CHILDREN.yes, // header
+        DW.AT.stmt_list,                     DW.FORM.sec_offset,  DW.AT.low_pc,
+        DW.FORM.addr,                        DW.AT.high_pc,       DW.FORM.addr,
+        DW.AT.name,                          DW.FORM.strp,        DW.AT.comp_dir,
+        DW.FORM.strp,                        DW.AT.producer,      DW.FORM.strp,
+        DW.AT.language,                      DW.FORM.data2,       0,
         0, // table sentinel
-        abbrev_subprogram,
+        @enumToInt(AbbrevKind.subprogram),
         DW.TAG.subprogram,
         DW.CHILDREN.yes, // header
         DW.AT.low_pc,
@@ -1262,15 +1265,15 @@ pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void {
         DW.FORM.ref4,
         DW.AT.name,
         DW.FORM.string,
-        0,                         0, // table sentinel
-        abbrev_subprogram_retvoid,
+        0,                                         0, // table sentinel
+        @enumToInt(AbbrevKind.subprogram_retvoid),
         DW.TAG.subprogram, DW.CHILDREN.yes, // header
         DW.AT.low_pc,      DW.FORM.addr,
         DW.AT.high_pc,     DW.FORM.data4,
         DW.AT.name,        DW.FORM.string,
         0,
         0, // table sentinel
-        abbrev_base_type,
+        @enumToInt(AbbrevKind.base_type),
         DW.TAG.base_type,
         DW.CHILDREN.no, // header
         DW.AT.encoding,
@@ -1281,14 +1284,14 @@ pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void {
         DW.FORM.string,
         0,
         0, // table sentinel
-        abbrev_ptr_type,
+        @enumToInt(AbbrevKind.ptr_type),
         DW.TAG.pointer_type,
         DW.CHILDREN.no, // header
         DW.AT.type,
         DW.FORM.ref4,
         0,
         0, // table sentinel
-        abbrev_struct_type,
+        @enumToInt(AbbrevKind.struct_type),
         DW.TAG.structure_type,
         DW.CHILDREN.yes, // header
         DW.AT.byte_size,
@@ -1297,7 +1300,7 @@ pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void {
         DW.FORM.string,
         0,
         0, // table sentinel
-        abbrev_struct_member,
+        @enumToInt(AbbrevKind.struct_member),
         DW.TAG.member,
         DW.CHILDREN.no, // header
         DW.AT.name,
@@ -1308,7 +1311,7 @@ pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void {
         DW.FORM.sdata,
         0,
         0, // table sentinel
-        abbrev_enum_type,
+        @enumToInt(AbbrevKind.enum_type),
         DW.TAG.enumeration_type,
         DW.CHILDREN.yes, // header
         DW.AT.byte_size,
@@ -1317,7 +1320,7 @@ pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void {
         DW.FORM.string,
         0,
         0, // table sentinel
-        abbrev_enum_variant,
+        @enumToInt(AbbrevKind.enum_variant),
         DW.TAG.enumerator,
         DW.CHILDREN.no, // header
         DW.AT.name,
@@ -1326,7 +1329,7 @@ pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void {
         DW.FORM.data8,
         0,
         0, // table sentinel
-        abbrev_union_type,
+        @enumToInt(AbbrevKind.union_type),
         DW.TAG.union_type,
         DW.CHILDREN.yes, // header
         DW.AT.byte_size,
@@ -1335,18 +1338,25 @@ pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void {
         DW.FORM.string,
         0,
         0, // table sentinel
-        abbrev_pad1,
+        @enumToInt(AbbrevKind.pad1),
         DW.TAG.unspecified_type,
         DW.CHILDREN.no, // header
         0,
         0, // table sentinel
-        abbrev_parameter,
+        @enumToInt(AbbrevKind.parameter),
         DW.TAG.formal_parameter, DW.CHILDREN.no, // header
         DW.AT.location,          DW.FORM.exprloc,
         DW.AT.type,              DW.FORM.ref4,
         DW.AT.name,              DW.FORM.string,
         0,
         0, // table sentinel
+        @enumToInt(AbbrevKind.variable),
+        DW.TAG.variable, DW.CHILDREN.no, // header
+        DW.AT.location,  DW.FORM.exprloc,
+        DW.AT.type,      DW.FORM.ref4,
+        DW.AT.name,      DW.FORM.string,
+        0,
+        0, // table sentinel
         0,
         0,
         0, // section sentinel
@@ -1459,7 +1469,7 @@ pub fn writeDbgInfoHeader(self: *Dwarf, file: *File, module: *Module, low_pc: u6
     const comp_dir_strp = try self.makeString(module.root_pkg.root_src_directory.path orelse ".");
     const producer_strp = try self.makeString(link.producer_string);
 
-    di_buf.appendAssumeCapacity(abbrev_compile_unit);
+    di_buf.appendAssumeCapacity(@enumToInt(AbbrevKind.compile_unit));
     if (self.tag == .macho) {
         mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), 0); // DW.AT.stmt_list, DW.FORM.sec_offset
         mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), low_pc);
@@ -1606,7 +1616,7 @@ fn pwriteDbgInfoNops(
     const tracy = trace(@src());
     defer tracy.end();
 
-    const page_of_nops = [1]u8{abbrev_pad1} ** 4096;
+    const page_of_nops = [1]u8{@enumToInt(AbbrevKind.pad1)} ** 4096;
     var vecs: [32]std.os.iovec_const = undefined;
     var vec_index: usize = 0;
     {
@@ -1673,7 +1683,7 @@ pub fn writeDbgAranges(self: *Dwarf, file: *File, addr: u64, size: u64) !void {
         .p32 => @as(usize, 4),
         .p64 => 12,
     };
-    const ptr_width_bytes: u8 = self.ptrWidthBytes();
+    const ptr_width_bytes = self.ptrWidthBytes();
 
     // Enough for all the data without resizing. When support for more compilation units
     // is added, the size of this section will become more variable.
@@ -2040,7 +2050,7 @@ fn addDbgInfoErrorSet(
     const target_endian = target.cpu.arch.endian();
 
     // DW.AT.enumeration_type
-    try dbg_info_buffer.append(abbrev_enum_type);
+    try dbg_info_buffer.append(@enumToInt(AbbrevKind.enum_type));
     // DW.AT.byte_size, DW.FORM.sdata
     const abi_size = ty.abiSize(target);
     try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
@@ -2051,7 +2061,7 @@ fn addDbgInfoErrorSet(
     // DW.AT.enumerator
     const no_error = "(no error)";
     try dbg_info_buffer.ensureUnusedCapacity(no_error.len + 2 + @sizeOf(u64));
-    dbg_info_buffer.appendAssumeCapacity(abbrev_enum_variant);
+    dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.enum_variant));
     // DW.AT.name, DW.FORM.string
     dbg_info_buffer.appendSliceAssumeCapacity(no_error);
     dbg_info_buffer.appendAssumeCapacity(0);
@@ -2063,7 +2073,7 @@ fn addDbgInfoErrorSet(
         const kv = module.getErrorValue(error_name) catch unreachable;
         // DW.AT.enumerator
         try dbg_info_buffer.ensureUnusedCapacity(error_name.len + 2 + @sizeOf(u64));
-        dbg_info_buffer.appendAssumeCapacity(abbrev_enum_variant);
+        dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.enum_variant));
         // DW.AT.name, DW.FORM.string
         dbg_info_buffer.appendSliceAssumeCapacity(error_name);
         dbg_info_buffer.appendAssumeCapacity(0);