authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-02-13 11:48:56+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-03-10 10:26:12+00:00
log2b8feabb8f2b3a3c96d2d8e74393e0c8dfa17390
tree2860038efbf658a07a8b305205bf3e92e7aa6260
parent6402e119e8d21403cbafc4a6c8ca353b894592ba
signaturelock-open Commit is signed but in an unrecognized format.

llvm: some more improvements to debug info

Most importantly, adds support for `DW_TAG_typedef` to `llvm.Builder`, and uses it to define error sets and optional pointers/errors. Also deletes some random dead code I found.

2 files changed, 142 insertions(+), 173 deletions(-)

lib/std/zig/llvm/Builder.zig+58
......@@ -8021,6 +8021,7 @@ pub const Metadata = packed struct(u32) {
80218021 composite_vector_type,
80228022 derived_pointer_type,
80238023 derived_member_type,
8024 derived_typedef_type,
80248025 subroutine_type,
80258026 enumerator_unsigned,
80268027 enumerator_signed_positive,
......@@ -8064,6 +8065,7 @@ pub const Metadata = packed struct(u32) {
80648065 .composite_vector_type,
80658066 .derived_pointer_type,
80668067 .derived_member_type,
8068 .derived_typedef_type,
80678069 .subroutine_type,
80688070 .enumerator_unsigned,
80698071 .enumerator_signed_positive,
......@@ -10463,15 +10465,18 @@ pub fn print(self: *Builder, w: *Writer) (Writer.Error || Allocator.Error)!void
1046310465 },
1046410466 .derived_pointer_type,
1046510467 .derived_member_type,
10468 .derived_typedef_type,
1046610469 => |kind| {
1046710470 const extra = self.metadataExtraData(Metadata.DerivedType, metadata_item.data);
1046810471 try metadata_formatter.specialized(.@"!", .DIDerivedType, .{
1046910472 .tag = @as(enum {
1047010473 DW_TAG_pointer_type,
1047110474 DW_TAG_member,
10475 DW_TAG_typedef,
1047210476 }, switch (kind) {
1047310477 .derived_pointer_type => .DW_TAG_pointer_type,
1047410478 .derived_member_type => .DW_TAG_member,
10479 .derived_typedef_type => .DW_TAG_typedef,
1047510480 else => unreachable,
1047610481 }),
1047710482 .name = extra.name,
......@@ -12360,6 +12365,30 @@ pub fn debugMemberType(
1236012365 );
1236112366}
1236212367
12368pub fn debugTypedefType(
12369 self: *Builder,
12370 name: ?Metadata.String,
12371 file: ?Metadata,
12372 scope: ?Metadata,
12373 line: u32,
12374 underlying_type: ?Metadata,
12375 size_in_bits: u64,
12376 align_in_bits: u64,
12377 offset_in_bits: u64,
12378) Allocator.Error!Metadata {
12379 try self.ensureUnusedMetadataCapacity(1, Metadata.DerivedType, 0);
12380 return self.debugTypedefTypeAssumeCapacity(
12381 name,
12382 file,
12383 scope,
12384 line,
12385 underlying_type,
12386 size_in_bits,
12387 align_in_bits,
12388 offset_in_bits,
12389 );
12390}
12391
1236312392pub fn debugSubroutineType(self: *Builder, types_tuple: ?Metadata) Allocator.Error!Metadata {
1236412393 try self.ensureUnusedMetadataCapacity(1, Metadata.SubroutineType, 0);
1236512394 return self.debugSubroutineTypeAssumeCapacity(types_tuple);
......@@ -12875,6 +12904,33 @@ fn debugMemberTypeAssumeCapacity(
1287512904 });
1287612905}
1287712906
12907fn debugTypedefTypeAssumeCapacity(
12908 self: *Builder,
12909 name: ?Metadata.String,
12910 file: ?Metadata,
12911 scope: ?Metadata,
12912 line: u32,
12913 underlying_type: ?Metadata,
12914 size_in_bits: u64,
12915 align_in_bits: u64,
12916 offset_in_bits: u64,
12917) Metadata {
12918 assert(!self.strip);
12919 return self.metadataSimpleAssumeCapacity(.derived_typedef_type, Metadata.DerivedType{
12920 .name = .wrap(name),
12921 .file = .wrap(file),
12922 .scope = .wrap(scope),
12923 .line = line,
12924 .underlying_type = .wrap(underlying_type),
12925 .size_in_bits_lo = @truncate(size_in_bits),
12926 .size_in_bits_hi = @truncate(size_in_bits >> 32),
12927 .align_in_bits_lo = @truncate(align_in_bits),
12928 .align_in_bits_hi = @truncate(align_in_bits >> 32),
12929 .offset_in_bits_lo = @truncate(offset_in_bits),
12930 .offset_in_bits_hi = @truncate(offset_in_bits >> 32),
12931 });
12932}
12933
1287812934fn debugSubroutineTypeAssumeCapacity(self: *Builder, types_tuple: ?Metadata) Metadata {
1287912935 assert(!self.strip);
1288012936 return self.metadataSimpleAssumeCapacity(.subroutine_type, Metadata.SubroutineType{
......@@ -14223,12 +14279,14 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco
1422314279 },
1422414280 .derived_pointer_type,
1422514281 .derived_member_type,
14282 .derived_typedef_type,
1422614283 => |kind| {
1422714284 const extra = self.metadataExtraData(Metadata.DerivedType, data);
1422814285 try metadata_block.writeAbbrevAdapted(MetadataBlock.DerivedType{
1422914286 .tag = switch (kind) {
1423014287 .derived_pointer_type => DW.TAG.pointer_type,
1423114288 .derived_member_type => DW.TAG.member,
14289 .derived_typedef_type => DW.TAG.typedef,
1423214290 else => unreachable,
1423314291 },
1423414292 .name = extra.name,
src/codegen/llvm.zig+84-173
......@@ -1914,6 +1914,15 @@ pub const Object = struct {
19141914
19151915 const name = try o.builder.metadataStringFmt("{f}", .{ty.fmt(pt)});
19161916
1917 // lldb cannot handle non-byte-sized types, so in the logic below, bit sizes are padded up.
1918 // For instance, `bool` is considered to be 8 bits, and `u60` is considered to be 64 bits.
1919
1920 // I tried using variants (DW_TAG_variant_part + DW_TAG_variant) to encode error unions,
1921 // tagged unions, etc; this would have told debuggers which field was active, which could
1922 // improve UX significantly. GDB handles this perfectly fine, but unfortunately, LLDB has no
1923 // handling for variants at all, and will never print fields in them, so I opted not to use
1924 // them for now.
1925
19171926 switch (ty.zigTypeTag(zcu)) {
19181927 .void,
19191928 .noreturn,
......@@ -1925,23 +1934,19 @@ pub const Object = struct {
19251934 .enum_literal,
19261935 => return o.builder.debugSignedType(name, 0),
19271936
1937 .float => return o.builder.debugFloatType(name, ty.floatBits(target)),
1938
1939 .bool => return o.builder.debugBoolType(name, 8),
1940
19281941 .int => {
19291942 const info = ty.intInfo(zcu);
1930 const bits = ty.abiSize(zcu) * 8; // lldb cannot handle non-byte sized types
1943 const bits = ty.abiSize(zcu) * 8;
19311944 return switch (info.signedness) {
19321945 .signed => try o.builder.debugSignedType(name, bits),
19331946 .unsigned => try o.builder.debugUnsignedType(name, bits),
19341947 };
19351948 },
1936 .float => {
1937 return o.builder.debugFloatType(name, ty.floatBits(target));
1938 },
1939 .bool => {
1940 return o.builder.debugBoolType(
1941 name,
1942 8, // lldb cannot handle non-byte sized types
1943 );
1944 },
1949
19451950 .pointer => {
19461951 const ptr_size = Type.ptrAbiSize(zcu.getTarget());
19471952 const ptr_align = Type.ptrAbiAlignment(zcu.getTarget());
......@@ -1949,20 +1954,20 @@ pub const Object = struct {
19491954 if (ty.isSlice(zcu)) {
19501955 const debug_ptr_type = try o.builder.debugMemberType(
19511956 try o.builder.metadataString("ptr"),
1952 null, // File
1957 null, // file
19531958 ty_fwd_ref,
1954 0, // Line
1959 0, // line
19551960 try o.getDebugType(pt, ty.slicePtrFieldType(zcu)),
19561961 ptr_size * 8,
19571962 ptr_align.toByteUnits().? * 8,
1958 0, // Offset
1963 0, // offset
19591964 );
19601965
19611966 const debug_len_type = try o.builder.debugMemberType(
19621967 try o.builder.metadataString("len"),
1963 null, // File
1968 null, // file
19641969 ty_fwd_ref,
1965 0, // Line
1970 0, // line
19661971 try o.getDebugType(pt, .usize),
19671972 ptr_size * 8,
19681973 ptr_align.toByteUnits().? * 8,
......@@ -1971,10 +1976,10 @@ pub const Object = struct {
19711976
19721977 return o.builder.debugStructType(
19731978 name,
1974 null, // File
1975 o.debug_compile_unit.unwrap().?, // Scope
1976 0, // Line
1977 null, // Underlying type
1979 null, // file
1980 o.debug_compile_unit.unwrap().?, // scope
1981 0, // line
1982 null, // underlying type
19781983 ptr_size * 2 * 8,
19791984 ptr_align.toByteUnits().? * 8,
19801985 try o.builder.metadataTuple(&.{
......@@ -1986,36 +1991,34 @@ pub const Object = struct {
19861991
19871992 return o.builder.debugPointerType(
19881993 name,
1989 null, // File
1990 null, // Scope
1991 0, // Line
1994 null, // file
1995 o.debug_compile_unit.unwrap().?, // scope
1996 0, // line
19921997 try o.getDebugType(pt, ty.childType(zcu)),
19931998 ptr_size * 8,
19941999 ptr_align.toByteUnits().? * 8,
1995 0, // Offset
1996 );
1997 },
1998 .array => {
1999 return o.builder.debugArrayType(
2000 null, // Name
2001 null, // File
2002 null, // Scope
2003 0, // Line
2004 try o.getDebugType(pt, ty.childType(zcu)),
2005 ty.abiSize(zcu) * 8,
2006 (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
2007 try o.builder.metadataTuple(&.{
2008 try o.builder.debugSubrange(
2009 try o.builder.metadataConstant(try o.builder.intConst(.i64, 0)),
2010 try o.builder.metadataConstant(try o.builder.intConst(.i64, ty.arrayLen(zcu))),
2011 ),
2012 }),
2000 0, // offset
20132001 );
20142002 },
2003 .array => return o.builder.debugArrayType(
2004 name,
2005 null, // file
2006 o.debug_compile_unit.unwrap().?, // scope
2007 0, // line
2008 try o.getDebugType(pt, ty.childType(zcu)),
2009 ty.abiSize(zcu) * 8,
2010 ty.abiAlignment(zcu).toByteUnits().? * 8,
2011 try o.builder.metadataTuple(&.{
2012 try o.builder.debugSubrange(
2013 try o.builder.metadataConstant(try o.builder.intConst(.i64, 0)),
2014 try o.builder.metadataConstant(try o.builder.intConst(.i64, ty.arrayLen(zcu))),
2015 ),
2016 }),
2017 ),
20152018 .vector => {
20162019 const elem_ty = ty.childType(zcu);
20172020 // Vector elements cannot be padded since that would make
2018 // @bitSizOf(elem) * len > @bitSizOf(vec).
2021 // @bitSizeOf(elem) * len > @bitSizOf(vec).
20192022 // Neither gdb nor lldb seem to be able to display non-byte sized
20202023 // vectors properly.
20212024 const debug_elem_type = switch (elem_ty.zigTypeTag(zcu)) {
......@@ -2027,18 +2030,19 @@ pub const Object = struct {
20272030 };
20282031 },
20292032 .bool => try o.builder.debugBoolType(try o.builder.metadataString("bool"), 1),
2033 // We don't pad pointers or floats, so we can lower those normally.
20302034 .pointer, .optional, .float => try o.getDebugType(pt, elem_ty),
20312035 else => unreachable,
20322036 };
20332037
20342038 return o.builder.debugVectorType(
2035 null, // Name
2036 null, // File
2037 null, // Scope
2038 0, // Line
2039 name,
2040 null, // file
2041 o.debug_compile_unit.unwrap().?, // scope
2042 0, // line
20392043 debug_elem_type,
20402044 ty.abiSize(zcu) * 8,
2041 (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
2045 ty.abiAlignment(zcu).toByteUnits().? * 8,
20422046 try o.builder.metadataTuple(&.{
20432047 try o.builder.debugSubrange(
20442048 try o.builder.metadataConstant(try o.builder.intConst(.i64, 0)),
......@@ -2050,26 +2054,15 @@ pub const Object = struct {
20502054 .optional => {
20512055 const payload_ty = ty.optionalChild(zcu);
20522056 if (ty.optionalReprIsPayload(zcu)) {
2053 // MLUGG TODO: these should use DW_TAG_typedef instead, but std.zig.llvm.Builder currently lacks support for those.
2054 const payload_member = try o.builder.debugMemberType(
2055 try o.builder.metadataString("payload"),
2056 null, // file
2057 ty_fwd_ref,
2058 0, // line
2059 try o.getDebugType(pt, .anyerror),
2060 ty.abiSize(zcu) * 8,
2061 ty.abiAlignment(zcu).toByteUnits().? * 8,
2062 0, // offset
2063 );
2064 return o.builder.debugStructType(
2057 return o.builder.debugTypedefType(
20652058 name,
20662059 null, // file
20672060 o.debug_compile_unit.unwrap().?, // scope
20682061 0, // line
2069 null, // underlying type
2062 try o.getDebugType(pt, payload_ty),
20702063 ty.abiSize(zcu) * 8,
20712064 ty.abiAlignment(zcu).toByteUnits().? * 8,
2072 try o.builder.metadataTuple(&.{payload_member}),
2065 0, // offset
20732066 );
20742067 }
20752068
......@@ -2083,7 +2076,7 @@ pub const Object = struct {
20832076 const debug_payload_type = try o.builder.debugMemberType(
20842077 try o.builder.metadataString("payload"),
20852078 null, // file
2086 ty_fwd_ref,
2079 ty_fwd_ref, // scope
20872080 0, // line
20882081 try o.getDebugType(pt, payload_ty),
20892082 payload_size * 8,
......@@ -2104,12 +2097,12 @@ pub const Object = struct {
21042097
21052098 return o.builder.debugStructType(
21062099 name,
2107 null, // File
2108 o.debug_compile_unit.unwrap().?, // Scope
2109 0, // Line
2110 null, // Underlying type
2100 null, // file
2101 o.debug_compile_unit.unwrap().?, // scope
2102 0, // line
2103 null, // underlying type
21112104 ty.abiSize(zcu) * 8,
2112 (ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
2105 ty.abiAlignment(zcu).toByteUnits().? * 8,
21132106 try o.builder.metadataTuple(&.{
21142107 debug_payload_type,
21152108 debug_some_type,
......@@ -2125,30 +2118,29 @@ pub const Object = struct {
21252118 const payload_size = payload_ty.abiSize(zcu);
21262119 const payload_align = payload_ty.abiAlignment(zcu);
21272120
2128 const error_index: u1, const payload_index: u1, const error_offset: u64, const payload_offset: u64 = fields: {
2121 const error_offset: u64, const payload_offset: u64 = offsets: {
21292122 if (error_align.compare(.gt, payload_align)) {
2130 break :fields .{ 0, 1, 0, payload_align.forward(error_size) };
2123 break :offsets .{ 0, payload_align.forward(error_size) };
21312124 } else {
2132 break :fields .{ 1, 0, error_align.forward(payload_size), 0 };
2125 break :offsets .{ error_align.forward(payload_size), 0 };
21332126 }
21342127 };
21352128
2136 var fields: [2]Builder.Metadata = undefined;
2137 fields[error_index] = try o.builder.debugMemberType(
2129 const error_field = try o.builder.debugMemberType(
21382130 try o.builder.metadataString("error"),
2139 null, // File
2131 null, // file
21402132 ty_fwd_ref,
2141 0, // Line
2133 0, // line
21422134 try o.getDebugType(pt, error_ty),
21432135 error_size * 8,
21442136 error_align.toByteUnits().? * 8,
21452137 error_offset * 8,
21462138 );
2147 fields[payload_index] = try o.builder.debugMemberType(
2139 const payload_field = try o.builder.debugMemberType(
21482140 try o.builder.metadataString("payload"),
2149 null, // File
2150 ty_fwd_ref,
2151 0, // Line
2141 null, // file
2142 ty_fwd_ref, // scope
2143 0, // line
21522144 try o.getDebugType(pt, payload_ty),
21532145 payload_size * 8,
21542146 payload_align.toByteUnits().? * 8,
......@@ -2163,32 +2155,21 @@ pub const Object = struct {
21632155 null, // Underlying type
21642156 ty.abiSize(zcu) * 8,
21652157 ty.abiAlignment(zcu).toByteUnits().? * 8,
2166 try o.builder.metadataTuple(&fields),
2158 try o.builder.metadataTuple(&.{ error_field, payload_field }),
21672159 );
21682160 },
21692161 .error_set => {
21702162 assert(ty.toIntern() != .anyerror_type); // handled specially in `updateConst`; will be populated by `emit` instead
21712163 // Error sets are just named wrappers around `anyerror`.
2172 // MLUGG TODO: these should use DW_TAG_typedef instead, but std.zig.llvm.Builder currently lacks support for those.
2173 const anyerror_member = try o.builder.debugMemberType(
2174 try o.builder.metadataString("error"),
2175 null, // file
2176 ty_fwd_ref,
2177 0, // line
2178 try o.getDebugType(pt, .anyerror),
2179 ty.abiSize(zcu) * 8,
2180 ty.abiAlignment(zcu).toByteUnits().? * 8,
2181 0, // offset
2182 );
2183 return o.builder.debugStructType(
2164 return o.builder.debugTypedefType(
21842165 name,
21852166 null, // file
21862167 o.debug_compile_unit.unwrap().?, // scope
21872168 0, // line
2188 null, // underlying type
2169 try o.getDebugType(pt, .anyerror),
21892170 ty.abiSize(zcu) * 8,
21902171 ty.abiAlignment(zcu).toByteUnits().? * 8,
2191 try o.builder.metadataTuple(&.{anyerror_member}),
2172 0, // offset
21922173 );
21932174 },
21942175 .@"fn" => {
......@@ -2570,14 +2551,22 @@ pub const Object = struct {
25702551 const error_set_bits = zcu.errorSetBits();
25712552 const error_names = ip.global_error_set.getNamesFromMainThread();
25722553
2573 const enumerators = try gpa.alloc(Builder.Metadata, error_names.len);
2554 const enumerators = try gpa.alloc(Builder.Metadata, error_names.len + 1);
25742555 defer gpa.free(enumerators);
25752556
2576 for (enumerators, error_names, 1..) |*out, error_name, error_value| {
2557 // The value 0 means "no error" in optionals and error unions.
2558 enumerators[0] = try o.builder.debugEnumerator(
2559 try o.builder.metadataString("null"),
2560 true, // unsigned,
2561 error_set_bits,
2562 .{ .limbs = &.{0}, .positive = true }, // zero
2563 );
2564
2565 for (enumerators[1..], error_names, 1..) |*out, error_name, error_value| {
25772566 var space: Value.BigIntSpace = undefined;
25782567 var bigint: std.math.big.int.Mutable = .init(&space.limbs, error_value);
25792568 out.* = try o.builder.debugEnumerator(
2580 try o.builder.metadataString(error_name.toSlice(ip)),
2569 try o.builder.metadataStringFmt("error.{f}", .{error_name.fmtId(ip)}),
25812570 true, // unsigned
25822571 error_set_bits,
25832572 bigint.toConst(),
......@@ -3452,84 +3441,6 @@ pub const Object = struct {
34523441 );
34533442 }
34543443
3455 fn lowerValueToInt(o: *Object, pt: Zcu.PerThread, llvm_int_ty: Builder.Type, arg_val: InternPool.Index) Error!Builder.Constant {
3456 const zcu = pt.zcu;
3457 const ip = &zcu.intern_pool;
3458 const target = zcu.getTarget();
3459
3460 const val = Value.fromInterned(arg_val);
3461 const val_key = ip.indexToKey(val.toIntern());
3462
3463 if (val.isUndef(zcu)) return o.builder.undefConst(llvm_int_ty);
3464
3465 const ty = Type.fromInterned(val_key.typeOf());
3466 switch (val_key) {
3467 .@"extern" => |@"extern"| {
3468 const function_index = try o.resolveLlvmFunction(pt, @"extern".owner_nav);
3469 const ptr = function_index.ptrConst(&o.builder).global.toConst();
3470 return o.builder.convConst(ptr, llvm_int_ty);
3471 },
3472 .func => |func| {
3473 const function_index = try o.resolveLlvmFunction(pt, func.owner_nav);
3474 const ptr = function_index.ptrConst(&o.builder).global.toConst();
3475 return o.builder.convConst(ptr, llvm_int_ty);
3476 },
3477 .ptr => return o.builder.convConst(try o.lowerPtr(pt, arg_val, 0), llvm_int_ty),
3478 .aggregate => switch (ip.indexToKey(ty.toIntern())) {
3479 .struct_type, .vector_type => {},
3480 else => unreachable,
3481 },
3482 .un => |un| {
3483 const layout = ty.unionGetLayout(zcu);
3484 if (layout.payload_size == 0) return o.lowerValue(pt, un.tag);
3485
3486 const union_obj = zcu.typeToUnion(ty).?;
3487 const container_layout = union_obj.layout;
3488
3489 assert(container_layout == .@"packed");
3490
3491 var need_unnamed = false;
3492 if (un.tag == .none) {
3493 assert(layout.tag_size == 0);
3494 const union_val = try o.lowerValueToInt(pt, llvm_int_ty, un.val);
3495
3496 need_unnamed = true;
3497 return union_val;
3498 }
3499 const field_index = zcu.unionTagFieldIndex(union_obj, Value.fromInterned(un.tag)).?;
3500 const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_index]);
3501 if (!field_ty.hasRuntimeBits(zcu)) return o.builder.intConst(llvm_int_ty, 0);
3502 return o.lowerValueToInt(pt, llvm_int_ty, un.val);
3503 },
3504 .simple_value => |simple_value| switch (simple_value) {
3505 .false, .true => {},
3506 else => unreachable,
3507 },
3508 .int,
3509 .float,
3510 .enum_tag,
3511 => {},
3512 .opt => {}, // pointer like optional expected
3513 else => unreachable,
3514 }
3515 var stack = std.heap.stackFallback(32, o.gpa);
3516 const allocator = stack.get();
3517
3518 const bits: usize = @intCast(ty.bitSize(zcu));
3519
3520 const buffer = try allocator.alloc(u8, (bits + 7) / 8);
3521 defer allocator.free(buffer);
3522 const limbs = try allocator.alloc(std.math.big.Limb, std.math.big.int.calcTwosCompLimbCount(bits));
3523 defer allocator.free(limbs);
3524
3525 val.writeToPackedMemory(pt, buffer, 0) catch unreachable;
3526
3527 var big: std.math.big.int.Mutable = .init(limbs, 0);
3528 big.readTwosComplement(buffer, bits, target.cpu.arch.endian(), .unsigned);
3529
3530 return o.builder.bigIntConst(llvm_int_ty, big.toConst());
3531 }
3532
35333444 fn lowerValue(o: *Object, pt: Zcu.PerThread, arg_val: InternPool.Index) Error!Builder.Constant {
35343445 const zcu = pt.zcu;
35353446 const ip = &zcu.intern_pool;