Commit 02852098ee

Jakub Konka <kubkon@jakubkonka.com>
2022-11-09 16:47:00
aarch64: emit DWARF debug info for fn params and locals
We postpone emitting debug info until *after* we generate the function so that we have an idea of the consumed stack space. The stack offsets encoded within DWARF are with respect to the frame pointer `.fp`.
1 parent 7007ecd
Changed files (2)
src
arch
src/arch/aarch64/bits.zig
@@ -296,6 +296,13 @@ pub const Register = enum(u8) {
     pub fn dwarfLocOp(self: Register) u8 {
         return @as(u8, self.enc()) + DW.OP.reg0;
     }
+
+    /// DWARF encodings that push a value onto the DWARF stack that is either
+    /// the contents of a register or the result of adding the contents a given
+    /// register to a given signed offset.
+    pub fn dwarfLocOpDeref(self: Register) u8 {
+        return @as(u8, self.enc()) + DW.OP.breg0;
+    }
 };
 
 test "Register.enc" {
src/arch/aarch64/CodeGen.zig
@@ -51,13 +51,14 @@ gpa: Allocator,
 air: Air,
 liveness: Liveness,
 bin_file: *link.File,
+debug_output: DebugInfoOutput,
 target: *const std.Target,
 mod_fn: *const Module.Fn,
 err_msg: ?*ErrorMsg,
 args: []MCValue,
 ret_mcv: MCValue,
 fn_type: Type,
-arg_index: usize,
+arg_index: u32,
 src_loc: Module.SrcLoc,
 stack_align: u32,
 
@@ -75,6 +76,12 @@ end_di_column: u32,
 /// which is a relative jump, based on the address following the reloc.
 exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{},
 
+/// We postpone the creation of debug info for function args and locals
+/// until after all Mir instructions have been generated. Only then we
+/// will know saved_regs_stack_space which is necessary in order to
+/// calculate the right stack offsest with respect to the `.fp` register.
+dbg_info_relocs: std.ArrayListUnmanaged(DbgInfoReloc) = .{},
+
 /// Whenever there is a runtime branch, we push a Branch onto this stack,
 /// and pop it off when the runtime branch joins. This provides an "overlay"
 /// of the table of mappings from instructions to `MCValue` from within the branch.
@@ -160,6 +167,213 @@ const MCValue = union(enum) {
     stack_argument_offset: u32,
 };
 
+const DbgInfoReloc = struct {
+    tag: Air.Inst.Tag,
+    ty: Type,
+    name: [:0]const u8,
+    mcv: MCValue,
+
+    fn genDbgInfo(reloc: DbgInfoReloc, function: Self) !void {
+        switch (reloc.tag) {
+            .arg => try reloc.genArgDbgInfo(function),
+
+            .dbg_var_ptr,
+            .dbg_var_val,
+            => try reloc.genVarDbgInfo(function),
+
+            else => unreachable,
+        }
+    }
+
+    fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) error{OutOfMemory}!void {
+        const name_with_null = reloc.name.ptr[0 .. reloc.name.len + 1];
+
+        switch (function.debug_output) {
+            .dwarf => |dw| {
+                const dbg_info = &dw.dbg_info;
+                switch (reloc.mcv) {
+                    .register => |reg| {
+                        try dbg_info.ensureUnusedCapacity(3);
+                        dbg_info.appendAssumeCapacity(@enumToInt(link.File.Dwarf.AbbrevKind.parameter));
+                        dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
+                            1, // ULEB128 dwarf expression length
+                            reg.dwarfLocOp(),
+                        });
+                        try dbg_info.ensureUnusedCapacity(5 + name_with_null.len);
+                        try function.addDbgInfoTypeReloc(reloc.ty); // DW.AT.type,  DW.FORM.ref4
+                        dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string
+                    },
+
+                    .stack_offset,
+                    .stack_argument_offset,
+                    => |offset| {
+                        const adjusted_offset = switch (reloc.mcv) {
+                            .stack_offset => -@intCast(i32, offset),
+                            .stack_argument_offset => @intCast(i32, function.saved_regs_stack_space + offset),
+                            else => unreachable,
+                        };
+
+                        try dbg_info.ensureUnusedCapacity(8);
+                        dbg_info.appendAssumeCapacity(@enumToInt(link.File.Dwarf.AbbrevKind.parameter));
+                        const fixup = dbg_info.items.len;
+                        dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
+                            1, // we will backpatch it after we encode the displacement in LEB128
+                            Register.x29.dwarfLocOpDeref(), // frame pointer
+                        });
+                        leb128.writeILEB128(dbg_info.writer(), adjusted_offset) catch unreachable;
+                        dbg_info.items[fixup] += @intCast(u8, dbg_info.items.len - fixup - 2);
+                        try dbg_info.ensureUnusedCapacity(5 + name_with_null.len);
+                        try function.addDbgInfoTypeReloc(reloc.ty); // DW.AT.type,  DW.FORM.ref4
+                        dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string
+
+                    },
+
+                    else => unreachable, // not a possible argument
+                }
+            },
+            .plan9 => {},
+            .none => {},
+        }
+    }
+
+    fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) !void {
+        const name_with_null = reloc.name.ptr[0 .. reloc.name.len + 1];
+        const ty = switch (reloc.tag) {
+            .dbg_var_ptr => reloc.ty.childType(),
+            .dbg_var_val => reloc.ty,
+            else => unreachable,
+        };
+
+        switch (function.debug_output) {
+            .dwarf => |dw| {
+                const dbg_info = &dw.dbg_info;
+                try dbg_info.append(@enumToInt(link.File.Dwarf.AbbrevKind.variable));
+                const endian = function.target.cpu.arch.endian();
+
+                switch (reloc.mcv) {
+                    .register => |reg| {
+                        try dbg_info.ensureUnusedCapacity(2);
+                        dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
+                            1, // ULEB128 dwarf expression length
+                            reg.dwarfLocOp(),
+                        });
+                    },
+
+                    .ptr_stack_offset,
+                    .stack_offset,
+                    => |off| {
+                        try dbg_info.ensureUnusedCapacity(7);
+                        const fixup = dbg_info.items.len;
+                        dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
+                            1, // we will backpatch it after we encode the displacement in LEB128
+                            Register.x29.dwarfLocOpDeref(), // frame pointer
+                        });
+                        leb128.writeILEB128(dbg_info.writer(), -@intCast(i32, off)) catch unreachable;
+                        dbg_info.items[fixup] += @intCast(u8, dbg_info.items.len - fixup - 2);
+                    },
+
+                    .memory,
+                    .linker_load,
+                    => {
+                        const ptr_width = @intCast(u8, @divExact(function.target.cpu.arch.ptrBitWidth(), 8));
+                        const is_ptr = switch (reloc.tag) {
+                            .dbg_var_ptr => true,
+                            .dbg_var_val => false,
+                            else => unreachable,
+                        };
+                        try dbg_info.ensureUnusedCapacity(2 + ptr_width);
+                        dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
+                            1 + ptr_width + @boolToInt(is_ptr),
+                            DW.OP.addr, // literal address
+                        });
+                        const offset = @intCast(u32, dbg_info.items.len);
+                        const addr = switch (reloc.mcv) {
+                            .memory => |addr| addr,
+                            else => 0,
+                        };
+                        switch (ptr_width) {
+                            0...4 => {
+                                try dbg_info.writer().writeInt(u32, @intCast(u32, addr), endian);
+                            },
+                            5...8 => {
+                                try dbg_info.writer().writeInt(u64, addr, endian);
+                            },
+                            else => unreachable,
+                        }
+                        if (is_ptr) {
+                            // We need deref the address as we point to the value via GOT entry.
+                            try dbg_info.append(DW.OP.deref);
+                        }
+                        switch (reloc.mcv) {
+                            .linker_load => |load_struct| try dw.addExprlocReloc(
+                                load_struct.sym_index,
+                                offset,
+                                is_ptr,
+                            ),
+                            else => {},
+                        }
+                    },
+
+                    .immediate => |x| {
+                        try dbg_info.ensureUnusedCapacity(2);
+                        const fixup = dbg_info.items.len;
+                        dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
+                            1,
+                            if (ty.isSignedInt()) DW.OP.consts else DW.OP.constu,
+                        });
+                        if (ty.isSignedInt()) {
+                            try leb128.writeILEB128(dbg_info.writer(), @bitCast(i64, x));
+                        } else {
+                            try leb128.writeULEB128(dbg_info.writer(), x);
+                        }
+                        try dbg_info.append(DW.OP.stack_value);
+                        dbg_info.items[fixup] += @intCast(u8, dbg_info.items.len - fixup - 2);
+                    },
+
+                    .undef => {
+                        // DW.AT.location, DW.FORM.exprloc
+                        // uleb128(exprloc_len)
+                        // DW.OP.implicit_value uleb128(len_of_bytes) bytes
+                        const abi_size = @intCast(u32, ty.abiSize(function.target.*));
+                        var implicit_value_len = std.ArrayList(u8).init(function.gpa);
+                        defer implicit_value_len.deinit();
+                        try leb128.writeULEB128(implicit_value_len.writer(), abi_size);
+                        const total_exprloc_len = 1 + implicit_value_len.items.len + abi_size;
+                        try leb128.writeULEB128(dbg_info.writer(), total_exprloc_len);
+                        try dbg_info.ensureUnusedCapacity(total_exprloc_len);
+                        dbg_info.appendAssumeCapacity(DW.OP.implicit_value);
+                        dbg_info.appendSliceAssumeCapacity(implicit_value_len.items);
+                        dbg_info.appendNTimesAssumeCapacity(0xaa, abi_size);
+                    },
+
+                    .none => {
+                        try dbg_info.ensureUnusedCapacity(3);
+                        dbg_info.appendSliceAssumeCapacity(&[3]u8{ // DW.AT.location, DW.FORM.exprloc
+                            2, DW.OP.lit0, DW.OP.stack_value,
+                        });
+                    },
+
+                    .stack_argument_offset => unreachable,
+
+                    else => {
+                        try dbg_info.ensureUnusedCapacity(2);
+                        dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc
+                            1, DW.OP.nop,
+                        });
+                        log.debug("TODO generate debug info for {}", .{reloc.mcv});
+                    },
+                }
+
+                try dbg_info.ensureUnusedCapacity(5 + name_with_null.len);
+                try function.addDbgInfoTypeReloc(ty); // DW.AT.type,  DW.FORM.ref4
+                dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string
+            },
+            .plan9 => {},
+            .none => {},
+        }
+    }
+};
+
 const Branch = struct {
     inst_table: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, MCValue) = .{},
 
@@ -262,6 +476,7 @@ pub fn generate(
         .gpa = bin_file.allocator,
         .air = air,
         .liveness = liveness,
+        .debug_output = debug_output,
         .target = &bin_file.options.target,
         .bin_file = bin_file,
         .mod_fn = module_fn,
@@ -279,6 +494,7 @@ pub fn generate(
     defer function.stack.deinit(bin_file.allocator);
     defer function.blocks.deinit(bin_file.allocator);
     defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
+    defer function.dbg_info_relocs.deinit(bin_file.allocator);
 
     var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) {
         error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
@@ -302,6 +518,10 @@ pub fn generate(
         else => |e| return e,
     };
 
+    for (function.dbg_info_relocs.items) |reloc| {
+        try reloc.genDbgInfo(function);
+    }
+
     var mir = Mir{
         .instructions = function.mir_instructions.toOwnedSlice(),
         .extra = function.mir_extra.toOwnedSlice(bin_file.allocator),
@@ -854,23 +1074,20 @@ fn ensureProcessDeathCapacity(self: *Self, additional_count: usize) !void {
 
 /// Adds a Type to the .debug_info at the current position. The bytes will be populated later,
 /// after codegen for this symbol is done.
-fn addDbgInfoTypeReloc(self: *Self, ty: Type) !void {
+fn addDbgInfoTypeReloc(self: Self, ty: Type) !void {
     switch (self.debug_output) {
-        .dwarf => |dbg_out| {
-            assert(ty.hasRuntimeBits());
-            const index = dbg_out.dbg_info.items.len;
-            try dbg_out.dbg_info.resize(index + 4); // DW.AT.type,  DW.FORM.ref4
-
-            const gop = try dbg_out.dbg_info_type_relocs.getOrPutContext(self.gpa, ty, .{
-                .target = self.target.*,
-            });
-            if (!gop.found_existing) {
-                gop.value_ptr.* = .{
-                    .off = undefined,
-                    .relocs = .{},
-                };
-            }
-            try gop.value_ptr.relocs.append(self.gpa, @intCast(u32, index));
+        .dwarf => |dw| {
+            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 mod = self.bin_file.options.module.?;
+            const fn_owner_decl = mod.declPtr(self.mod_fn.owner_decl);
+            const atom = switch (self.bin_file.tag) {
+                .elf => &fn_owner_decl.link.elf.dbg_info_atom,
+                .macho => &fn_owner_decl.link.macho.dbg_info_atom,
+                else => unreachable,
+            };
+            try dw.addTypeRelocGlobal(atom, ty, @intCast(u32, index));
         },
         .plan9 => {},
         .none => {},
@@ -3872,8 +4089,9 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {
     self.arg_index += 1;
 
     const ty = self.air.typeOfIndex(inst);
-
     const result = self.args[arg_index];
+    const name = self.mod_fn.getParamName(self.bin_file.options.module.?, arg_index);
+
     const mcv = switch (result) {
         // Copy registers to the stack
         .register => |reg| blk: {
@@ -3889,8 +4107,14 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {
         },
         else => result,
     };
-    // TODO generate debug info
-    // try self.genArgDbgInfo(inst, mcv);
+
+    const tag = self.air.instructions.items(.tag)[inst];
+    try self.dbg_info_relocs.append(self.gpa, .{
+        .tag = tag,
+        .ty = ty,
+        .name = name,
+        .mcv = result,
+    });
 
     if (self.liveness.isUnused(inst))
         return self.finishAirBookkeeping();
@@ -4378,10 +4602,21 @@ fn airDbgBlock(self: *Self, inst: Air.Inst.Index) !void {
 
 fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void {
     const pl_op = self.air.instructions.items(.data)[inst].pl_op;
-    const name = self.air.nullTerminatedString(pl_op.payload);
     const operand = pl_op.operand;
-    // TODO emit debug info for this variable
-    _ = name;
+    const tag = self.air.instructions.items(.tag)[inst];
+    const ty = self.air.typeOf(operand);
+    const mcv = try self.resolveInst(operand);
+    const name = self.air.nullTerminatedString(pl_op.payload);
+
+    log.debug("airDbgVar: %{d}: {}, {}", .{ inst, ty.fmtDebug(), mcv });
+
+    try self.dbg_info_relocs.append(self.gpa, .{
+        .tag = tag,
+        .ty = ty,
+        .name = name,
+        .mcv = mcv,
+    });
+
     return self.finishAir(inst, .dead, .{ operand, .none, .none });
 }