Commit f697e0a326

Jacob G-W <jacoblevgw@gmail.com>
2021-08-29 23:25:18
plan9 linker: link lineinfo and filenames
1 parent 84ab03a
Changed files (3)
src/link/Plan9/aout.zig
@@ -34,6 +34,12 @@ pub const Sym = struct {
     type: Type,
     name: []const u8,
 
+    pub const undefined_symbol: Sym = .{
+        .value = undefined,
+        .type = .bad,
+        .name = "undefined_symbol",
+    };
+
     /// The type field is one of the following characters with the
     /// high bit set:
     /// T    text segment symbol
@@ -65,6 +71,8 @@ pub const Sym = struct {
         z = 0x80 | 'z',
         Z = 0x80 | 'Z',
         m = 0x80 | 'm',
+        /// represents an undefined symbol, to be removed in flush
+        bad = 0,
 
         pub fn toGlobal(self: Type) Type {
             return switch (self) {
src/link/Plan9.zig
@@ -20,6 +20,14 @@ const Allocator = std.mem.Allocator;
 const log = std.log.scoped(.link);
 const assert = std.debug.assert;
 
+const FnDeclOutput = struct {
+    code: []const u8,
+    /// this might have to be modified in the linker, so thats why its mutable
+    lineinfo: []u8,
+    start_line: u32,
+    end_line: u32,
+};
+
 base: link.File,
 sixtyfour_bit: bool,
 error_flags: File.ErrorFlags = File.ErrorFlags{},
@@ -27,9 +35,31 @@ bases: Bases,
 
 /// A symbol's value is just casted down when compiling
 /// for a 32 bit target.
+/// Does not represent the order or amount of symbols in the file
+/// it is just useful for storing symbols. Some other symbols are in
+/// file_segments.
 syms: std.ArrayListUnmanaged(aout.Sym) = .{},
 
-fn_decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, []const u8) = .{},
+/// The plan9 a.out format requires segments of
+/// filenames to be deduplicated, so we use this map to
+/// de duplicate it. The value is the value of the path
+/// component
+file_segments: std.StringArrayHashMapUnmanaged(u16) = .{},
+/// The value of a 'f' symbol increments by 1 every time, so that no 2 'f'
+/// symbols have the same value.
+file_segments_i: u16 = 1,
+
+path_arena: std.heap.ArenaAllocator,
+
+/// maps a file scope to a hash map of decl to codegen output
+/// this is useful for line debuginfo, since it makes sense to sort by file
+/// The debugger looks for the first file (aout.Sym.Type.z) preceeding the text symbol
+/// of the function to know what file it came from.
+/// If we group the decls by file, it makes it really easy to do this (put the symbol in the correct place)
+fn_decl_table: std.AutoArrayHashMapUnmanaged(
+    *Module.Scope.File,
+    struct { sym_index: u32, functions: std.AutoArrayHashMapUnmanaged(*Module.Decl, FnDeclOutput) = .{} },
+) = .{},
 data_decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, []const u8) = .{},
 
 hdr: aout.ExecHdr = undefined,
@@ -110,8 +140,12 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Plan9 {
         33...64 => true,
         else => return error.UnsupportedP9Architecture,
     };
+
+    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
+
     const self = try gpa.create(Plan9);
     self.* = .{
+        .path_arena = arena_allocator,
         .base = .{
             .tag = .plan9,
             .options = options,
@@ -122,9 +156,66 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Plan9 {
         .bases = undefined,
         .magic = try aout.magicFromArch(self.base.options.target.cpu.arch),
     };
+    // a / will always be in a file path
+    try self.file_segments.put(self.base.allocator, "/", 1);
     return self;
 }
 
+fn putFn(self: *Plan9, decl: *Module.Decl, out: FnDeclOutput) !void {
+    const gpa = self.base.allocator;
+    const fn_map_res = try self.fn_decl_table.getOrPut(gpa, decl.namespace.file_scope);
+    if (fn_map_res.found_existing) {
+        try fn_map_res.value_ptr.functions.put(gpa, decl, out);
+    } else {
+        const file = decl.namespace.file_scope;
+        const arena = &self.path_arena.allocator;
+        // each file gets a symbol
+        fn_map_res.value_ptr.* = .{
+            .sym_index = blk: {
+                try self.syms.append(gpa, undefined);
+                break :blk @intCast(u32, self.syms.items.len - 1);
+            },
+        };
+        try fn_map_res.value_ptr.functions.put(gpa, decl, out);
+
+        var a = std.ArrayList(u8).init(arena);
+        errdefer a.deinit();
+        // every 'z' starts with 0
+        try a.append(0);
+        // path component value of '/'
+        try a.writer().writeIntBig(u16, 1);
+
+        // getting the full file path
+        var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+        const dir = file.pkg.root_src_directory.path orelse try std.os.getcwd(&buf);
+        const sub_path = try std.fs.path.join(arena, &.{ dir, file.sub_file_path });
+        try self.addPathComponents(sub_path, &a);
+
+        // null terminate
+        try a.append(0);
+        const final = a.toOwnedSlice();
+        self.syms.items[fn_map_res.value_ptr.sym_index] = .{
+            .type = .z,
+            .value = 1,
+            .name = final,
+        };
+    }
+}
+
+fn addPathComponents(self: *Plan9, path: []const u8, a: *std.ArrayList(u8)) !void {
+    const sep = std.fs.path.sep;
+    var it = std.mem.tokenize(u8, path, &.{sep});
+    while (it.next()) |component| {
+        if (self.file_segments.get(component)) |num| {
+            try a.writer().writeIntBig(u16, num);
+        } else {
+            self.file_segments_i += 1;
+            try self.file_segments.put(self.base.allocator, component, self.file_segments_i);
+            try a.writer().writeIntBig(u16, self.file_segments_i);
+        }
+    }
+}
+
 pub fn updateFunc(self: *Plan9, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
     if (build_options.skip_non_native and builtin.object_format != .plan9) {
         @panic("Attempted to compile for object format that was disabled by build configuration");
@@ -139,7 +230,9 @@ pub fn updateFunc(self: *Plan9, module: *Module, func: *Module.Fn, air: Air, liv
     defer code_buffer.deinit();
     var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator);
     defer dbg_line_buffer.deinit();
-    var end_line: u32 = 0;
+    var start_line: ?u32 = null;
+    var end_line: u32 = undefined;
+    var pcop_change_index: ?u32 = null;
 
     const res = try codegen.generateFunction(
         &self.base,
@@ -152,6 +245,8 @@ pub fn updateFunc(self: *Plan9, module: *Module, func: *Module.Fn, air: Air, liv
             .plan9 = .{
                 .dbg_line = &dbg_line_buffer,
                 .end_line = &end_line,
+                .start_line = &start_line,
+                .pcop_change_index = &pcop_change_index,
             },
         },
     );
@@ -163,7 +258,13 @@ pub fn updateFunc(self: *Plan9, module: *Module, func: *Module.Fn, air: Air, liv
             return;
         },
     };
-    try self.fn_decl_table.put(self.base.allocator, decl, code);
+    const out: FnDeclOutput = .{
+        .code = code,
+        .lineinfo = dbg_line_buffer.toOwnedSlice(),
+        .start_line = start_line.?,
+        .end_line = end_line,
+    };
+    try self.putFn(decl, out);
     return self.updateFinish(decl);
 }
 
@@ -242,6 +343,30 @@ pub fn flush(self: *Plan9, comp: *Compilation) !void {
     return self.flushModule(comp);
 }
 
+pub fn changeLine(l: *std.ArrayList(u8), delta_line: i32) !void {
+    if (delta_line > 0 and delta_line < 65) {
+        const toappend = @intCast(u8, delta_line);
+        try l.append(toappend);
+    } else if (delta_line < 0 and delta_line > -65) {
+        const toadd: u8 = @intCast(u8, -delta_line + 64);
+        try l.append(toadd);
+    } else if (delta_line != 0) {
+        try l.append(0);
+        try l.writer().writeIntBig(i32, delta_line);
+    }
+}
+
+fn declCount(self: *Plan9) u64 {
+    var fn_decl_count: u64 = 0;
+    var itf_files = self.fn_decl_table.iterator();
+    while (itf_files.next()) |ent| {
+        // get the submap
+        var submap = ent.value_ptr.functions;
+        fn_decl_count += submap.count();
+    }
+    return self.data_decl_table.count() + fn_decl_count;
+}
+
 pub fn flushModule(self: *Plan9, comp: *Compilation) !void {
     if (build_options.skip_non_native and builtin.object_format != .plan9) {
         @panic("Attempted to compile for object format that was disabled by build configuration");
@@ -257,13 +382,13 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void {
 
     const mod = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
 
-    assert(self.got_len == self.fn_decl_table.count() + self.data_decl_table.count() + self.got_index_free_list.items.len);
+    assert(self.got_len == self.declCount() + self.got_index_free_list.items.len);
     const got_size = self.got_len * if (!self.sixtyfour_bit) @as(u32, 4) else 8;
     var got_table = try self.base.allocator.alloc(u8, got_size);
     defer self.base.allocator.free(got_table);
 
-    // + 3 for header, got, symbols
-    var iovecs = try self.base.allocator.alloc(std.os.iovec_const, self.fn_decl_table.count() + self.data_decl_table.count() + 3);
+    // + 4 for header, got, symbols, linecountinfo
+    var iovecs = try self.base.allocator.alloc(std.os.iovec_const, self.declCount() + 4);
     defer self.base.allocator.free(iovecs);
 
     const file = self.base.file.?;
@@ -276,30 +401,52 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void {
     iovecs[0] = .{ .iov_base = hdr_slice.ptr, .iov_len = hdr_slice.len };
     var iovecs_i: usize = 1;
     var text_i: u64 = 0;
+
+    var linecountinfo = std.ArrayList(u8).init(self.base.allocator);
+    defer linecountinfo.deinit();
     // text
     {
-        var it = self.fn_decl_table.iterator();
-        while (it.next()) |entry| {
-            const decl = entry.key_ptr.*;
-            const code = entry.value_ptr.*;
-            log.debug("write text decl {*} ({s})", .{ decl, decl.name });
-            foff += code.len;
-            iovecs[iovecs_i] = .{ .iov_base = code.ptr, .iov_len = code.len };
-            iovecs_i += 1;
-            const off = self.getAddr(text_i, .t);
-            text_i += code.len;
-            decl.link.plan9.offset = off;
-            if (!self.sixtyfour_bit) {
-                mem.writeIntNative(u32, got_table[decl.link.plan9.got_index.? * 4 ..][0..4], @intCast(u32, off));
-                mem.writeInt(u32, got_table[decl.link.plan9.got_index.? * 4 ..][0..4], @intCast(u32, off), self.base.options.target.cpu.arch.endian());
-            } else {
-                mem.writeInt(u64, got_table[decl.link.plan9.got_index.? * 8 ..][0..8], off, self.base.options.target.cpu.arch.endian());
-            }
-            self.syms.items[decl.link.plan9.sym_index.?].value = off;
-            if (mod.decl_exports.get(decl)) |exports| {
-                try self.addDeclExports(mod, decl, exports);
+        var linecount: u32 = 0;
+        var it_file = self.fn_decl_table.iterator();
+        while (it_file.next()) |fentry| {
+            var it = fentry.value_ptr.functions.iterator();
+            while (it.next()) |entry| {
+                const decl = entry.key_ptr.*;
+                const out = entry.value_ptr.*;
+                log.debug("write text decl {*} ({s}), lines {d} to {d}", .{ decl, decl.name, out.start_line, out.end_line });
+                {
+                    // connect the previous decl to the next
+                    const delta_line = @intCast(i32, out.start_line) - @intCast(i32, linecount);
+
+                    try changeLine(&linecountinfo, delta_line);
+                    // TODO change the pc too (maybe?)
+
+                    // write out the actual info that was generated in codegen now
+                    try linecountinfo.appendSlice(out.lineinfo);
+                    linecount = out.end_line;
+                }
+                foff += out.code.len;
+                iovecs[iovecs_i] = .{ .iov_base = out.code.ptr, .iov_len = out.code.len };
+                iovecs_i += 1;
+                const off = self.getAddr(text_i, .t);
+                text_i += out.code.len;
+                decl.link.plan9.offset = off;
+                if (!self.sixtyfour_bit) {
+                    mem.writeIntNative(u32, got_table[decl.link.plan9.got_index.? * 4 ..][0..4], @intCast(u32, off));
+                    mem.writeInt(u32, got_table[decl.link.plan9.got_index.? * 4 ..][0..4], @intCast(u32, off), self.base.options.target.cpu.arch.endian());
+                } else {
+                    mem.writeInt(u64, got_table[decl.link.plan9.got_index.? * 8 ..][0..8], off, self.base.options.target.cpu.arch.endian());
+                }
+                self.syms.items[decl.link.plan9.sym_index.?].value = off;
+                if (mod.decl_exports.get(decl)) |exports| {
+                    try self.addDeclExports(mod, decl, exports);
+                }
             }
         }
+        if (linecountinfo.items.len & 1 == 1) {
+            // just a nop to make it even, the plan9 linker does this
+            try linecountinfo.append(129);
+        }
         // etext symbol
         self.syms.items[2].value = self.getAddr(text_i, .t);
     }
@@ -337,20 +484,23 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void {
     // edata
     self.syms.items[1].value = self.getAddr(0x0, .b);
     var sym_buf = std.ArrayList(u8).init(self.base.allocator);
-    defer sym_buf.deinit();
     try self.writeSyms(&sym_buf);
-    assert(2 + self.fn_decl_table.count() + self.data_decl_table.count() == iovecs_i); // we didn't write all the decls
-    iovecs[iovecs_i] = .{ .iov_base = sym_buf.items.ptr, .iov_len = sym_buf.items.len };
+    const syms = sym_buf.toOwnedSlice();
+    defer self.base.allocator.free(syms);
+    assert(2 + self.declCount() == iovecs_i); // we didn't write all the decls
+    iovecs[iovecs_i] = .{ .iov_base = syms.ptr, .iov_len = syms.len };
+    iovecs_i += 1;
+    iovecs[iovecs_i] = .{ .iov_base = linecountinfo.items.ptr, .iov_len = linecountinfo.items.len };
     iovecs_i += 1;
     // generate the header
     self.hdr = .{
         .magic = self.magic,
         .text = @intCast(u32, text_i),
         .data = @intCast(u32, data_i),
-        .syms = @intCast(u32, sym_buf.items.len),
+        .syms = @intCast(u32, syms.len),
         .bss = 0,
-        .pcsz = 0,
         .spsz = 0,
+        .pcsz = @intCast(u32, linecountinfo.items.len),
         .entry = @intCast(u32, self.entry_val.?),
     };
     std.mem.copy(u8, hdr_slice, self.hdr.toU8s()[0..hdr_size]);
@@ -397,7 +547,15 @@ pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void {
     // in the deleteUnusedDecl function.
     const is_fn = (decl.val.tag() == .function);
     if (is_fn) {
-        _ = self.fn_decl_table.swapRemove(decl);
+        var symidx_and_submap =
+            self.fn_decl_table.get(decl.namespace.file_scope).?;
+        var submap = symidx_and_submap.functions;
+        _ = submap.swapRemove(decl);
+        if (submap.count() == 0) {
+            self.syms.items[symidx_and_submap.sym_index] = aout.Sym.undefined_symbol;
+            self.syms_index_free_list.append(self.base.allocator, symidx_and_submap.sym_index) catch {};
+            submap.deinit(self.base.allocator);
+        }
     } else {
         _ = self.data_decl_table.swapRemove(decl);
     }
@@ -407,7 +565,7 @@ pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void {
     }
     if (decl.link.plan9.sym_index) |i| {
         self.syms_index_free_list.append(self.base.allocator, i) catch {};
-        self.syms.items[i] = undefined;
+        self.syms.items[i] = aout.Sym.undefined_symbol;
     }
 }
 
@@ -436,19 +594,29 @@ pub fn updateDeclExports(
     _ = exports;
 }
 pub fn deinit(self: *Plan9) void {
-    var itf = self.fn_decl_table.iterator();
-    while (itf.next()) |entry| {
-        self.base.allocator.free(entry.value_ptr.*);
+    const gpa = self.base.allocator;
+    var itf_files = self.fn_decl_table.iterator();
+    while (itf_files.next()) |ent| {
+        // get the submap
+        var submap = ent.value_ptr.functions;
+        defer submap.deinit(gpa);
+        var itf = submap.iterator();
+        while (itf.next()) |entry| {
+            gpa.free(entry.value_ptr.code);
+            gpa.free(entry.value_ptr.lineinfo);
+        }
     }
-    self.fn_decl_table.deinit(self.base.allocator);
+    self.fn_decl_table.deinit(gpa);
     var itd = self.data_decl_table.iterator();
     while (itd.next()) |entry| {
-        self.base.allocator.free(entry.value_ptr.*);
+        gpa.free(entry.value_ptr.*);
     }
-    self.data_decl_table.deinit(self.base.allocator);
-    self.syms.deinit(self.base.allocator);
-    self.got_index_free_list.deinit(self.base.allocator);
-    self.syms_index_free_list.deinit(self.base.allocator);
+    self.data_decl_table.deinit(gpa);
+    self.syms.deinit(gpa);
+    self.got_index_free_list.deinit(gpa);
+    self.syms_index_free_list.deinit(gpa);
+    self.file_segments.deinit(gpa);
+    self.path_arena.deinit();
 }
 
 pub const Export = ?usize;
@@ -458,7 +626,6 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
         return error.LLVMBackendDoesNotSupportPlan9;
     assert(options.object_format == .plan9);
     const file = try options.emit.?.directory.handle.createFile(sub_path, .{
-        .truncate = false,
         .read = true,
         .mode = link.determineMode(options),
     });
@@ -492,21 +659,72 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
     return self;
 }
 
+pub fn writeSym(self: *Plan9, w: anytype, sym: aout.Sym) !void {
+    log.debug("write sym.name: {s}", .{sym.name});
+    log.debug("write sym.value: {x}", .{sym.value});
+    if (sym.type == .bad) return; // we don't want to write free'd symbols
+    if (!self.sixtyfour_bit) {
+        try w.writeIntBig(u32, @intCast(u32, sym.value));
+    } else {
+        try w.writeIntBig(u64, sym.value);
+    }
+    try w.writeByte(@enumToInt(sym.type));
+    try w.writeAll(sym.name);
+    try w.writeByte(0);
+}
 pub fn writeSyms(self: *Plan9, buf: *std.ArrayList(u8)) !void {
     const writer = buf.writer();
-    for (self.syms.items) |sym| {
-        log.debug("sym.name: {s}", .{sym.name});
-        log.debug("sym.value: {x}", .{sym.value});
-        if (mem.eql(u8, sym.name, "_start"))
-            self.entry_val = sym.value;
-        if (!self.sixtyfour_bit) {
-            try writer.writeIntBig(u32, @intCast(u32, sym.value));
-        } else {
-            try writer.writeIntBig(u64, sym.value);
+    // write the f symbols
+    {
+        var it = self.file_segments.iterator();
+        while (it.next()) |entry| {
+            try self.writeSym(writer, .{
+                .type = .f,
+                .value = entry.value_ptr.*,
+                .name = entry.key_ptr.*,
+            });
+        }
+    }
+    // write the data symbols
+    {
+        var it = self.data_decl_table.iterator();
+        while (it.next()) |entry| {
+            const decl = entry.key_ptr.*;
+            const sym = self.syms.items[decl.link.plan9.sym_index.?];
+            try self.writeSym(writer, sym);
+            if (self.base.options.module.?.decl_exports.get(decl)) |exports| {
+                for (exports) |e| {
+                    try self.writeSym(writer, self.syms.items[e.link.plan9.?]);
+                }
+            }
+        }
+    }
+    // text symbols are the hardest:
+    // the file of a text symbol is the .z symbol before it
+    // so we have to write everything in the right order
+    {
+        var it_file = self.fn_decl_table.iterator();
+        while (it_file.next()) |fentry| {
+            var symidx_and_submap = fentry.value_ptr;
+            // write the z symbol
+            try self.writeSym(writer, self.syms.items[symidx_and_submap.sym_index]);
+
+            // write all the decls come from the file of the z symbol
+            var submap_it = symidx_and_submap.functions.iterator();
+            while (submap_it.next()) |entry| {
+                const decl = entry.key_ptr.*;
+                const sym = self.syms.items[decl.link.plan9.sym_index.?];
+                try self.writeSym(writer, sym);
+                if (self.base.options.module.?.decl_exports.get(decl)) |exports| {
+                    for (exports) |e| {
+                        const s = self.syms.items[e.link.plan9.?];
+                        if (mem.eql(u8, s.name, "_start"))
+                            self.entry_val = s.value;
+                        try self.writeSym(writer, s);
+                    }
+                }
+            }
         }
-        try writer.writeByte(@enumToInt(sym.type));
-        try writer.writeAll(sym.name);
-        try writer.writeByte(0);
     }
 }
 
src/codegen.zig
@@ -52,16 +52,23 @@ pub const DebugInfoOutput = union(enum) {
     /// 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
     /// x when x < 65 -> add x to line offset
-    /// x when x < 129 -> subtract 64 from x and add it to the line offset
+    /// x when x < 129 -> subtract 64 from x and subtract it from the line offset
     /// x -> subtract 129 from x, multiply it by the quanta of the instruction size
     /// (1 on x86_64), and add it to the pc
     /// after every opcode, add the quanta of the instruction size to the pc
     plan9: struct {
         /// the actual opcodes
         dbg_line: *std.ArrayList(u8),
+        /// what line the debuginfo starts on
+        /// this helps because the linker might have to insert some opcodes to make sure that the line count starts at the right amount for the next decl
+        start_line: *?u32,
         /// what the line count ends on after codegen
         /// this helps because the linker might have to insert some opcodes to make sure that the line count starts at the right amount for the next decl
         end_line: *u32,
+        /// the last pc change op
+        /// This is very useful for adding quanta
+        /// to it if its not actually the last one.
+        pcop_change_index: *?u32,
     },
     none,
 };
@@ -946,7 +953,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
 
         fn dbgAdvancePCAndLine(self: *Self, line: u32, column: u32) InnerError!void {
             const delta_line = @intCast(i32, line) - @intCast(i32, self.prev_di_line);
-            const delta_pc = self.code.items.len - self.prev_di_pc;
+            const delta_pc: usize = self.code.items.len - self.prev_di_pc;
             switch (self.debug_output) {
                 .dwarf => |dbg_out| {
                     // TODO Look into using the DWARF special opcodes to compress this data.
@@ -960,30 +967,39 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         leb128.writeILEB128(dbg_out.dbg_line.writer(), delta_line) catch unreachable;
                     }
                     dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.copy);
+                    self.prev_di_pc = self.code.items.len;
+                    self.prev_di_line = line;
+                    self.prev_di_column = column;
+                    self.prev_di_pc = self.code.items.len;
                 },
                 .plan9 => |dbg_out| {
+                    if (delta_pc <= 0) return; // only do this when the pc changes
                     // we have already checked the target in the linker to make sure it is compatable
                     const quant = @import("link/Plan9/aout.zig").getPCQuant(self.target.cpu.arch) catch unreachable;
 
                     // increasing the line number
-                    if (delta_line > 0 and delta_line < 65) {
-                        try dbg_out.dbg_line.append(@intCast(u8, delta_line));
-                    } else if (delta_line < 0 and delta_line > -65) {
-                        try dbg_out.dbg_line.append(@intCast(u8, -delta_line + 64));
-                    } else if (delta_line != 0) {
-                        try dbg_out.dbg_line.writer().writeIntBig(i32, delta_line);
-                    }
+                    try @import("link/Plan9.zig").changeLine(dbg_out.dbg_line, delta_line);
                     // increasing the pc
-                    if (delta_pc - quant != 0) {
-                        try dbg_out.dbg_line.append(@intCast(u8, delta_pc - quant + 129));
-                    }
+                    const d_pc_p9 = @intCast(i64, delta_pc) - quant;
+                    if (d_pc_p9 > 0) {
+                        // minus one becaue if its the last one, we want to leave space to change the line which is one quanta
+                        try dbg_out.dbg_line.append(@intCast(u8, @divExact(d_pc_p9, quant) + 128) - quant);
+                        if (dbg_out.pcop_change_index.*) |pci|
+                            dbg_out.dbg_line.items[pci] += 1;
+                        dbg_out.pcop_change_index.* = @intCast(u32, dbg_out.dbg_line.items.len - 1);
+                    } else if (d_pc_p9 == 0) {
+                        // we don't need to do anything, because adding the quant does it for us
+                    } else unreachable;
+                    if (dbg_out.start_line.* == null)
+                        dbg_out.start_line.* = self.prev_di_line;
                     dbg_out.end_line.* = line;
+                    // only do this if the pc changed
+                    self.prev_di_line = line;
+                    self.prev_di_column = column;
+                    self.prev_di_pc = self.code.items.len;
                 },
                 .none => {},
             }
-            self.prev_di_line = line;
-            self.prev_di_column = column;
-            self.prev_di_pc = self.code.items.len;
         }
 
         /// Asserts there is already capacity to insert into top branch inst_table.