Commit f5d9160f1b

Jakub Konka <kubkon@jakubkonka.com>
2022-03-30 11:03:50
dwarf: pass DeclState around instead of storing a temp global in Dwarf
Avoids many pitfalls connected with premature/early return in case there are errors with Decl, etc. This is effectively bringing back the old design however in a much nicer packaging, where every mechanism related to tracking Decl's debug info is now nicely wrapped in a single struct (aka the `DeclState`). This includes relocation table, type arena, etc. It is now the caller's responsibility to deinit the state (so that no memory is leaked) after `Decl` has been analysed (or errored out). The caller here is typically a linker such as `Elf` or `MachO`.
1 parent b73cf97
Changed files (9)
src/arch/aarch64/Emit.zig
@@ -390,7 +390,7 @@ fn dbgAdvancePCAndLine(self: *Emit, line: u32, column: u32) !void {
             // TODO Look into using the DWARF special opcodes to compress this data.
             // It lets you emit single-byte opcodes that add different numbers to
             // both the PC and the line number at the same time.
-            const dbg_line = dw.getDeclDebugLineBuffer();
+            const dbg_line = &dw.dbg_line;
             try dbg_line.ensureUnusedCapacity(11);
             dbg_line.appendAssumeCapacity(DW.LNS.advance_pc);
             leb128.writeULEB128(dbg_line.writer(), delta_pc) catch unreachable;
@@ -588,7 +588,7 @@ fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) !void {
 fn mirDebugPrologueEnd(self: *Emit) !void {
     switch (self.debug_output) {
         .dwarf => |dw| {
-            try dw.getDeclDebugLineBuffer().append(DW.LNS.set_prologue_end);
+            try dw.dbg_line.append(DW.LNS.set_prologue_end);
             try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column);
         },
         .plan9 => {},
@@ -599,7 +599,7 @@ fn mirDebugPrologueEnd(self: *Emit) !void {
 fn mirDebugEpilogueBegin(self: *Emit) !void {
     switch (self.debug_output) {
         .dwarf => |dw| {
-            try dw.getDeclDebugLineBuffer().append(DW.LNS.set_epilogue_begin);
+            try dw.dbg_line.append(DW.LNS.set_epilogue_begin);
             try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column);
         },
         .plan9 => {},
src/arch/arm/Emit.zig
@@ -332,7 +332,7 @@ fn dbgAdvancePCAndLine(self: *Emit, line: u32, column: u32) !void {
             // TODO Look into using the DWARF special opcodes to compress this data.
             // It lets you emit single-byte opcodes that add different numbers to
             // both the PC and the line number at the same time.
-            const dbg_line = dw.getDeclDebugLineBuffer();
+            const dbg_line = &dw.dbg_line;
             try dbg_line.ensureUnusedCapacity(11);
             dbg_line.appendAssumeCapacity(DW.LNS.advance_pc);
             leb128.writeULEB128(dbg_line.writer(), delta_pc) catch unreachable;
@@ -382,7 +382,7 @@ fn addDbgInfoTypeReloc(self: *Emit, ty: Type) !void {
     switch (self.debug_output) {
         .dwarf => |dw| {
             assert(ty.hasRuntimeBits());
-            const dbg_info = dw.getDeclDebugInfoBuffer();
+            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) {
@@ -409,7 +409,7 @@ fn genArgDbgInfo(self: *Emit, inst: Air.Inst.Index, arg_index: u32) !void {
         .register => |reg| {
             switch (self.debug_output) {
                 .dwarf => |dw| {
-                    const dbg_info = dw.getDeclDebugInfoBuffer();
+                    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
@@ -442,7 +442,7 @@ fn genArgDbgInfo(self: *Emit, inst: Air.Inst.Index, arg_index: u32) !void {
                         else => unreachable,
                     };
 
-                    const dbg_info = dw.getDeclDebugInfoBuffer();
+                    const dbg_info = &dw.dbg_info;
                     try dbg_info.append(link.File.Dwarf.abbrev_parameter);
 
                     // Get length of the LEB128 stack offset
@@ -560,7 +560,7 @@ fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) !void {
 fn mirDebugPrologueEnd(emit: *Emit) !void {
     switch (emit.debug_output) {
         .dwarf => |dw| {
-            try dw.getDeclDebugLineBuffer().append(DW.LNS.set_prologue_end);
+            try dw.dbg_line.append(DW.LNS.set_prologue_end);
             try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
         },
         .plan9 => {},
@@ -571,7 +571,7 @@ fn mirDebugPrologueEnd(emit: *Emit) !void {
 fn mirDebugEpilogueBegin(emit: *Emit) !void {
     switch (emit.debug_output) {
         .dwarf => |dw| {
-            try dw.getDeclDebugLineBuffer().append(DW.LNS.set_epilogue_begin);
+            try dw.dbg_line.append(DW.LNS.set_epilogue_begin);
             try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
         },
         .plan9 => {},
src/arch/riscv64/CodeGen.zig
@@ -749,7 +749,7 @@ fn addDbgInfoTypeReloc(self: *Self, ty: Type) !void {
     switch (self.debug_output) {
         .dwarf => |dw| {
             assert(ty.hasRuntimeBits());
-            const dbg_info = dw.getDeclDebugInfoBuffer();
+            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) {
@@ -1572,7 +1572,7 @@ fn genArgDbgInfo(self: *Self, inst: Air.Inst.Index, mcv: MCValue, arg_index: u32
         .register => |reg| {
             switch (self.debug_output) {
                 .dwarf => |dw| {
-                    const dbg_info = dw.getDeclDebugInfoBuffer();
+                    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
src/arch/riscv64/Emit.zig
@@ -93,7 +93,7 @@ fn dbgAdvancePCAndLine(self: *Emit, line: u32, column: u32) !void {
             // TODO Look into using the DWARF special opcodes to compress this data.
             // It lets you emit single-byte opcodes that add different numbers to
             // both the PC and the line number at the same time.
-            const dbg_line = dw.getDeclDebugLineBuffer();
+            const dbg_line = &dw.dbg_line;
             try dbg_line.ensureUnusedCapacity(11);
             dbg_line.appendAssumeCapacity(DW.LNS.advance_pc);
             leb128.writeULEB128(dbg_line.writer(), delta_pc) catch unreachable;
@@ -184,7 +184,7 @@ fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) !void {
 fn mirDebugPrologueEnd(self: *Emit) !void {
     switch (self.debug_output) {
         .dwarf => |dw| {
-            try dw.getDeclDebugLineBuffer().append(DW.LNS.set_prologue_end);
+            try dw.dbg_line.append(DW.LNS.set_prologue_end);
             try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column);
         },
         .plan9 => {},
@@ -195,7 +195,7 @@ fn mirDebugPrologueEnd(self: *Emit) !void {
 fn mirDebugEpilogueBegin(self: *Emit) !void {
     switch (self.debug_output) {
         .dwarf => |dw| {
-            try dw.getDeclDebugLineBuffer().append(DW.LNS.set_epilogue_begin);
+            try dw.dbg_line.append(DW.LNS.set_epilogue_begin);
             try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column);
         },
         .plan9 => {},
src/arch/x86_64/Emit.zig
@@ -977,7 +977,7 @@ fn dbgAdvancePCAndLine(emit: *Emit, line: u32, column: u32) InnerError!void {
             // TODO Look into using the DWARF special opcodes to compress this data.
             // It lets you emit single-byte opcodes that add different numbers to
             // both the PC and the line number at the same time.
-            const dbg_line = dw.getDeclDebugLineBuffer();
+            const dbg_line = &dw.dbg_line;
             try dbg_line.ensureUnusedCapacity(11);
             dbg_line.appendAssumeCapacity(DW.LNS.advance_pc);
             leb128.writeULEB128(dbg_line.writer(), delta_pc) catch unreachable;
@@ -1034,7 +1034,7 @@ fn mirDbgPrologueEnd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
     assert(tag == .dbg_prologue_end);
     switch (emit.debug_output) {
         .dwarf => |dw| {
-            try dw.getDeclDebugLineBuffer().append(DW.LNS.set_prologue_end);
+            try dw.dbg_line.append(DW.LNS.set_prologue_end);
             log.debug("mirDbgPrologueEnd (line={d}, col={d})", .{ emit.prev_di_line, emit.prev_di_column });
             try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
         },
@@ -1048,7 +1048,7 @@ fn mirDbgEpilogueBegin(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
     assert(tag == .dbg_epilogue_begin);
     switch (emit.debug_output) {
         .dwarf => |dw| {
-            try dw.getDeclDebugLineBuffer().append(DW.LNS.set_epilogue_begin);
+            try dw.dbg_line.append(DW.LNS.set_epilogue_begin);
             log.debug("mirDbgEpilogueBegin (line={d}, col={d})", .{ emit.prev_di_line, emit.prev_di_column });
             try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
         },
@@ -1075,7 +1075,7 @@ fn genArgDbgInfo(emit: *Emit, inst: Air.Inst.Index, mcv: MCValue, max_stack: u32
         .register => |reg| {
             switch (emit.debug_output) {
                 .dwarf => |dw| {
-                    const dbg_info = dw.getDeclDebugInfoBuffer();
+                    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
@@ -1099,7 +1099,7 @@ fn genArgDbgInfo(emit: *Emit, inst: Air.Inst.Index, mcv: MCValue, max_stack: u32
                     // 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.getDeclDebugInfoBuffer();
+                    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;
@@ -1128,7 +1128,7 @@ fn addDbgInfoTypeReloc(emit: *Emit, ty: Type) !void {
     switch (emit.debug_output) {
         .dwarf => |dw| {
             assert(ty.hasRuntimeBits());
-            const dbg_info = dw.getDeclDebugInfoBuffer();
+            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) {
src/link/Dwarf.zig
@@ -43,11 +43,6 @@ abbrev_table_offset: ?u64 = null,
 /// Table of debug symbol names.
 strtab: std.ArrayListUnmanaged(u8) = .{},
 
-/// Lives only as long as the analysed Decl.
-/// Allocated with `initDeclState`.
-/// Freed with `commitDeclState`.
-decl_state: ?DeclState = null,
-
 /// List of atoms that are owned directly by the DWARF module.
 /// TODO convert links in DebugInfoAtom into indices and make
 /// sure every atom is owned by this module.
@@ -71,6 +66,8 @@ pub const Atom = struct {
 /// and a set of relocations that will be resolved once this
 /// Decl's inner Atom is assigned an offset within the DWARF section.
 pub const DeclState = struct {
+    gpa: Allocator,
+    target: std.Target,
     dbg_line: std.ArrayList(u8),
     dbg_info: std.ArrayList(u8),
     abbrev_type_arena: std.heap.ArenaAllocator,
@@ -83,21 +80,438 @@ pub const DeclState = struct {
     ) = .{},
     abbrev_relocs: std.ArrayListUnmanaged(AbbrevRelocation) = .{},
 
-    fn init(gpa: Allocator) DeclState {
+    fn init(gpa: Allocator, target: std.Target) DeclState {
         return .{
+            .gpa = gpa,
+            .target = target,
             .dbg_line = std.ArrayList(u8).init(gpa),
             .dbg_info = std.ArrayList(u8).init(gpa),
             .abbrev_type_arena = std.heap.ArenaAllocator.init(gpa),
         };
     }
 
-    pub fn deinit(self: *DeclState, gpa: Allocator) void {
+    pub fn deinit(self: *DeclState) void {
         self.dbg_line.deinit();
         self.dbg_info.deinit();
         self.abbrev_type_arena.deinit();
-        self.abbrev_table.deinit(gpa);
-        self.abbrev_resolver.deinit(gpa);
-        self.abbrev_relocs.deinit(gpa);
+        self.abbrev_table.deinit(self.gpa);
+        self.abbrev_resolver.deinit(self.gpa);
+        self.abbrev_relocs.deinit(self.gpa);
+    }
+
+    pub fn addTypeReloc(
+        self: *DeclState,
+        atom: *const Atom,
+        ty: Type,
+        offset: u32,
+        addend: ?u32,
+    ) !void {
+        const resolv = self.abbrev_resolver.getContext(ty, .{
+            .target = self.target,
+        }) orelse blk: {
+            const sym_index = @intCast(u32, self.abbrev_table.items.len);
+            try self.abbrev_table.append(self.gpa, .{
+                .atom = atom,
+                .@"type" = ty,
+                .offset = undefined,
+            });
+            log.debug("@{d}: {}", .{ sym_index, ty.fmtDebug() });
+            try self.abbrev_resolver.putNoClobberContext(self.gpa, ty, sym_index, .{
+                .target = self.target,
+            });
+            break :blk self.abbrev_resolver.getContext(ty, .{
+                .target = self.target,
+            }).?;
+        };
+        const add: u32 = addend orelse 0;
+
+        log.debug("{x}: @{d} + {x}", .{ offset, resolv, add });
+        try self.abbrev_relocs.append(self.gpa, .{
+            .target = resolv,
+            .atom = atom,
+            .offset = offset,
+            .addend = add,
+        });
+    }
+
+    fn addDbgInfoType(
+        self: *DeclState,
+        module: *Module,
+        atom: *Atom,
+        ty: Type,
+    ) error{OutOfMemory}!void {
+        const arena = self.abbrev_type_arena.allocator();
+        const dbg_info_buffer = &self.dbg_info;
+        const target = self.target;
+        const target_endian = self.target.cpu.arch.endian();
+
+        switch (ty.zigTypeTag()) {
+            .NoReturn => unreachable,
+            .Void => {
+                try dbg_info_buffer.append(abbrev_pad1);
+            },
+            .Bool => {
+                try dbg_info_buffer.appendSlice(&[_]u8{
+                    abbrev_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
+                });
+            },
+            .Int => {
+                const info = ty.intInfo(target);
+                try dbg_info_buffer.ensureUnusedCapacity(12);
+                dbg_info_buffer.appendAssumeCapacity(abbrev_base_type);
+                // DW.AT.encoding, DW.FORM.data1
+                dbg_info_buffer.appendAssumeCapacity(switch (info.signedness) {
+                    .signed => DW.ATE.signed,
+                    .unsigned => DW.ATE.unsigned,
+                });
+                // DW.AT.byte_size,  DW.FORM.data1
+                dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(target)));
+                // DW.AT.name,  DW.FORM.string
+                try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(target)});
+            },
+            .Optional => {
+                if (ty.isPtrLikeOptional()) {
+                    try dbg_info_buffer.ensureUnusedCapacity(12);
+                    dbg_info_buffer.appendAssumeCapacity(abbrev_base_type);
+                    // DW.AT.encoding, DW.FORM.data1
+                    dbg_info_buffer.appendAssumeCapacity(DW.ATE.address);
+                    // DW.AT.byte_size,  DW.FORM.data1
+                    dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(target)));
+                    // DW.AT.name,  DW.FORM.string
+                    try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(target)});
+                } else {
+                    // Non-pointer optionals are structs: struct { .maybe = *, .val = * }
+                    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);
+                    // DW.AT.byte_size, DW.FORM.sdata
+                    const abi_size = ty.abiSize(target);
+                    try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
+                    // DW.AT.name, DW.FORM.string
+                    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);
+                    // DW.AT.name, DW.FORM.string
+                    dbg_info_buffer.appendSliceAssumeCapacity("maybe");
+                    dbg_info_buffer.appendAssumeCapacity(0);
+                    // DW.AT.type, DW.FORM.ref4
+                    var index = dbg_info_buffer.items.len;
+                    try dbg_info_buffer.resize(index + 4);
+                    try self.addTypeReloc(atom, Type.bool, @intCast(u32, index), null);
+                    // DW.AT.data_member_location, DW.FORM.sdata
+                    try dbg_info_buffer.ensureUnusedCapacity(6);
+                    dbg_info_buffer.appendAssumeCapacity(0);
+                    // DW.AT.member
+                    dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                    // DW.AT.name, DW.FORM.string
+                    dbg_info_buffer.appendSliceAssumeCapacity("val");
+                    dbg_info_buffer.appendAssumeCapacity(0);
+                    // DW.AT.type, DW.FORM.ref4
+                    index = dbg_info_buffer.items.len;
+                    try dbg_info_buffer.resize(index + 4);
+                    try self.addTypeReloc(atom, payload_ty, @intCast(u32, index), null);
+                    // DW.AT.data_member_location, DW.FORM.sdata
+                    const offset = abi_size - payload_ty.abiSize(target);
+                    try leb128.writeULEB128(dbg_info_buffer.writer(), offset);
+                    // DW.AT.structure_type delimit children
+                    try dbg_info_buffer.append(0);
+                }
+            },
+            .Pointer => {
+                if (ty.isSlice()) {
+                    // Slices are structs: struct { .ptr = *, .len = N }
+                    // DW.AT.structure_type
+                    try dbg_info_buffer.ensureUnusedCapacity(2);
+                    dbg_info_buffer.appendAssumeCapacity(abbrev_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);
+                    // DW.AT.name, DW.FORM.string
+                    dbg_info_buffer.appendSliceAssumeCapacity("ptr");
+                    dbg_info_buffer.appendAssumeCapacity(0);
+                    // DW.AT.type, DW.FORM.ref4
+                    var index = dbg_info_buffer.items.len;
+                    try dbg_info_buffer.resize(index + 4);
+                    var buf = try arena.create(Type.SlicePtrFieldTypeBuffer);
+                    const ptr_ty = ty.slicePtrFieldType(buf);
+                    try self.addTypeReloc(atom, ptr_ty, @intCast(u32, index), null);
+                    // DW.AT.data_member_location, DW.FORM.sdata
+                    try dbg_info_buffer.ensureUnusedCapacity(6);
+                    dbg_info_buffer.appendAssumeCapacity(0);
+                    // DW.AT.member
+                    dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                    // DW.AT.name, DW.FORM.string
+                    dbg_info_buffer.appendSliceAssumeCapacity("len");
+                    dbg_info_buffer.appendAssumeCapacity(0);
+                    // DW.AT.type, DW.FORM.ref4
+                    index = dbg_info_buffer.items.len;
+                    try dbg_info_buffer.resize(index + 4);
+                    try self.addTypeReloc(atom, Type.usize, @intCast(u32, index), null);
+                    // DW.AT.data_member_location, DW.FORM.sdata
+                    try dbg_info_buffer.ensureUnusedCapacity(2);
+                    dbg_info_buffer.appendAssumeCapacity(@sizeOf(usize));
+                    // DW.AT.structure_type delimit children
+                    dbg_info_buffer.appendAssumeCapacity(0);
+                } else {
+                    try dbg_info_buffer.ensureUnusedCapacity(5);
+                    dbg_info_buffer.appendAssumeCapacity(abbrev_ptr_type);
+                    // DW.AT.type, DW.FORM.ref4
+                    const index = dbg_info_buffer.items.len;
+                    try dbg_info_buffer.resize(index + 4);
+                    try self.addTypeReloc(atom, ty.childType(), @intCast(u32, index), null);
+                }
+            },
+            .Struct => blk: {
+                // DW.AT.structure_type
+                try dbg_info_buffer.append(abbrev_struct_type);
+                // DW.AT.byte_size, DW.FORM.sdata
+                const abi_size = ty.abiSize(target);
+                try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
+
+                switch (ty.tag()) {
+                    .tuple, .anon_struct => {
+                        // DW.AT.name, DW.FORM.string
+                        try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(target)});
+
+                        const fields = ty.tupleFields();
+                        for (fields.types) |field, field_index| {
+                            // DW.AT.member
+                            try dbg_info_buffer.append(abbrev_struct_member);
+                            // DW.AT.name, DW.FORM.string
+                            try dbg_info_buffer.writer().print("{d}\x00", .{field_index});
+                            // DW.AT.type, DW.FORM.ref4
+                            var index = dbg_info_buffer.items.len;
+                            try dbg_info_buffer.resize(index + 4);
+                            try self.addTypeReloc(atom, field, @intCast(u32, index), null);
+                            // DW.AT.data_member_location, DW.FORM.sdata
+                            const field_off = ty.structFieldOffset(field_index, target);
+                            try leb128.writeULEB128(dbg_info_buffer.writer(), field_off);
+                        }
+                    },
+                    else => {
+                        // DW.AT.name, DW.FORM.string
+                        const struct_name = try ty.nameAllocArena(arena, target);
+                        try dbg_info_buffer.ensureUnusedCapacity(struct_name.len + 1);
+                        dbg_info_buffer.appendSliceAssumeCapacity(struct_name);
+                        dbg_info_buffer.appendAssumeCapacity(0);
+
+                        const struct_obj = ty.castTag(.@"struct").?.data;
+                        if (struct_obj.layout == .Packed) {
+                            log.debug("TODO implement .debug_info for packed structs", .{});
+                            break :blk;
+                        }
+
+                        const fields = ty.structFields();
+                        for (fields.keys()) |field_name, field_index| {
+                            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);
+                            // DW.AT.name, DW.FORM.string
+                            dbg_info_buffer.appendSliceAssumeCapacity(field_name);
+                            dbg_info_buffer.appendAssumeCapacity(0);
+                            // DW.AT.type, DW.FORM.ref4
+                            var index = dbg_info_buffer.items.len;
+                            try dbg_info_buffer.resize(index + 4);
+                            try self.addTypeReloc(atom, field.ty, @intCast(u32, index), null);
+                            // DW.AT.data_member_location, DW.FORM.sdata
+                            const field_off = ty.structFieldOffset(field_index, target);
+                            try leb128.writeULEB128(dbg_info_buffer.writer(), field_off);
+                        }
+                    },
+                }
+
+                // DW.AT.structure_type delimit children
+                try dbg_info_buffer.append(0);
+            },
+            .Enum => {
+                // DW.AT.enumeration_type
+                try dbg_info_buffer.append(abbrev_enum_type);
+                // DW.AT.byte_size, DW.FORM.sdata
+                const abi_size = ty.abiSize(target);
+                try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
+                // DW.AT.name, DW.FORM.string
+                const enum_name = try ty.nameAllocArena(arena, target);
+                try dbg_info_buffer.ensureUnusedCapacity(enum_name.len + 1);
+                dbg_info_buffer.appendSliceAssumeCapacity(enum_name);
+                dbg_info_buffer.appendAssumeCapacity(0);
+
+                const fields = ty.enumFields();
+                const values: ?Module.EnumFull.ValueMap = switch (ty.tag()) {
+                    .enum_full, .enum_nonexhaustive => ty.cast(Type.Payload.EnumFull).?.data.values,
+                    .enum_simple => null,
+                    .enum_numbered => ty.castTag(.enum_numbered).?.data.values,
+                    else => unreachable,
+                };
+                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);
+                    // DW.AT.name, DW.FORM.string
+                    dbg_info_buffer.appendSliceAssumeCapacity(field_name);
+                    dbg_info_buffer.appendAssumeCapacity(0);
+                    // DW.AT.const_value, DW.FORM.data8
+                    const value: u64 = if (values) |vals| value: {
+                        if (vals.count() == 0) break :value @intCast(u64, field_i); // auto-numbered
+                        const value = vals.keys()[field_i];
+                        var int_buffer: Value.Payload.U64 = undefined;
+                        break :value value.enumToInt(ty, &int_buffer).toUnsignedInt(target);
+                    } else @intCast(u64, field_i);
+                    mem.writeInt(u64, dbg_info_buffer.addManyAsArrayAssumeCapacity(8), value, target_endian);
+                }
+
+                // DW.AT.enumeration_type delimit children
+                try dbg_info_buffer.append(0);
+            },
+            .Union => {
+                const layout = ty.unionGetLayout(target);
+                const union_obj = ty.cast(Type.Payload.Union).?.data;
+                const payload_offset = if (layout.tag_align >= layout.payload_align) layout.tag_size else 0;
+                const tag_offset = if (layout.tag_align >= layout.payload_align) 0 else layout.payload_size;
+                const is_tagged = layout.tag_size > 0;
+                const union_name = try ty.nameAllocArena(arena, target);
+
+                // TODO this is temporary to match current state of unions in Zig - we don't yet have
+                // safety checks implemented meaning the implicit tag is not yet stored and generated
+                // for untagged unions.
+                if (is_tagged) {
+                    // DW.AT.structure_type
+                    try dbg_info_buffer.append(abbrev_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
+                    try dbg_info_buffer.ensureUnusedCapacity(union_name.len + 1);
+                    dbg_info_buffer.appendSliceAssumeCapacity(union_name);
+                    dbg_info_buffer.appendAssumeCapacity(0);
+
+                    // DW.AT.member
+                    try dbg_info_buffer.ensureUnusedCapacity(9);
+                    dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                    // DW.AT.name, DW.FORM.string
+                    dbg_info_buffer.appendSliceAssumeCapacity("payload");
+                    dbg_info_buffer.appendAssumeCapacity(0);
+                    // DW.AT.type, DW.FORM.ref4
+                    const inner_union_index = dbg_info_buffer.items.len;
+                    try dbg_info_buffer.resize(inner_union_index + 4);
+                    try self.addTypeReloc(atom, ty, @intCast(u32, inner_union_index), 5);
+                    // DW.AT.data_member_location, DW.FORM.sdata
+                    try leb128.writeULEB128(dbg_info_buffer.writer(), payload_offset);
+                }
+
+                // DW.AT.union_type
+                try dbg_info_buffer.append(abbrev_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
+                if (is_tagged) {
+                    try dbg_info_buffer.writer().print("AnonUnion\x00", .{});
+                } else {
+                    try dbg_info_buffer.writer().print("{s}\x00", .{union_name});
+                }
+
+                const fields = ty.unionFields();
+                for (fields.keys()) |field_name| {
+                    const field = fields.get(field_name).?;
+                    if (!field.ty.hasRuntimeBits()) continue;
+                    // DW.AT.member
+                    try dbg_info_buffer.append(abbrev_struct_member);
+                    // DW.AT.name, DW.FORM.string
+                    try dbg_info_buffer.writer().print("{s}\x00", .{field_name});
+                    // DW.AT.type, DW.FORM.ref4
+                    const index = dbg_info_buffer.items.len;
+                    try dbg_info_buffer.resize(index + 4);
+                    try self.addTypeReloc(atom, field.ty, @intCast(u32, index), null);
+                    // DW.AT.data_member_location, DW.FORM.sdata
+                    try dbg_info_buffer.append(0);
+                }
+                // DW.AT.union_type delimit children
+                try dbg_info_buffer.append(0);
+
+                if (is_tagged) {
+                    // DW.AT.member
+                    try dbg_info_buffer.ensureUnusedCapacity(5);
+                    dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                    // DW.AT.name, DW.FORM.string
+                    dbg_info_buffer.appendSliceAssumeCapacity("tag");
+                    dbg_info_buffer.appendAssumeCapacity(0);
+                    // DW.AT.type, DW.FORM.ref4
+                    const index = dbg_info_buffer.items.len;
+                    try dbg_info_buffer.resize(index + 4);
+                    try self.addTypeReloc(atom, union_obj.tag_ty, @intCast(u32, index), null);
+                    // DW.AT.data_member_location, DW.FORM.sdata
+                    try leb128.writeULEB128(dbg_info_buffer.writer(), tag_offset);
+
+                    // DW.AT.structure_type delimit children
+                    try dbg_info_buffer.append(0);
+                }
+            },
+            .ErrorSet => {
+                try addDbgInfoErrorSet(
+                    self.abbrev_type_arena.allocator(),
+                    module,
+                    ty,
+                    self.target,
+                    &self.dbg_info,
+                );
+            },
+            .ErrorUnion => {
+                const error_ty = ty.errorUnionSet();
+                const payload_ty = ty.errorUnionPayload();
+                const abi_size = ty.abiSize(target);
+                const abi_align = ty.abiAlignment(target);
+                const payload_off = mem.alignForwardGeneric(u64, error_ty.abiSize(target), abi_align);
+
+                // DW.AT.structure_type
+                try dbg_info_buffer.append(abbrev_struct_type);
+                // DW.AT.byte_size, DW.FORM.sdata
+                try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
+                // DW.AT.name, DW.FORM.string
+                const name = try ty.nameAllocArena(arena, target);
+                try dbg_info_buffer.writer().print("{s}\x00", .{name});
+
+                // DW.AT.member
+                try dbg_info_buffer.ensureUnusedCapacity(7);
+                dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                // DW.AT.name, DW.FORM.string
+                dbg_info_buffer.appendSliceAssumeCapacity("value");
+                dbg_info_buffer.appendAssumeCapacity(0);
+                // DW.AT.type, DW.FORM.ref4
+                var index = dbg_info_buffer.items.len;
+                try dbg_info_buffer.resize(index + 4);
+                try self.addTypeReloc(atom, payload_ty, @intCast(u32, index), null);
+                // DW.AT.data_member_location, DW.FORM.sdata
+                try leb128.writeULEB128(dbg_info_buffer.writer(), payload_off);
+
+                // DW.AT.member
+                try dbg_info_buffer.ensureUnusedCapacity(5);
+                dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                // DW.AT.name, DW.FORM.string
+                dbg_info_buffer.appendSliceAssumeCapacity("err");
+                dbg_info_buffer.appendAssumeCapacity(0);
+                // DW.AT.type, DW.FORM.ref4
+                index = dbg_info_buffer.items.len;
+                try dbg_info_buffer.resize(index + 4);
+                try self.addTypeReloc(atom, error_ty, @intCast(u32, index), null);
+                // DW.AT.data_member_location, DW.FORM.sdata
+                try dbg_info_buffer.append(0);
+
+                // DW.AT.structure_type delimit children
+                try dbg_info_buffer.append(0);
+            },
+            else => {
+                log.debug("TODO implement .debug_info for type '{}'", .{ty.fmtDebug()});
+                try dbg_info_buffer.append(abbrev_pad1);
+            },
+        }
     }
 };
 
@@ -191,7 +605,7 @@ pub fn deinit(self: *Dwarf) void {
 
 /// Initializes Decl's state and its matching output buffers.
 /// Call this before `commitDeclState`.
-pub fn initDeclState(self: *Dwarf, decl: *Module.Decl) !void {
+pub fn initDeclState(self: *Dwarf, decl: *Module.Decl) !DeclState {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -201,10 +615,10 @@ pub fn initDeclState(self: *Dwarf, decl: *Module.Decl) !void {
     log.debug("initDeclState {s}{*}", .{ decl_name, decl });
 
     const gpa = self.allocator;
-    assert(self.decl_state == null);
-    self.decl_state = DeclState.init(gpa);
-    const dbg_line_buffer = &self.decl_state.?.dbg_line;
-    const dbg_info_buffer = &self.decl_state.?.dbg_info;
+    var decl_state = DeclState.init(gpa, self.target);
+    errdefer decl_state.deinit();
+    const dbg_line_buffer = &decl_state.dbg_line;
+    const dbg_info_buffer = &decl_state.dbg_info;
 
     assert(decl.has_tv);
 
@@ -274,7 +688,12 @@ pub fn initDeclState(self: *Dwarf, decl: *Module.Decl) !void {
                     .macho => &decl.link.macho.dbg_info_atom,
                     else => unreachable,
                 };
-                try self.addTypeReloc(atom, fn_ret_type, @intCast(u32, dbg_info_buffer.items.len), null);
+                try decl_state.addTypeReloc(
+                    atom,
+                    fn_ret_type,
+                    @intCast(u32, dbg_info_buffer.items.len),
+                    null,
+                );
                 dbg_info_buffer.items.len += 4; // DW.AT.type,  DW.FORM.ref4
             }
 
@@ -285,6 +704,8 @@ pub fn initDeclState(self: *Dwarf, decl: *Module.Decl) !void {
             // TODO implement .debug_info for global variables
         },
     }
+
+    return decl_state;
 }
 
 pub fn commitDeclState(
@@ -294,19 +715,14 @@ pub fn commitDeclState(
     decl: *Module.Decl,
     sym_addr: u64,
     sym_size: u64,
+    decl_state: *DeclState,
 ) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    assert(self.decl_state != null); // Caller forgot to call `initDeclState`
-    defer {
-        self.decl_state.?.deinit(self.allocator);
-        self.decl_state = null;
-    }
-
     const gpa = self.allocator;
-    var dbg_line_buffer = &self.decl_state.?.dbg_line;
-    var dbg_info_buffer = &self.decl_state.?.dbg_info;
+    var dbg_line_buffer = &decl_state.dbg_line;
+    var dbg_info_buffer = &decl_state.dbg_info;
 
     const target_endian = self.target.cpu.arch.endian();
 
@@ -509,7 +925,6 @@ pub fn commitDeclState(
         .macho => &decl.link.macho.dbg_info_atom,
         else => unreachable,
     };
-    const decl_state = &self.decl_state.?;
 
     {
         // Now we emit the .debug_info types of the Decl. These will count towards the size of
@@ -532,7 +947,7 @@ pub fn commitDeclState(
             if (deferred) continue;
 
             symbol.offset = @intCast(u32, dbg_info_buffer.items.len);
-            try self.addDbgInfoType(decl_state.abbrev_type_arena.allocator(), module, atom, ty, dbg_info_buffer);
+            try decl_state.addDbgInfoType(module, atom, ty);
         }
     }
 
@@ -825,414 +1240,6 @@ pub fn freeDecl(self: *Dwarf, decl: *Module.Decl) void {
     }
 }
 
-/// Asserts the type has codegen bits.
-fn addDbgInfoType(
-    self: *Dwarf,
-    arena: Allocator,
-    module: *Module,
-    atom: *Atom,
-    ty: Type,
-    dbg_info_buffer: *std.ArrayList(u8),
-) error{OutOfMemory}!void {
-    const target = self.target;
-    const target_endian = self.target.cpu.arch.endian();
-
-    switch (ty.zigTypeTag()) {
-        .NoReturn => unreachable,
-        .Void => {
-            try dbg_info_buffer.append(abbrev_pad1);
-        },
-        .Bool => {
-            try dbg_info_buffer.appendSlice(&[_]u8{
-                abbrev_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
-            });
-        },
-        .Int => {
-            const info = ty.intInfo(target);
-            try dbg_info_buffer.ensureUnusedCapacity(12);
-            dbg_info_buffer.appendAssumeCapacity(abbrev_base_type);
-            // DW.AT.encoding, DW.FORM.data1
-            dbg_info_buffer.appendAssumeCapacity(switch (info.signedness) {
-                .signed => DW.ATE.signed,
-                .unsigned => DW.ATE.unsigned,
-            });
-            // DW.AT.byte_size,  DW.FORM.data1
-            dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(target)));
-            // DW.AT.name,  DW.FORM.string
-            try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(target)});
-        },
-        .Optional => {
-            if (ty.isPtrLikeOptional()) {
-                try dbg_info_buffer.ensureUnusedCapacity(12);
-                dbg_info_buffer.appendAssumeCapacity(abbrev_base_type);
-                // DW.AT.encoding, DW.FORM.data1
-                dbg_info_buffer.appendAssumeCapacity(DW.ATE.address);
-                // DW.AT.byte_size,  DW.FORM.data1
-                dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(target)));
-                // DW.AT.name,  DW.FORM.string
-                try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(target)});
-            } else {
-                // Non-pointer optionals are structs: struct { .maybe = *, .val = * }
-                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);
-                // DW.AT.byte_size, DW.FORM.sdata
-                const abi_size = ty.abiSize(target);
-                try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
-                // DW.AT.name, DW.FORM.string
-                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);
-                // DW.AT.name, DW.FORM.string
-                dbg_info_buffer.appendSliceAssumeCapacity("maybe");
-                dbg_info_buffer.appendAssumeCapacity(0);
-                // DW.AT.type, DW.FORM.ref4
-                var index = dbg_info_buffer.items.len;
-                try dbg_info_buffer.resize(index + 4);
-                try self.addTypeReloc(atom, Type.bool, @intCast(u32, index), null);
-                // DW.AT.data_member_location, DW.FORM.sdata
-                try dbg_info_buffer.ensureUnusedCapacity(6);
-                dbg_info_buffer.appendAssumeCapacity(0);
-                // DW.AT.member
-                dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
-                // DW.AT.name, DW.FORM.string
-                dbg_info_buffer.appendSliceAssumeCapacity("val");
-                dbg_info_buffer.appendAssumeCapacity(0);
-                // DW.AT.type, DW.FORM.ref4
-                index = dbg_info_buffer.items.len;
-                try dbg_info_buffer.resize(index + 4);
-                try self.addTypeReloc(atom, payload_ty, @intCast(u32, index), null);
-                // DW.AT.data_member_location, DW.FORM.sdata
-                const offset = abi_size - payload_ty.abiSize(target);
-                try leb128.writeULEB128(dbg_info_buffer.writer(), offset);
-                // DW.AT.structure_type delimit children
-                try dbg_info_buffer.append(0);
-            }
-        },
-        .Pointer => {
-            if (ty.isSlice()) {
-                // Slices are structs: struct { .ptr = *, .len = N }
-                // DW.AT.structure_type
-                try dbg_info_buffer.ensureUnusedCapacity(2);
-                dbg_info_buffer.appendAssumeCapacity(abbrev_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);
-                // DW.AT.name, DW.FORM.string
-                dbg_info_buffer.appendSliceAssumeCapacity("ptr");
-                dbg_info_buffer.appendAssumeCapacity(0);
-                // DW.AT.type, DW.FORM.ref4
-                var index = dbg_info_buffer.items.len;
-                try dbg_info_buffer.resize(index + 4);
-                var buf = try arena.create(Type.SlicePtrFieldTypeBuffer);
-                const ptr_ty = ty.slicePtrFieldType(buf);
-                try self.addTypeReloc(atom, ptr_ty, @intCast(u32, index), null);
-                // DW.AT.data_member_location, DW.FORM.sdata
-                try dbg_info_buffer.ensureUnusedCapacity(6);
-                dbg_info_buffer.appendAssumeCapacity(0);
-                // DW.AT.member
-                dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
-                // DW.AT.name, DW.FORM.string
-                dbg_info_buffer.appendSliceAssumeCapacity("len");
-                dbg_info_buffer.appendAssumeCapacity(0);
-                // DW.AT.type, DW.FORM.ref4
-                index = dbg_info_buffer.items.len;
-                try dbg_info_buffer.resize(index + 4);
-                try self.addTypeReloc(atom, Type.usize, @intCast(u32, index), null);
-                // DW.AT.data_member_location, DW.FORM.sdata
-                try dbg_info_buffer.ensureUnusedCapacity(2);
-                dbg_info_buffer.appendAssumeCapacity(@sizeOf(usize));
-                // DW.AT.structure_type delimit children
-                dbg_info_buffer.appendAssumeCapacity(0);
-            } else {
-                try dbg_info_buffer.ensureUnusedCapacity(5);
-                dbg_info_buffer.appendAssumeCapacity(abbrev_ptr_type);
-                // DW.AT.type, DW.FORM.ref4
-                const index = dbg_info_buffer.items.len;
-                try dbg_info_buffer.resize(index + 4);
-                try self.addTypeReloc(atom, ty.childType(), @intCast(u32, index), null);
-            }
-        },
-        .Struct => blk: {
-            // DW.AT.structure_type
-            try dbg_info_buffer.append(abbrev_struct_type);
-            // DW.AT.byte_size, DW.FORM.sdata
-            const abi_size = ty.abiSize(target);
-            try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
-
-            switch (ty.tag()) {
-                .tuple, .anon_struct => {
-                    // DW.AT.name, DW.FORM.string
-                    try dbg_info_buffer.writer().print("{}\x00", .{ty.fmt(target)});
-
-                    const fields = ty.tupleFields();
-                    for (fields.types) |field, field_index| {
-                        // DW.AT.member
-                        try dbg_info_buffer.append(abbrev_struct_member);
-                        // DW.AT.name, DW.FORM.string
-                        try dbg_info_buffer.writer().print("{d}\x00", .{field_index});
-                        // DW.AT.type, DW.FORM.ref4
-                        var index = dbg_info_buffer.items.len;
-                        try dbg_info_buffer.resize(index + 4);
-                        try self.addTypeReloc(atom, field, @intCast(u32, index), null);
-                        // DW.AT.data_member_location, DW.FORM.sdata
-                        const field_off = ty.structFieldOffset(field_index, target);
-                        try leb128.writeULEB128(dbg_info_buffer.writer(), field_off);
-                    }
-                },
-                else => {
-                    // DW.AT.name, DW.FORM.string
-                    const struct_name = try ty.nameAllocArena(arena, target);
-                    try dbg_info_buffer.ensureUnusedCapacity(struct_name.len + 1);
-                    dbg_info_buffer.appendSliceAssumeCapacity(struct_name);
-                    dbg_info_buffer.appendAssumeCapacity(0);
-
-                    const struct_obj = ty.castTag(.@"struct").?.data;
-                    if (struct_obj.layout == .Packed) {
-                        log.debug("TODO implement .debug_info for packed structs", .{});
-                        break :blk;
-                    }
-
-                    const fields = ty.structFields();
-                    for (fields.keys()) |field_name, field_index| {
-                        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);
-                        // DW.AT.name, DW.FORM.string
-                        dbg_info_buffer.appendSliceAssumeCapacity(field_name);
-                        dbg_info_buffer.appendAssumeCapacity(0);
-                        // DW.AT.type, DW.FORM.ref4
-                        var index = dbg_info_buffer.items.len;
-                        try dbg_info_buffer.resize(index + 4);
-                        try self.addTypeReloc(atom, field.ty, @intCast(u32, index), null);
-                        // DW.AT.data_member_location, DW.FORM.sdata
-                        const field_off = ty.structFieldOffset(field_index, target);
-                        try leb128.writeULEB128(dbg_info_buffer.writer(), field_off);
-                    }
-                },
-            }
-
-            // DW.AT.structure_type delimit children
-            try dbg_info_buffer.append(0);
-        },
-        .Enum => {
-            // DW.AT.enumeration_type
-            try dbg_info_buffer.append(abbrev_enum_type);
-            // DW.AT.byte_size, DW.FORM.sdata
-            const abi_size = ty.abiSize(target);
-            try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
-            // DW.AT.name, DW.FORM.string
-            const enum_name = try ty.nameAllocArena(arena, target);
-            try dbg_info_buffer.ensureUnusedCapacity(enum_name.len + 1);
-            dbg_info_buffer.appendSliceAssumeCapacity(enum_name);
-            dbg_info_buffer.appendAssumeCapacity(0);
-
-            const fields = ty.enumFields();
-            const values: ?Module.EnumFull.ValueMap = switch (ty.tag()) {
-                .enum_full, .enum_nonexhaustive => ty.cast(Type.Payload.EnumFull).?.data.values,
-                .enum_simple => null,
-                .enum_numbered => ty.castTag(.enum_numbered).?.data.values,
-                else => unreachable,
-            };
-            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);
-                // DW.AT.name, DW.FORM.string
-                dbg_info_buffer.appendSliceAssumeCapacity(field_name);
-                dbg_info_buffer.appendAssumeCapacity(0);
-                // DW.AT.const_value, DW.FORM.data8
-                const value: u64 = if (values) |vals| value: {
-                    if (vals.count() == 0) break :value @intCast(u64, field_i); // auto-numbered
-                    const value = vals.keys()[field_i];
-                    var int_buffer: Value.Payload.U64 = undefined;
-                    break :value value.enumToInt(ty, &int_buffer).toUnsignedInt(target);
-                } else @intCast(u64, field_i);
-                mem.writeInt(u64, dbg_info_buffer.addManyAsArrayAssumeCapacity(8), value, target_endian);
-            }
-
-            // DW.AT.enumeration_type delimit children
-            try dbg_info_buffer.append(0);
-        },
-        .Union => {
-            const layout = ty.unionGetLayout(target);
-            const union_obj = ty.cast(Type.Payload.Union).?.data;
-            const payload_offset = if (layout.tag_align >= layout.payload_align) layout.tag_size else 0;
-            const tag_offset = if (layout.tag_align >= layout.payload_align) 0 else layout.payload_size;
-            const is_tagged = layout.tag_size > 0;
-            const union_name = try ty.nameAllocArena(arena, target);
-
-            // TODO this is temporary to match current state of unions in Zig - we don't yet have
-            // safety checks implemented meaning the implicit tag is not yet stored and generated
-            // for untagged unions.
-            if (is_tagged) {
-                // DW.AT.structure_type
-                try dbg_info_buffer.append(abbrev_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
-                try dbg_info_buffer.ensureUnusedCapacity(union_name.len + 1);
-                dbg_info_buffer.appendSliceAssumeCapacity(union_name);
-                dbg_info_buffer.appendAssumeCapacity(0);
-
-                // DW.AT.member
-                try dbg_info_buffer.ensureUnusedCapacity(9);
-                dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
-                // DW.AT.name, DW.FORM.string
-                dbg_info_buffer.appendSliceAssumeCapacity("payload");
-                dbg_info_buffer.appendAssumeCapacity(0);
-                // DW.AT.type, DW.FORM.ref4
-                const inner_union_index = dbg_info_buffer.items.len;
-                try dbg_info_buffer.resize(inner_union_index + 4);
-                try self.addTypeReloc(atom, ty, @intCast(u32, inner_union_index), 5);
-                // DW.AT.data_member_location, DW.FORM.sdata
-                try leb128.writeULEB128(dbg_info_buffer.writer(), payload_offset);
-            }
-
-            // DW.AT.union_type
-            try dbg_info_buffer.append(abbrev_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
-            if (is_tagged) {
-                try dbg_info_buffer.writer().print("AnonUnion\x00", .{});
-            } else {
-                try dbg_info_buffer.writer().print("{s}\x00", .{union_name});
-            }
-
-            const fields = ty.unionFields();
-            for (fields.keys()) |field_name| {
-                const field = fields.get(field_name).?;
-                if (!field.ty.hasRuntimeBits()) continue;
-                // DW.AT.member
-                try dbg_info_buffer.append(abbrev_struct_member);
-                // DW.AT.name, DW.FORM.string
-                try dbg_info_buffer.writer().print("{s}\x00", .{field_name});
-                // DW.AT.type, DW.FORM.ref4
-                const index = dbg_info_buffer.items.len;
-                try dbg_info_buffer.resize(index + 4);
-                try self.addTypeReloc(atom, field.ty, @intCast(u32, index), null);
-                // DW.AT.data_member_location, DW.FORM.sdata
-                try dbg_info_buffer.append(0);
-            }
-            // DW.AT.union_type delimit children
-            try dbg_info_buffer.append(0);
-
-            if (is_tagged) {
-                // DW.AT.member
-                try dbg_info_buffer.ensureUnusedCapacity(5);
-                dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
-                // DW.AT.name, DW.FORM.string
-                dbg_info_buffer.appendSliceAssumeCapacity("tag");
-                dbg_info_buffer.appendAssumeCapacity(0);
-                // DW.AT.type, DW.FORM.ref4
-                const index = dbg_info_buffer.items.len;
-                try dbg_info_buffer.resize(index + 4);
-                try self.addTypeReloc(atom, union_obj.tag_ty, @intCast(u32, index), null);
-                // DW.AT.data_member_location, DW.FORM.sdata
-                try leb128.writeULEB128(dbg_info_buffer.writer(), tag_offset);
-
-                // DW.AT.structure_type delimit children
-                try dbg_info_buffer.append(0);
-            }
-        },
-        .ErrorSet => {
-            // DW.AT.enumeration_type
-            try dbg_info_buffer.append(abbrev_enum_type);
-            // DW.AT.byte_size, DW.FORM.sdata
-            const abi_size = ty.abiSize(target);
-            try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
-            // DW.AT.name, DW.FORM.string
-            const name = try ty.nameAllocArena(arena, target);
-            try dbg_info_buffer.writer().print("{s}\x00", .{name});
-
-            // 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);
-            // DW.AT.name, DW.FORM.string
-            dbg_info_buffer.appendSliceAssumeCapacity(no_error);
-            dbg_info_buffer.appendAssumeCapacity(0);
-            // DW.AT.const_value, DW.FORM.data8
-            mem.writeInt(u64, dbg_info_buffer.addManyAsArrayAssumeCapacity(8), 0, target_endian);
-
-            const error_names = ty.errorSetNames();
-            for (error_names) |error_name| {
-                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);
-                // DW.AT.name, DW.FORM.string
-                dbg_info_buffer.appendSliceAssumeCapacity(error_name);
-                dbg_info_buffer.appendAssumeCapacity(0);
-                // DW.AT.const_value, DW.FORM.data8
-                mem.writeInt(u64, dbg_info_buffer.addManyAsArrayAssumeCapacity(8), kv.value, target_endian);
-            }
-
-            // DW.AT.enumeration_type delimit children
-            try dbg_info_buffer.append(0);
-        },
-        .ErrorUnion => {
-            const error_ty = ty.errorUnionSet();
-            const payload_ty = ty.errorUnionPayload();
-            const abi_size = ty.abiSize(target);
-            const abi_align = ty.abiAlignment(target);
-            const payload_off = mem.alignForwardGeneric(u64, error_ty.abiSize(target), abi_align);
-
-            // DW.AT.structure_type
-            try dbg_info_buffer.append(abbrev_struct_type);
-            // DW.AT.byte_size, DW.FORM.sdata
-            try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
-            // DW.AT.name, DW.FORM.string
-            const name = try ty.nameAllocArena(arena, target);
-            try dbg_info_buffer.writer().print("{s}\x00", .{name});
-
-            // DW.AT.member
-            try dbg_info_buffer.ensureUnusedCapacity(7);
-            dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
-            // DW.AT.name, DW.FORM.string
-            dbg_info_buffer.appendSliceAssumeCapacity("value");
-            dbg_info_buffer.appendAssumeCapacity(0);
-            // DW.AT.type, DW.FORM.ref4
-            var index = dbg_info_buffer.items.len;
-            try dbg_info_buffer.resize(index + 4);
-            try self.addTypeReloc(atom, payload_ty, @intCast(u32, index), null);
-            // DW.AT.data_member_location, DW.FORM.sdata
-            try leb128.writeULEB128(dbg_info_buffer.writer(), payload_off);
-
-            // DW.AT.member
-            try dbg_info_buffer.ensureUnusedCapacity(5);
-            dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
-            // DW.AT.name, DW.FORM.string
-            dbg_info_buffer.appendSliceAssumeCapacity("err");
-            dbg_info_buffer.appendAssumeCapacity(0);
-            // DW.AT.type, DW.FORM.ref4
-            index = dbg_info_buffer.items.len;
-            try dbg_info_buffer.resize(index + 4);
-            try self.addTypeReloc(atom, error_ty, @intCast(u32, index), null);
-            // DW.AT.data_member_location, DW.FORM.sdata
-            try dbg_info_buffer.append(0);
-
-            // DW.AT.structure_type delimit children
-            try dbg_info_buffer.append(0);
-        },
-        else => {
-            log.debug("TODO implement .debug_info for type '{}'", .{ty.fmtDebug()});
-            try dbg_info_buffer.append(abbrev_pad1);
-        },
-    }
-}
-
 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.
@@ -1952,45 +1959,6 @@ fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
         std.math.maxInt(@TypeOf(actual_size));
 }
 
-pub fn addTypeReloc(self: *Dwarf, atom: *const Atom, ty: Type, offset: u32, addend: ?u32) !void {
-    const decl_state = &self.decl_state.?;
-    const gpa = self.allocator;
-    const resolv = decl_state.abbrev_resolver.getContext(ty, .{
-        .target = self.target,
-    }) orelse blk: {
-        const sym_index = @intCast(u32, decl_state.abbrev_table.items.len);
-        try decl_state.abbrev_table.append(gpa, .{
-            .atom = atom,
-            .@"type" = ty,
-            .offset = undefined,
-        });
-        log.debug("@{d}: {}", .{ sym_index, ty.fmtDebug() });
-        try decl_state.abbrev_resolver.putNoClobberContext(gpa, ty, sym_index, .{
-            .target = self.target,
-        });
-        break :blk decl_state.abbrev_resolver.getContext(ty, .{
-            .target = self.target,
-        }).?;
-    };
-    const add: u32 = addend orelse 0;
-
-    log.debug("{x}: @{d} + {x}", .{ offset, resolv, add });
-    try decl_state.abbrev_relocs.append(gpa, .{
-        .target = resolv,
-        .atom = atom,
-        .offset = offset,
-        .addend = add,
-    });
-}
-
-pub fn getDeclDebugLineBuffer(self: *Dwarf) *std.ArrayList(u8) {
-    return &self.decl_state.?.dbg_line;
-}
-
-pub fn getDeclDebugInfoBuffer(self: *Dwarf) *std.ArrayList(u8) {
-    return &self.decl_state.?.dbg_info;
-}
-
 pub fn flushModule(self: *Dwarf, file: *File, module: *Module) !void {
     if (self.global_abbrev_relocs.items.len > 0) {
         const gpa = self.allocator;
@@ -2018,7 +1986,7 @@ pub fn flushModule(self: *Dwarf, file: *File, module: *Module) !void {
         };
 
         var dbg_info_buffer = std.ArrayList(u8).init(arena);
-        try self.addDbgInfoType(arena, module, atom, error_ty, &dbg_info_buffer);
+        try addDbgInfoErrorSet(arena, module, error_ty, self.target, &dbg_info_buffer);
 
         try self.managed_atoms.append(gpa, atom);
         try self.updateDeclDebugInfoAllocation(file, atom, @intCast(u32, dbg_info_buffer.items.len));
@@ -2060,6 +2028,49 @@ pub fn flushModule(self: *Dwarf, file: *File, module: *Module) !void {
             }
         }
     }
+}
+
+fn addDbgInfoErrorSet(
+    arena: Allocator,
+    module: *Module,
+    ty: Type,
+    target: std.Target,
+    dbg_info_buffer: *std.ArrayList(u8),
+) !void {
+    const target_endian = target.cpu.arch.endian();
+
+    // DW.AT.enumeration_type
+    try dbg_info_buffer.append(abbrev_enum_type);
+    // DW.AT.byte_size, DW.FORM.sdata
+    const abi_size = ty.abiSize(target);
+    try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
+    // DW.AT.name, DW.FORM.string
+    const name = try ty.nameAllocArena(arena, target);
+    try dbg_info_buffer.writer().print("{s}\x00", .{name});
+
+    // 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);
+    // DW.AT.name, DW.FORM.string
+    dbg_info_buffer.appendSliceAssumeCapacity(no_error);
+    dbg_info_buffer.appendAssumeCapacity(0);
+    // DW.AT.const_value, DW.FORM.data8
+    mem.writeInt(u64, dbg_info_buffer.addManyAsArrayAssumeCapacity(8), 0, target_endian);
+
+    const error_names = ty.errorSetNames();
+    for (error_names) |error_name| {
+        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);
+        // DW.AT.name, DW.FORM.string
+        dbg_info_buffer.appendSliceAssumeCapacity(error_name);
+        dbg_info_buffer.appendAssumeCapacity(0);
+        // DW.AT.const_value, DW.FORM.data8
+        mem.writeInt(u64, dbg_info_buffer.addManyAsArrayAssumeCapacity(8), kv.value, target_endian);
+    }
 
-    assert(self.decl_state == null);
+    // DW.AT.enumeration_type delimit children
+    try dbg_info_buffer.append(0);
 }
src/link/Elf.zig
@@ -2339,19 +2339,12 @@ pub fn updateFunc(self: *Elf, module: *Module, func: *Module.Fn, air: Air, liven
     const decl = func.owner_decl;
     self.freeUnnamedConsts(decl);
 
-    if (self.dwarf) |*dw| {
-        try dw.initDeclState(decl);
-    }
-    defer if (self.dwarf) |*dw| {
-        if (dw.decl_state) |*ds| {
-            ds.deinit(dw.allocator);
-            dw.decl_state = null;
-        }
-    };
+    var decl_state: ?Dwarf.DeclState = if (self.dwarf) |*dw| try dw.initDeclState(decl) else null;
+    defer if (decl_state) |*ds| ds.deinit();
 
-    const res = if (self.dwarf) |*dw|
+    const res = if (decl_state) |*ds|
         try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .{
-            .dwarf = dw,
+            .dwarf = ds,
         })
     else
         try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .none);
@@ -2365,8 +2358,15 @@ pub fn updateFunc(self: *Elf, module: *Module, func: *Module.Fn, air: Air, liven
         },
     };
     const local_sym = try self.updateDeclCode(decl, code, elf.STT_FUNC);
-    if (self.dwarf) |*dw| {
-        try dw.commitDeclState(&self.base, module, decl, local_sym.st_value, local_sym.st_size);
+    if (decl_state) |*ds| {
+        try self.dwarf.?.commitDeclState(
+            &self.base,
+            module,
+            decl,
+            local_sym.st_value,
+            local_sym.st_size,
+            ds,
+        );
     }
 
     // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
@@ -2400,18 +2400,17 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
     var code_buffer = std.ArrayList(u8).init(self.base.allocator);
     defer code_buffer.deinit();
 
-    if (self.dwarf) |*dw| {
-        try dw.initDeclState(decl);
-    }
+    var decl_state: ?Dwarf.DeclState = if (self.dwarf) |*dw| try dw.initDeclState(decl) else null;
+    defer if (decl_state) |*ds| ds.deinit();
 
     // TODO implement .debug_info for global variables
     const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val;
-    const res = if (self.dwarf) |*dw|
+    const res = if (decl_state) |*ds|
         try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
             .ty = decl.ty,
             .val = decl_val,
         }, &code_buffer, .{
-            .dwarf = dw,
+            .dwarf = ds,
         }, .{
             .parent_atom_index = decl.link.elf.local_sym_index,
         })
@@ -2434,8 +2433,15 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
     };
 
     const local_sym = try self.updateDeclCode(decl, code, elf.STT_OBJECT);
-    if (self.dwarf) |*dw| {
-        try dw.commitDeclState(&self.base, module, decl, local_sym.st_value, local_sym.st_size);
+    if (decl_state) |*ds| {
+        try self.dwarf.?.commitDeclState(
+            &self.base,
+            module,
+            decl,
+            local_sym.st_value,
+            local_sym.st_size,
+            ds,
+        );
     }
 
     // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
src/link/MachO.zig
@@ -27,6 +27,7 @@ const Atom = @import("MachO/Atom.zig");
 const Cache = @import("../Cache.zig");
 const CodeSignature = @import("MachO/CodeSignature.zig");
 const Compilation = @import("../Compilation.zig");
+const Dwarf = File.Dwarf;
 const Dylib = @import("MachO/Dylib.zig");
 const File = link.File;
 const Object = @import("MachO/Object.zig");
@@ -3676,13 +3677,15 @@ 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();
 
-    if (self.d_sym) |*d_sym| {
-        try d_sym.dwarf.initDeclState(decl);
-    }
+    var decl_state = if (self.d_sym) |*d_sym|
+        try d_sym.dwarf.initDeclState(decl)
+    else
+        null;
+    defer if (decl_state) |*ds| ds.deinit();
 
-    const res = if (self.d_sym) |*d_sym|
+    const res = if (decl_state) |*ds|
         try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .{
-            .dwarf = &d_sym.dwarf,
+            .dwarf = ds,
         })
     else
         try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .none);
@@ -3700,8 +3703,15 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv
 
     const symbol = try self.placeDecl(decl, decl.link.macho.code.items.len);
 
-    if (self.d_sym) |*d_sym| {
-        try d_sym.dwarf.commitDeclState(&self.base, module, decl, symbol.n_value, decl.link.macho.size);
+    if (decl_state) |*ds| {
+        try self.d_sym.?.dwarf.commitDeclState(
+            &self.base,
+            module,
+            decl,
+            symbol.n_value,
+            decl.link.macho.size,
+            ds,
+        );
     }
 
     // Since we updated the vaddr and the size, each corresponding export symbol also
@@ -3801,17 +3811,19 @@ 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();
 
-    if (self.d_sym) |*d_sym| {
-        try d_sym.dwarf.initDeclState(decl);
-    }
+    var decl_state: ?Dwarf.DeclState = if (self.d_sym) |*d_sym|
+        try d_sym.dwarf.initDeclState(decl)
+    else
+        null;
+    defer if (decl_state) |*ds| ds.deinit();
 
     const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val;
-    const res = if (self.d_sym) |*d_sym|
+    const res = if (decl_state) |*ds|
         try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
             .ty = decl.ty,
             .val = decl_val,
         }, &code_buffer, .{
-            .dwarf = &d_sym.dwarf,
+            .dwarf = ds,
         }, .{
             .parent_atom_index = decl.link.macho.local_sym_index,
         })
@@ -3845,8 +3857,15 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
     };
     const symbol = try self.placeDecl(decl, code.len);
 
-    if (self.d_sym) |*d_sym| {
-        try d_sym.dwarf.commitDeclState(&self.base, module, decl, symbol.n_value, decl.link.macho.size);
+    if (decl_state) |*ds| {
+        try self.d_sym.?.dwarf.commitDeclState(
+            &self.base,
+            module,
+            decl,
+            symbol.n_value,
+            decl.link.macho.size,
+            ds,
+        );
     }
 
     // Since we updated the vaddr and the size, each corresponding export symbol also
src/codegen.zig
@@ -42,7 +42,7 @@ pub const GenerateSymbolError = error{
 };
 
 pub const DebugInfoOutput = union(enum) {
-    dwarf: *link.File.Dwarf,
+    dwarf: *link.File.Dwarf.DeclState,
     /// the plan9 debuginfo output is a bytecode with 4 opcodes
     /// assume all numbers/variables are bytes
     /// 0 w x y z -> interpret w x y z as a big-endian i32, and add it to the line offset