Commit ea4ff34e13

Jakub Konka <kubkon@jakubkonka.com>
2020-12-30 22:18:21
macho: put all DWARF-related logic into DebugSymbols
1 parent d189614
Changed files (2)
src/link/MachO/DebugSymbols.zig
@@ -10,6 +10,7 @@ const DW = std.dwarf;
 const leb = std.leb;
 const Allocator = mem.Allocator;
 
+const build_options = @import("build_options");
 const trace = @import("../../tracy.zig").trace;
 const Module = @import("../../Module.zig");
 const Type = @import("../../type.zig").Type;
@@ -87,21 +88,21 @@ debug_aranges_section_dirty: bool = false,
 debug_info_header_dirty: bool = false,
 debug_line_header_dirty: bool = false,
 
-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_pad1 = 5;
-pub const abbrev_parameter = 6;
+const abbrev_compile_unit = 1;
+const abbrev_subprogram = 2;
+const abbrev_subprogram_retvoid = 3;
+const abbrev_base_type = 4;
+const abbrev_pad1 = 5;
+const abbrev_parameter = 6;
 
 /// The reloc offset for the virtual address of a function in its Line Number Program.
 /// Size is a virtual address integer.
-pub const dbg_line_vaddr_reloc_index = 3;
+const dbg_line_vaddr_reloc_index = 3;
 /// The reloc offset for the virtual address of a function in its .debug_info TAG_subprogram.
 /// Size is a virtual address integer.
-pub const dbg_info_low_pc_reloc_index = 1;
+const dbg_info_low_pc_reloc_index = 1;
 
-pub const min_nop_size = 2;
+const min_nop_size = 2;
 
 /// You must call this function *after* `MachO.populateMissingMetadata()`
 /// has been called to get a viable debug symbols output.
@@ -888,8 +889,304 @@ fn writeStringTable(self: *DebugSymbols) !void {
     self.string_table_dirty = false;
 }
 
+pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const Module.Decl) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const container_scope = decl.scope.cast(Module.Scope.Container).?;
+    const tree = container_scope.file_scope.contents.tree;
+    const file_ast_decls = tree.root_node.decls();
+    // TODO Look into improving the performance here by adding a token-index-to-line
+    // lookup table. Currently this involves scanning over the source code for newlines.
+    const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
+    const block = fn_proto.getBodyNode().?.castTag(.Block).?;
+    const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
+    const casted_line_off = @intCast(u28, line_delta);
+
+    const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment;
+    const shdr = &dwarf_segment.sections.items[self.debug_line_section_index.?];
+    const file_pos = shdr.offset + decl.fn_link.macho.off + getRelocDbgLineOff();
+    var data: [4]u8 = undefined;
+    leb.writeUnsignedFixed(4, &data, casted_line_off);
+    try self.file.pwriteAll(&data, file_pos);
+}
+
+pub const DeclDebugBuffers = struct {
+    dbg_line_buffer: std.ArrayList(u8),
+    dbg_info_buffer: std.ArrayList(u8),
+    dbg_info_type_relocs: link.File.DbgInfoTypeRelocsTable,
+};
+
+/// Caller owns the returned memory.
+pub fn initDeclDebugBuffers(
+    self: *DebugSymbols,
+    allocator: *Allocator,
+    module: *Module,
+    decl: *Module.Decl,
+) !DeclDebugBuffers {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    var dbg_line_buffer = std.ArrayList(u8).init(allocator);
+    var dbg_info_buffer = std.ArrayList(u8).init(allocator);
+    var dbg_info_type_relocs: link.File.DbgInfoTypeRelocsTable = .{};
+
+    const typed_value = decl.typed_value.most_recent.typed_value;
+    switch (typed_value.ty.zigTypeTag()) {
+        .Fn => {
+            const zir_dumps = if (std.builtin.is_test) &[0][]const u8{} else build_options.zir_dumps;
+            if (zir_dumps.len != 0) {
+                for (zir_dumps) |fn_name| {
+                    if (mem.eql(u8, mem.spanZ(decl.name), fn_name)) {
+                        std.debug.print("\n{}\n", .{decl.name});
+                        typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*);
+                    }
+                }
+            }
+
+            // For functions we need to add a prologue to the debug line program.
+            try dbg_line_buffer.ensureCapacity(26);
+
+            const line_off: u28 = blk: {
+                if (decl.scope.cast(Module.Scope.Container)) |container_scope| {
+                    const tree = container_scope.file_scope.contents.tree;
+                    const file_ast_decls = tree.root_node.decls();
+                    // TODO Look into improving the performance here by adding a token-index-to-line
+                    // lookup table. Currently this involves scanning over the source code for newlines.
+                    const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
+                    const block = fn_proto.getBodyNode().?.castTag(.Block).?;
+                    const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
+                    break :blk @intCast(u28, line_delta);
+                } else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| {
+                    const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src;
+                    const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off);
+                    break :blk @intCast(u28, line_delta);
+                } else {
+                    unreachable;
+                }
+            };
+
+            dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{
+                DW.LNS_extended_op,
+                @sizeOf(u64) + 1,
+                DW.LNE_set_address,
+            });
+            // This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`.
+            assert(dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len);
+            dbg_line_buffer.items.len += @sizeOf(u64);
+
+            dbg_line_buffer.appendAssumeCapacity(DW.LNS_advance_line);
+            // This is the "relocatable" relative line offset from the previous function's end curly
+            // to this function's begin curly.
+            assert(getRelocDbgLineOff() == dbg_line_buffer.items.len);
+            // Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later.
+            leb.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line_off);
+
+            dbg_line_buffer.appendAssumeCapacity(DW.LNS_set_file);
+            assert(getRelocDbgFileIndex() == dbg_line_buffer.items.len);
+            // Once we support more than one source file, this will have the ability to be more
+            // than one possible value.
+            const file_index = 1;
+            leb.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index);
+
+            // Emit a line for the begin curly with prologue_end=false. The codegen will
+            // do the work of setting prologue_end=true and epilogue_begin=true.
+            dbg_line_buffer.appendAssumeCapacity(DW.LNS_copy);
+
+            // .debug_info subprogram
+            const decl_name_with_null = decl.name[0 .. mem.lenZ(decl.name) + 1];
+            try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 27 + decl_name_with_null.len);
+
+            const fn_ret_type = typed_value.ty.fnReturnType();
+            const fn_ret_has_bits = fn_ret_type.hasCodeGenBits();
+            if (fn_ret_has_bits) {
+                dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram);
+            } else {
+                dbg_info_buffer.appendAssumeCapacity(abbrev_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
+            // moved in virtual address space.
+            assert(dbg_info_low_pc_reloc_index == dbg_info_buffer.items.len);
+            dbg_info_buffer.items.len += @sizeOf(u64); // DW.AT_low_pc,  DW.FORM_addr
+            assert(getRelocDbgInfoSubprogramHighPC() == dbg_info_buffer.items.len);
+            dbg_info_buffer.items.len += 4; // DW.AT_high_pc,  DW.FORM_data4
+            if (fn_ret_has_bits) {
+                const gop = try dbg_info_type_relocs.getOrPut(allocator, fn_ret_type);
+                if (!gop.found_existing) {
+                    gop.entry.value = .{
+                        .off = undefined,
+                        .relocs = .{},
+                    };
+                }
+                try gop.entry.value.relocs.append(allocator, @intCast(u32, dbg_info_buffer.items.len));
+                dbg_info_buffer.items.len += 4; // DW.AT_type,  DW.FORM_ref4
+            }
+            dbg_info_buffer.appendSliceAssumeCapacity(decl_name_with_null); // DW.AT_name, DW.FORM_string
+            mem.writeIntLittle(u32, dbg_info_buffer.addManyAsArrayAssumeCapacity(4), line_off + 1); // DW.AT_decl_line, DW.FORM_data4
+            dbg_info_buffer.appendAssumeCapacity(file_index); // DW.AT_decl_file, DW.FORM_data1
+        },
+        else => {
+            // TODO implement .debug_info for global variables
+        },
+    }
+
+    return DeclDebugBuffers{
+        .dbg_info_buffer = dbg_info_buffer,
+        .dbg_line_buffer = dbg_line_buffer,
+        .dbg_info_type_relocs = dbg_info_type_relocs,
+    };
+}
+
+pub fn commitDeclDebugInfo(
+    self: *DebugSymbols,
+    allocator: *Allocator,
+    module: *Module,
+    decl: *Module.Decl,
+    debug_buffers: *DeclDebugBuffers,
+    target: std.Target,
+) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    var dbg_line_buffer = &debug_buffers.dbg_line_buffer;
+    var dbg_info_buffer = &debug_buffers.dbg_info_buffer;
+    var dbg_info_type_relocs = &debug_buffers.dbg_info_type_relocs;
+
+    const symbol = self.base.local_symbols.items[decl.link.macho.local_sym_index];
+    const text_block = &decl.link.macho;
+    // If the Decl is a function, we need to update the __debug_line program.
+    const typed_value = decl.typed_value.most_recent.typed_value;
+    switch (typed_value.ty.zigTypeTag()) {
+        .Fn => {
+            // Perform the relocations based on vaddr.
+            {
+                const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..8];
+                mem.writeIntLittle(u64, ptr, symbol.n_value);
+            }
+            {
+                const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..8];
+                mem.writeIntLittle(u64, ptr, symbol.n_value);
+            }
+            {
+                const ptr = dbg_info_buffer.items[getRelocDbgInfoSubprogramHighPC()..][0..4];
+                mem.writeIntLittle(u32, ptr, @intCast(u32, text_block.size));
+            }
+
+            try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS_extended_op, 1, DW.LNE_end_sequence });
+
+            // Now we have the full contents and may allocate a region to store it.
+
+            // This logic is nearly identical to the logic below in `updateDeclDebugInfo` for
+            // `TextBlock` and the .debug_info. If you are editing this logic, you
+            // probably need to edit that logic too.
+
+            const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment;
+            const debug_line_sect = &dwarf_segment.sections.items[self.debug_line_section_index.?];
+            const src_fn = &decl.fn_link.macho;
+            src_fn.len = @intCast(u32, dbg_line_buffer.items.len);
+            if (self.dbg_line_fn_last) |last| {
+                if (src_fn.next) |next| {
+                    // Update existing function - non-last item.
+                    if (src_fn.off + src_fn.len + min_nop_size > next.off) {
+                        // It grew too big, so we move it to a new location.
+                        if (src_fn.prev) |prev| {
+                            _ = self.dbg_line_fn_free_list.put(allocator, prev, {}) catch {};
+                            prev.next = src_fn.next;
+                        }
+                        next.prev = src_fn.prev;
+                        src_fn.next = null;
+                        // Populate where it used to be with NOPs.
+                        const file_pos = debug_line_sect.offset + src_fn.off;
+                        try self.pwriteDbgLineNops(0, &[0]u8{}, src_fn.len, file_pos);
+                        // TODO Look at the free list before appending at the end.
+                        src_fn.prev = last;
+                        last.next = src_fn;
+                        self.dbg_line_fn_last = src_fn;
+
+                        src_fn.off = last.off + (last.len * alloc_num / alloc_den);
+                    }
+                } else if (src_fn.prev == null) {
+                    // Append new function.
+                    // TODO Look at the free list before appending at the end.
+                    src_fn.prev = last;
+                    last.next = src_fn;
+                    self.dbg_line_fn_last = src_fn;
+
+                    src_fn.off = last.off + (last.len * alloc_num / alloc_den);
+                }
+            } else {
+                // This is the first function of the Line Number Program.
+                self.dbg_line_fn_first = src_fn;
+                self.dbg_line_fn_last = src_fn;
+
+                src_fn.off = self.dbgLineNeededHeaderBytes(module) * alloc_num / alloc_den;
+            }
+
+            const last_src_fn = self.dbg_line_fn_last.?;
+            const needed_size = last_src_fn.off + last_src_fn.len;
+            if (needed_size != debug_line_sect.size) {
+                if (needed_size > dwarf_segment.allocatedSize(debug_line_sect.offset)) {
+                    const new_offset = dwarf_segment.findFreeSpace(needed_size, 1, null);
+                    const existing_size = last_src_fn.off;
+
+                    log.debug("moving __debug_line section: {} bytes from 0x{x} to 0x{x}", .{
+                        existing_size,
+                        debug_line_sect.offset,
+                        new_offset,
+                    });
+
+                    const amt = try self.file.copyRangeAll(debug_line_sect.offset, self.file, new_offset, existing_size);
+                    if (amt != existing_size) return error.InputOutput;
+                    debug_line_sect.offset = @intCast(u32, new_offset);
+                    debug_line_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff;
+                }
+                debug_line_sect.size = needed_size;
+                self.load_commands_dirty = true; // TODO look into making only the one section dirty
+                self.debug_line_header_dirty = true;
+            }
+            const prev_padding_size: u32 = if (src_fn.prev) |prev| src_fn.off - (prev.off + prev.len) else 0;
+            const next_padding_size: u32 = if (src_fn.next) |next| next.off - (src_fn.off + src_fn.len) else 0;
+
+            // We only have support for one compilation unit so far, so the offsets are directly
+            // from the .debug_line section.
+            const file_pos = debug_line_sect.offset + src_fn.off;
+            try self.pwriteDbgLineNops(prev_padding_size, dbg_line_buffer.items, next_padding_size, file_pos);
+
+            // .debug_info - End the TAG_subprogram children.
+            try dbg_info_buffer.append(0);
+        },
+        else => {},
+    }
+
+    // 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.
+    var it = dbg_info_type_relocs.iterator();
+    while (it.next()) |entry| {
+        entry.value.off = @intCast(u32, dbg_info_buffer.items.len);
+        try self.addDbgInfoType(entry.key, dbg_info_buffer, target);
+    }
+
+    try self.updateDeclDebugInfoAllocation(allocator, text_block, @intCast(u32, dbg_info_buffer.items.len));
+
+    // Now that we have the offset assigned we can finally perform type relocations.
+    it = dbg_info_type_relocs.iterator();
+    while (it.next()) |entry| {
+        for (entry.value.relocs.items) |off| {
+            mem.writeIntLittle(
+                u32,
+                dbg_info_buffer.items[off..][0..4],
+                text_block.dbg_info_off + entry.value.off,
+            );
+        }
+    }
+
+    try self.writeDeclDebugInfo(text_block, dbg_info_buffer.items);
+}
+
 /// Asserts the type has codegen bits.
-pub fn addDbgInfoType(
+fn addDbgInfoType(
     self: *DebugSymbols,
     ty: Type,
     dbg_info_buffer: *std.ArrayList(u8),
@@ -931,7 +1228,7 @@ pub fn addDbgInfoType(
     }
 }
 
-pub fn updateDeclDebugInfoAllocation(
+fn updateDeclDebugInfoAllocation(
     self: *DebugSymbols,
     allocator: *Allocator,
     text_block: *TextBlock,
@@ -986,7 +1283,7 @@ pub fn updateDeclDebugInfoAllocation(
     }
 }
 
-pub fn writeDeclDebugInfo(self: *DebugSymbols, text_block: *TextBlock, dbg_info_buf: []const u8) !void {
+fn writeDeclDebugInfo(self: *DebugSymbols, text_block: *TextBlock, dbg_info_buf: []const u8) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -1057,19 +1354,19 @@ fn makeDebugString(self: *DebugSymbols, allocator: *Allocator, bytes: []const u8
 
 /// The reloc offset for the line offset of a function from the previous function's line.
 /// It's a fixed-size 4-byte ULEB128.
-pub fn getRelocDbgLineOff() usize {
+fn getRelocDbgLineOff() usize {
     return dbg_line_vaddr_reloc_index + @sizeOf(u64) + 1;
 }
 
-pub fn getRelocDbgFileIndex() usize {
+fn getRelocDbgFileIndex() usize {
     return getRelocDbgLineOff() + 5;
 }
 
-pub fn getRelocDbgInfoSubprogramHighPC() u32 {
+fn getRelocDbgInfoSubprogramHighPC() u32 {
     return dbg_info_low_pc_reloc_index + @sizeOf(u64);
 }
 
-pub fn dbgLineNeededHeaderBytes(self: DebugSymbols, module: *Module) u32 {
+fn dbgLineNeededHeaderBytes(self: DebugSymbols, module: *Module) u32 {
     const directory_entry_format_count = 1;
     const file_name_entry_format_count = 1;
     const directory_count = 1;
@@ -1092,7 +1389,7 @@ fn dbgInfoNeededHeaderBytes(self: DebugSymbols) u32 {
 /// are less than 126,976 bytes (if this limit is ever reached, this function can be
 /// improved to make more than one pwritev call, or the limit can be raised by a fixed
 /// amount by increasing the length of `vecs`).
-pub fn pwriteDbgLineNops(
+fn pwriteDbgLineNops(
     self: *DebugSymbols,
     prev_padding_size: usize,
     buf: []const u8,
@@ -1170,7 +1467,7 @@ pub fn pwriteDbgLineNops(
 
 /// Writes to the file a buffer, prefixed and suffixed by the specified number of
 /// bytes of padding.
-pub fn pwriteDbgInfoNops(
+fn pwriteDbgInfoNops(
     self: *DebugSymbols,
     prev_padding_size: usize,
     buf: []const u8,
@@ -1239,25 +1536,3 @@ pub fn pwriteDbgInfoNops(
 
     try self.file.pwritevAll(vecs[0..vec_index], offset - prev_padding_size);
 }
-
-pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const Module.Decl) !void {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const container_scope = decl.scope.cast(Module.Scope.Container).?;
-    const tree = container_scope.file_scope.contents.tree;
-    const file_ast_decls = tree.root_node.decls();
-    // TODO Look into improving the performance here by adding a token-index-to-line
-    // lookup table. Currently this involves scanning over the source code for newlines.
-    const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
-    const block = fn_proto.getBodyNode().?.castTag(.Block).?;
-    const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
-    const casted_line_off = @intCast(u28, line_delta);
-
-    const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment;
-    const shdr = &dwarf_segment.sections.items[self.debug_line_section_index.?];
-    const file_pos = shdr.offset + decl.fn_link.macho.off + getRelocDbgLineOff();
-    var data: [4]u8 = undefined;
-    leb.writeUnsignedFixed(4, &data, casted_line_off);
-    try self.file.pwriteAll(&data, file_pos);
-}
src/link/MachO.zig
@@ -11,8 +11,6 @@ const codegen = @import("../codegen.zig");
 const aarch64 = @import("../codegen/aarch64.zig");
 const math = std.math;
 const mem = std.mem;
-const DW = std.dwarf;
-const leb = std.leb;
 
 const trace = @import("../tracy.zig").trace;
 const build_options = @import("build_options");
@@ -1119,128 +1117,30 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
     var code_buffer = std.ArrayList(u8).init(self.base.allocator);
     defer code_buffer.deinit();
 
-    var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator);
-    defer dbg_line_buffer.deinit();
-
-    var dbg_info_buffer = std.ArrayList(u8).init(self.base.allocator);
-    defer dbg_info_buffer.deinit();
-
-    var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{};
+    var debug_buffers = if (self.d_sym) |*ds| try ds.initDeclDebugBuffers(self.base.allocator, module, decl) else null;
     defer {
-        var it = dbg_info_type_relocs.iterator();
-        while (it.next()) |entry| {
-            entry.value.relocs.deinit(self.base.allocator);
+        if (debug_buffers) |*dbg| {
+            dbg.dbg_line_buffer.deinit();
+            dbg.dbg_info_buffer.deinit();
+            var it = dbg.dbg_info_type_relocs.iterator();
+            while (it.next()) |entry| {
+                entry.value.relocs.deinit(self.base.allocator);
+            }
+            dbg.dbg_info_type_relocs.deinit(self.base.allocator);
         }
-        dbg_info_type_relocs.deinit(self.base.allocator);
     }
 
     const typed_value = decl.typed_value.most_recent.typed_value;
-    const is_fn: bool = switch (typed_value.ty.zigTypeTag()) {
-        .Fn => true,
-        else => false,
-    };
-    if (is_fn) {
-        const zir_dumps = if (std.builtin.is_test) &[0][]const u8{} else build_options.zir_dumps;
-        if (zir_dumps.len != 0) {
-            for (zir_dumps) |fn_name| {
-                if (mem.eql(u8, mem.spanZ(decl.name), fn_name)) {
-                    std.debug.print("\n{}\n", .{decl.name});
-                    typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*);
-                }
-            }
-        }
-
-        // For functions we need to add a prologue to the debug line program.
-        try dbg_line_buffer.ensureCapacity(26);
-
-        const line_off: u28 = blk: {
-            if (decl.scope.cast(Module.Scope.Container)) |container_scope| {
-                const tree = container_scope.file_scope.contents.tree;
-                const file_ast_decls = tree.root_node.decls();
-                // TODO Look into improving the performance here by adding a token-index-to-line
-                // lookup table. Currently this involves scanning over the source code for newlines.
-                const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
-                const block = fn_proto.getBodyNode().?.castTag(.Block).?;
-                const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
-                break :blk @intCast(u28, line_delta);
-            } else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| {
-                const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src;
-                const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off);
-                break :blk @intCast(u28, line_delta);
-            } else {
-                unreachable;
-            }
-        };
-
-        dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{
-            DW.LNS_extended_op,
-            @sizeOf(u64) + 1,
-            DW.LNE_set_address,
-        });
-        // This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`.
-        assert(DebugSymbols.dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len);
-        dbg_line_buffer.items.len += @sizeOf(u64);
-
-        dbg_line_buffer.appendAssumeCapacity(DW.LNS_advance_line);
-        // This is the "relocatable" relative line offset from the previous function's end curly
-        // to this function's begin curly.
-        assert(DebugSymbols.getRelocDbgLineOff() == dbg_line_buffer.items.len);
-        // Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later.
-        leb.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line_off);
-
-        dbg_line_buffer.appendAssumeCapacity(DW.LNS_set_file);
-        assert(DebugSymbols.getRelocDbgFileIndex() == dbg_line_buffer.items.len);
-        // Once we support more than one source file, this will have the ability to be more
-        // than one possible value.
-        const file_index = 1;
-        leb.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index);
-
-        // Emit a line for the begin curly with prologue_end=false. The codegen will
-        // do the work of setting prologue_end=true and epilogue_begin=true.
-        dbg_line_buffer.appendAssumeCapacity(DW.LNS_copy);
-
-        // .debug_info subprogram
-        const decl_name_with_null = decl.name[0 .. mem.lenZ(decl.name) + 1];
-        try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 27 + decl_name_with_null.len);
-
-        const fn_ret_type = typed_value.ty.fnReturnType();
-        const fn_ret_has_bits = fn_ret_type.hasCodeGenBits();
-        if (fn_ret_has_bits) {
-            dbg_info_buffer.appendAssumeCapacity(DebugSymbols.abbrev_subprogram);
-        } else {
-            dbg_info_buffer.appendAssumeCapacity(DebugSymbols.abbrev_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
-        // moved in virtual address space.
-        assert(DebugSymbols.dbg_info_low_pc_reloc_index == dbg_info_buffer.items.len);
-        dbg_info_buffer.items.len += @sizeOf(u64); // DW.AT_low_pc,  DW.FORM_addr
-        assert(DebugSymbols.getRelocDbgInfoSubprogramHighPC() == dbg_info_buffer.items.len);
-        dbg_info_buffer.items.len += 4; // DW.AT_high_pc,  DW.FORM_data4
-        if (fn_ret_has_bits) {
-            const gop = try dbg_info_type_relocs.getOrPut(self.base.allocator, fn_ret_type);
-            if (!gop.found_existing) {
-                gop.entry.value = .{
-                    .off = undefined,
-                    .relocs = .{},
-                };
-            }
-            try gop.entry.value.relocs.append(self.base.allocator, @intCast(u32, dbg_info_buffer.items.len));
-            dbg_info_buffer.items.len += 4; // DW.AT_type,  DW.FORM_ref4
-        }
-        dbg_info_buffer.appendSliceAssumeCapacity(decl_name_with_null); // DW.AT_name, DW.FORM_string
-        mem.writeIntLittle(u32, dbg_info_buffer.addManyAsArrayAssumeCapacity(4), line_off + 1); // DW.AT_decl_line, DW.FORM_data4
-        dbg_info_buffer.appendAssumeCapacity(file_index); // DW.AT_decl_file, DW.FORM_data1
-    } else {
-        // TODO implement .debug_info for global variables
-    }
-    const res = try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .{
-        .dwarf = .{
-            .dbg_line = &dbg_line_buffer,
-            .dbg_info = &dbg_info_buffer,
-            .dbg_info_type_relocs = &dbg_info_type_relocs,
-        },
-    });
+    const res = if (debug_buffers) |*dbg|
+        try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .{
+            .dwarf = .{
+                .dbg_line = &dbg.dbg_line_buffer,
+                .dbg_info = &dbg.dbg_info_buffer,
+                .dbg_info_type_relocs = &dbg.dbg_info_type_relocs,
+            },
+        })
+    else
+        try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .none);
 
     const code = switch (res) {
         .externally_managed => |x| x,
@@ -1328,132 +1228,16 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
     const file_offset = text_section.offset + section_offset;
     try self.base.file.?.pwriteAll(code, file_offset);
 
-    const text_block = &decl.link.macho;
-    // If the Decl is a function, we need to update the __debug_line program.
-    if (is_fn) {
-        // Perform the relocations based on vaddr.
-        {
-            const ptr = dbg_line_buffer.items[DebugSymbols.dbg_line_vaddr_reloc_index..][0..8];
-            mem.writeIntLittle(u64, ptr, symbol.n_value);
-        }
-        {
-            const ptr = dbg_info_buffer.items[DebugSymbols.dbg_info_low_pc_reloc_index..][0..8];
-            mem.writeIntLittle(u64, ptr, symbol.n_value);
-        }
-        {
-            const ptr = dbg_info_buffer.items[DebugSymbols.getRelocDbgInfoSubprogramHighPC()..][0..4];
-            mem.writeIntLittle(u32, ptr, @intCast(u32, text_block.size));
-        }
-
-        try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS_extended_op, 1, DW.LNE_end_sequence });
-
-        // Now we have the full contents and may allocate a region to store it.
-
-        // This logic is nearly identical to the logic below in `updateDeclDebugInfo` for
-        // `TextBlock` and the .debug_info. If you are editing this logic, you
-        // probably need to edit that logic too.
-
-        const dwarf_segment = &self.d_sym.?.load_commands.items[self.d_sym.?.dwarf_segment_cmd_index.?].Segment;
-        const debug_line_sect = &dwarf_segment.sections.items[self.d_sym.?.debug_line_section_index.?];
-        const src_fn = &decl.fn_link.macho;
-        src_fn.len = @intCast(u32, dbg_line_buffer.items.len);
-        if (self.d_sym.?.dbg_line_fn_last) |last| {
-            if (src_fn.next) |next| {
-                // Update existing function - non-last item.
-                if (src_fn.off + src_fn.len + DebugSymbols.min_nop_size > next.off) {
-                    // It grew too big, so we move it to a new location.
-                    if (src_fn.prev) |prev| {
-                        _ = self.d_sym.?.dbg_line_fn_free_list.put(self.base.allocator, prev, {}) catch {};
-                        prev.next = src_fn.next;
-                    }
-                    next.prev = src_fn.prev;
-                    src_fn.next = null;
-                    // Populate where it used to be with NOPs.
-                    const file_pos = debug_line_sect.offset + src_fn.off;
-                    try self.d_sym.?.pwriteDbgLineNops(0, &[0]u8{}, src_fn.len, file_pos);
-                    // TODO Look at the free list before appending at the end.
-                    src_fn.prev = last;
-                    last.next = src_fn;
-                    self.d_sym.?.dbg_line_fn_last = src_fn;
-
-                    src_fn.off = last.off + (last.len * alloc_num / alloc_den);
-                }
-            } else if (src_fn.prev == null) {
-                // Append new function.
-                // TODO Look at the free list before appending at the end.
-                src_fn.prev = last;
-                last.next = src_fn;
-                self.d_sym.?.dbg_line_fn_last = src_fn;
-
-                src_fn.off = last.off + (last.len * alloc_num / alloc_den);
-            }
-        } else {
-            // This is the first function of the Line Number Program.
-            self.d_sym.?.dbg_line_fn_first = src_fn;
-            self.d_sym.?.dbg_line_fn_last = src_fn;
-
-            src_fn.off = self.d_sym.?.dbgLineNeededHeaderBytes(module) * alloc_num / alloc_den;
-        }
-
-        const last_src_fn = self.d_sym.?.dbg_line_fn_last.?;
-        const needed_size = last_src_fn.off + last_src_fn.len;
-        if (needed_size != debug_line_sect.size) {
-            if (needed_size > dwarf_segment.allocatedSize(debug_line_sect.offset)) {
-                const new_offset = dwarf_segment.findFreeSpace(needed_size, 1, null);
-                const existing_size = last_src_fn.off;
-
-                log.debug("moving __debug_line section: {} bytes from 0x{x} to 0x{x}", .{
-                    existing_size,
-                    debug_line_sect.offset,
-                    new_offset,
-                });
-
-                const amt = try self.d_sym.?.file.copyRangeAll(debug_line_sect.offset, self.d_sym.?.file, new_offset, existing_size);
-                if (amt != existing_size) return error.InputOutput;
-                debug_line_sect.offset = @intCast(u32, new_offset);
-                debug_line_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff;
-            }
-            debug_line_sect.size = needed_size;
-            self.d_sym.?.load_commands_dirty = true; // TODO look into making only the one section dirty
-            self.d_sym.?.debug_line_header_dirty = true;
-        }
-        const prev_padding_size: u32 = if (src_fn.prev) |prev| src_fn.off - (prev.off + prev.len) else 0;
-        const next_padding_size: u32 = if (src_fn.next) |next| next.off - (src_fn.off + src_fn.len) else 0;
-
-        // We only have support for one compilation unit so far, so the offsets are directly
-        // from the .debug_line section.
-        const file_pos = debug_line_sect.offset + src_fn.off;
-        try self.d_sym.?.pwriteDbgLineNops(prev_padding_size, dbg_line_buffer.items, next_padding_size, file_pos);
-
-        // .debug_info - End the TAG_subprogram children.
-        try dbg_info_buffer.append(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.
-    var it = dbg_info_type_relocs.iterator();
-    while (it.next()) |entry| {
-        entry.value.off = @intCast(u32, dbg_info_buffer.items.len);
-        try self.d_sym.?.addDbgInfoType(entry.key, &dbg_info_buffer, self.base.options.target);
-    }
-
-    try self.d_sym.?.updateDeclDebugInfoAllocation(self.base.allocator, text_block, @intCast(u32, dbg_info_buffer.items.len));
-
-    // Now that we have the offset assigned we can finally perform type relocations.
-    it = dbg_info_type_relocs.iterator();
-    while (it.next()) |entry| {
-        for (entry.value.relocs.items) |off| {
-            mem.writeIntLittle(
-                u32,
-                dbg_info_buffer.items[off..][0..4],
-                text_block.dbg_info_off + entry.value.off,
-            );
-        }
+    if (debug_buffers) |*db| {
+        try self.d_sym.?.commitDeclDebugInfo(
+            self.base.allocator,
+            module,
+            decl,
+            db,
+            self.base.options.target,
+        );
     }
 
-    try self.d_sym.?.writeDeclDebugInfo(text_block, dbg_info_buffer.items);
-
     // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
     const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
     try self.updateDeclExports(module, decl, decl_exports);