Commit 5b523d0469

mlugg <mlugg@mlugg.co.uk>
2024-06-22 01:29:38
Zir: make `src_line` absolute for `declaration` instructions
We need special logic for updating line numbers anyway, so it's fine to just use absolute numbers here. This eliminates a field from `Decl`.
1 parent 3e9ab6a
lib/std/zig/AstGen.zig
@@ -4368,7 +4368,7 @@ fn fnDecl(
         decl_inst,
         std.zig.hashSrc(tree.getNodeSource(decl_node)),
         .{ .named = fn_name_token },
-        decl_gz.decl_line - gz.decl_line,
+        decl_gz.decl_line,
         is_pub,
         is_export,
         doc_comment_index,
@@ -4529,7 +4529,7 @@ fn globalVarDecl(
         decl_inst,
         std.zig.hashSrc(tree.getNodeSource(node)),
         .{ .named = name_token },
-        block_scope.decl_line - gz.decl_line,
+        block_scope.decl_line,
         is_pub,
         is_export,
         doc_comment_index,
@@ -4579,7 +4579,7 @@ fn comptimeDecl(
         decl_inst,
         std.zig.hashSrc(tree.getNodeSource(node)),
         .@"comptime",
-        decl_block.decl_line - gz.decl_line,
+        decl_block.decl_line,
         false,
         false,
         .empty,
@@ -4629,7 +4629,7 @@ fn usingnamespaceDecl(
         decl_inst,
         std.zig.hashSrc(tree.getNodeSource(node)),
         .@"usingnamespace",
-        decl_block.decl_line - gz.decl_line,
+        decl_block.decl_line,
         is_pub,
         false,
         .empty,
@@ -4818,7 +4818,7 @@ fn testDecl(
         decl_inst,
         std.zig.hashSrc(tree.getNodeSource(node)),
         test_name,
-        decl_block.decl_line - gz.decl_line,
+        decl_block.decl_line,
         false,
         false,
         .empty,
@@ -13861,7 +13861,7 @@ fn setDeclaration(
     decl_inst: Zir.Inst.Index,
     src_hash: std.zig.SrcHash,
     name: DeclarationName,
-    line_offset: u32,
+    src_line: u32,
     is_pub: bool,
     is_export: bool,
     doc_comment: Zir.NullTerminatedString,
@@ -13913,7 +13913,7 @@ fn setDeclaration(
             .@"comptime" => .@"comptime",
             .@"usingnamespace" => .@"usingnamespace",
         },
-        .line_offset = line_offset,
+        .src_line = src_line,
         .flags = .{
             .value_body_len = @intCast(value_len),
             .is_pub = is_pub,
lib/std/zig/Zir.zig
@@ -2598,9 +2598,7 @@ pub const Inst = struct {
         src_hash_3: u32,
         /// The name of this `Decl`. Also indicates whether it is a test, comptime block, etc.
         name: Name,
-        /// This Decl's line number relative to that of its parent.
-        /// TODO: column must be encoded similarly to respect non-formatted code!
-        line_offset: u32,
+        src_line: u32,
         flags: Flags,
 
         pub const Flags = packed struct(u32) {
src/codegen/llvm.zig
@@ -1697,7 +1697,7 @@ pub const Object = struct {
         const file, const subprogram = if (!wip.strip) debug_info: {
             const file = try o.getDebugFile(namespace.file_scope);
 
-            const line_number = decl.src_line + 1;
+            const line_number = decl.navSrcLine(zcu) + 1;
             const is_internal_linkage = decl.val.getExternFunc(zcu) == null and
                 !zcu.decl_exports.contains(decl_index);
             const debug_decl_type = try o.lowerDebugType(decl.typeOf(zcu));
@@ -1741,7 +1741,7 @@ pub const Object = struct {
             .sync_scope = if (owner_mod.single_threaded) .singlethread else .system,
             .file = file,
             .scope = subprogram,
-            .base_line = dg.decl.src_line,
+            .base_line = dg.decl.navSrcLine(zcu),
             .prev_dbg_line = 0,
             .prev_dbg_column = 0,
             .err_ret_trace = err_ret_trace,
@@ -2067,7 +2067,7 @@ pub const Object = struct {
                     try o.builder.metadataString(name),
                     file,
                     scope,
-                    owner_decl.src_line + 1, // Line
+                    owner_decl.typeSrcLine(mod) + 1, // Line
                     try o.lowerDebugType(int_ty),
                     ty.abiSize(mod) * 8,
                     (ty.abiAlignment(mod).toByteUnits() orelse 0) * 8,
@@ -2237,7 +2237,7 @@ pub const Object = struct {
                     try o.builder.metadataString(name),
                     try o.getDebugFile(mod.namespacePtr(owner_decl.src_namespace).file_scope),
                     try o.namespaceToDebugScope(owner_decl.src_namespace),
-                    owner_decl.src_line + 1, // Line
+                    owner_decl.typeSrcLine(mod) + 1, // Line
                     .none, // Underlying type
                     0, // Size
                     0, // Align
@@ -2867,7 +2867,7 @@ pub const Object = struct {
             try o.builder.metadataString(decl.name.toSlice(&mod.intern_pool)), // TODO use fully qualified name
             try o.getDebugFile(mod.namespacePtr(decl.src_namespace).file_scope),
             try o.namespaceToDebugScope(decl.src_namespace),
-            decl.src_line + 1,
+            decl.typeSrcLine(mod) + 1,
             .none,
             0,
             0,
@@ -4762,7 +4762,7 @@ pub const DeclGen = struct {
                 else => try o.lowerValue(init_val),
             }, &o.builder);
 
-            const line_number = decl.src_line + 1;
+            const line_number = decl.navSrcLine(zcu) + 1;
             const is_internal_linkage = !o.module.decl_exports.contains(decl_index);
 
             const namespace = zcu.namespacePtr(decl.src_namespace);
@@ -5188,7 +5188,7 @@ pub const FuncGen = struct {
 
             self.file = try o.getDebugFile(namespace.file_scope);
 
-            const line_number = decl.src_line + 1;
+            const line_number = decl.navSrcLine(zcu) + 1;
             self.inlined = self.wip.debug_location;
 
             const fqn = try decl.fullyQualifiedName(zcu);
@@ -5217,7 +5217,7 @@ pub const FuncGen = struct {
                 o.debug_compile_unit,
             );
 
-            self.base_line = decl.src_line;
+            self.base_line = decl.navSrcLine(zcu);
             const inlined_at_location = try self.wip.debug_location.toMetadata(&o.builder);
             self.wip.debug_location = .{
                 .location = .{
@@ -8857,7 +8857,7 @@ pub const FuncGen = struct {
         const src_index = self.air.instructions.items(.data)[@intFromEnum(inst)].arg.src_index;
         const func_index = self.dg.decl.getOwnedFunctionIndex();
         const func = mod.funcInfo(func_index);
-        const lbrace_line = mod.declPtr(func.owner_decl).src_line + func.lbrace_line + 1;
+        const lbrace_line = mod.declPtr(func.owner_decl).navSrcLine(mod) + func.lbrace_line + 1;
         const lbrace_col = func.lbrace_column + 1;
 
         const debug_parameter = try o.builder.debugParameter(
src/codegen/spirv.zig
@@ -212,7 +212,7 @@ pub const Object = struct {
                 false => .{ .unstructured = .{} },
             },
             .current_block_label = undefined,
-            .base_line = decl.src_line,
+            .base_line = decl.navSrcLine(mod),
         };
         defer decl_gen.deinit();
 
@@ -6345,7 +6345,7 @@ const DeclGen = struct {
         const decl = mod.funcOwnerDeclPtr(extra.data.func);
         const old_base_line = self.base_line;
         defer self.base_line = old_base_line;
-        self.base_line = decl.src_line;
+        self.base_line = decl.navSrcLine(mod);
         return self.lowerBlock(inst, @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len]));
     }
 
src/link/Dwarf.zig
@@ -948,8 +948,8 @@ pub const DeclState = struct {
             leb128.writeUnsignedFixed(4, self.dbg_line.addManyAsArrayAssumeCapacity(4), new_file);
         }
 
-        const old_src_line: i33 = self.mod.declPtr(old_func_info.owner_decl).src_line;
-        const new_src_line: i33 = self.mod.declPtr(new_func_info.owner_decl).src_line;
+        const old_src_line: i33 = self.mod.declPtr(old_func_info.owner_decl).navSrcLine(self.mod);
+        const new_src_line: i33 = self.mod.declPtr(new_func_info.owner_decl).navSrcLine(self.mod);
         if (new_src_line != old_src_line) {
             self.dbg_line.appendAssumeCapacity(DW.LNS.advance_line);
             leb128.writeSignedFixed(5, self.dbg_line.addManyAsArrayAssumeCapacity(5), new_src_line - old_src_line);
@@ -1116,11 +1116,11 @@ pub fn initDeclState(self: *Dwarf, mod: *Module, decl_index: InternPool.DeclInde
             decl_state.dbg_line_func = decl.val.toIntern();
             const func = decl.val.getFunction(mod).?;
             log.debug("decl.src_line={d}, func.lbrace_line={d}, func.rbrace_line={d}", .{
-                decl.src_line,
+                decl.navSrcLine(mod),
                 func.lbrace_line,
                 func.rbrace_line,
             });
-            const line: u28 = @intCast(decl.src_line + func.lbrace_line);
+            const line: u28 = @intCast(decl.navSrcLine(mod) + func.lbrace_line);
 
             dbg_line_buffer.appendSliceAssumeCapacity(&.{
                 DW.LNS.extended_op,
@@ -1702,11 +1702,11 @@ pub fn updateDeclLineNumber(self: *Dwarf, mod: *Module, decl_index: InternPool.D
     const decl = mod.declPtr(decl_index);
     const func = decl.val.getFunction(mod).?;
     log.debug("decl.src_line={d}, func.lbrace_line={d}, func.rbrace_line={d}", .{
-        decl.src_line,
+        decl.navSrcLine(mod),
         func.lbrace_line,
         func.rbrace_line,
     });
-    const line: u28 = @intCast(decl.src_line + func.lbrace_line);
+    const line: u28 = @intCast(decl.navSrcLine(mod) + func.lbrace_line);
     var data: [4]u8 = undefined;
     leb128.writeUnsignedFixed(4, &data, line);
 
src/InternPool.zig
@@ -6958,7 +6958,6 @@ fn finishFuncInstance(
     const decl_index = try ip.createDecl(gpa, .{
         .name = undefined,
         .src_namespace = fn_owner_decl.src_namespace,
-        .src_line = fn_owner_decl.src_line,
         .has_tv = true,
         .owns_tv = true,
         .val = @import("Value.zig").fromInterned(func_index),
src/print_zir.zig
@@ -583,7 +583,7 @@ const Writer = struct {
 
             .reify => {
                 const inst_data = self.code.extraData(Zir.Inst.Reify, extended.operand).data;
-                try stream.print("{d}, ", .{inst_data.src_line});
+                try stream.print("line({d}), ", .{inst_data.src_line});
                 try self.writeInstRef(stream, inst_data.operand);
                 try stream.writeAll(")) ");
                 const prev_parent_decl_node = self.parent_decl_node;
@@ -2749,7 +2749,7 @@ const Writer = struct {
             extra.data.src_hash_3,
         };
         const src_hash_bytes: [16]u8 = @bitCast(src_hash_arr);
-        try stream.print(" line(+{d}) hash({})", .{ extra.data.line_offset, std.fmt.fmtSliceHexLower(&src_hash_bytes) });
+        try stream.print(" line({d}) hash({})", .{ extra.data.src_line, std.fmt.fmtSliceHexLower(&src_hash_bytes) });
 
         {
             const bodies = extra.data.getBodies(@intCast(extra.end), self.code);
src/Sema.zig
@@ -2827,7 +2827,6 @@ fn zirStructDecl(
         small.name_strategy,
         "struct",
         inst,
-        extra.data.src_line,
     );
     mod.declPtr(new_decl_index).owns_tv = true;
     errdefer mod.abortAnonDecl(new_decl_index);
@@ -2864,7 +2863,6 @@ fn createAnonymousDeclTypeNamed(
     name_strategy: Zir.Inst.NameStrategy,
     anon_prefix: []const u8,
     inst: ?Zir.Inst.Index,
-    src_line: u32,
 ) !InternPool.DeclIndex {
     const zcu = sema.mod;
     const ip = &zcu.intern_pool;
@@ -2876,7 +2874,7 @@ fn createAnonymousDeclTypeNamed(
     switch (name_strategy) {
         .anon => {}, // handled after switch
         .parent => {
-            try zcu.initNewAnonDecl(new_decl_index, src_line, val, block.type_name_ctx);
+            try zcu.initNewAnonDecl(new_decl_index, val, block.type_name_ctx);
             return new_decl_index;
         },
         .func => func_strat: {
@@ -2921,7 +2919,7 @@ fn createAnonymousDeclTypeNamed(
 
             try writer.writeByte(')');
             const name = try ip.getOrPutString(gpa, buf.items, .no_embedded_nulls);
-            try zcu.initNewAnonDecl(new_decl_index, src_line, val, name);
+            try zcu.initNewAnonDecl(new_decl_index, val, name);
             return new_decl_index;
         },
         .dbg_var => {
@@ -2935,7 +2933,7 @@ fn createAnonymousDeclTypeNamed(
                     const name = try ip.getOrPutStringFmt(gpa, "{}.{s}", .{
                         block.type_name_ctx.fmt(ip), zir_data[i].str_op.getStr(sema.code),
                     }, .no_embedded_nulls);
-                    try zcu.initNewAnonDecl(new_decl_index, src_line, val, name);
+                    try zcu.initNewAnonDecl(new_decl_index, val, name);
                     return new_decl_index;
                 },
                 else => {},
@@ -2956,7 +2954,7 @@ fn createAnonymousDeclTypeNamed(
     const name = ip.getOrPutStringFmt(gpa, "{}__{s}_{d}", .{
         block.type_name_ctx.fmt(ip), anon_prefix, @intFromEnum(new_decl_index),
     }, .no_embedded_nulls) catch unreachable;
-    try zcu.initNewAnonDecl(new_decl_index, src_line, val, name);
+    try zcu.initNewAnonDecl(new_decl_index, val, name);
     return new_decl_index;
 }
 
@@ -3062,7 +3060,6 @@ fn zirEnumDecl(
         small.name_strategy,
         "enum",
         inst,
-        extra.data.src_line,
     );
     const new_decl = mod.declPtr(new_decl_index);
     new_decl.owns_tv = true;
@@ -3330,7 +3327,6 @@ fn zirUnionDecl(
         small.name_strategy,
         "union",
         inst,
-        extra.data.src_line,
     );
     mod.declPtr(new_decl_index).owns_tv = true;
     errdefer mod.abortAnonDecl(new_decl_index);
@@ -3419,7 +3415,6 @@ fn zirOpaqueDecl(
         small.name_strategy,
         "opaque",
         inst,
-        extra.data.src_line,
     );
     mod.declPtr(new_decl_index).owns_tv = true;
     errdefer mod.abortAnonDecl(new_decl_index);
@@ -21546,7 +21541,7 @@ fn zirReify(
                 .needed_comptime_reason = "struct fields must be comptime-known",
             });
 
-            return try sema.reifyStruct(block, inst, src, layout, backing_integer_val, fields_arr, name_strategy, is_tuple_val.toBool(), extra.src_line);
+            return try sema.reifyStruct(block, inst, src, layout, backing_integer_val, fields_arr, name_strategy, is_tuple_val.toBool());
         },
         .Enum => {
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
@@ -21575,7 +21570,7 @@ fn zirReify(
                 .needed_comptime_reason = "enum fields must be comptime-known",
             });
 
-            return sema.reifyEnum(block, inst, src, tag_type_val.toType(), is_exhaustive_val.toBool(), fields_arr, name_strategy, extra.src_line);
+            return sema.reifyEnum(block, inst, src, tag_type_val.toType(), is_exhaustive_val.toBool(), fields_arr, name_strategy);
         },
         .Opaque => {
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
@@ -21606,7 +21601,6 @@ fn zirReify(
                 name_strategy,
                 "opaque",
                 inst,
-                extra.src_line,
             );
             mod.declPtr(new_decl_index).owns_tv = true;
             errdefer mod.abortAnonDecl(new_decl_index);
@@ -21643,7 +21637,7 @@ fn zirReify(
                 .needed_comptime_reason = "union fields must be comptime-known",
             });
 
-            return sema.reifyUnion(block, inst, src, layout, tag_type_val, fields_arr, name_strategy, extra.src_line);
+            return sema.reifyUnion(block, inst, src, layout, tag_type_val, fields_arr, name_strategy);
         },
         .Fn => {
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
@@ -21745,7 +21739,6 @@ fn reifyEnum(
     is_exhaustive: bool,
     fields_val: Value,
     name_strategy: Zir.Inst.NameStrategy,
-    src_line: u32,
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
     const gpa = sema.gpa;
@@ -21807,7 +21800,6 @@ fn reifyEnum(
         name_strategy,
         "enum",
         inst,
-        src_line,
     );
     mod.declPtr(new_decl_index).owns_tv = true;
     errdefer mod.abortAnonDecl(new_decl_index);
@@ -21871,7 +21863,6 @@ fn reifyUnion(
     opt_tag_type_val: Value,
     fields_val: Value,
     name_strategy: Zir.Inst.NameStrategy,
-    src_line: u32,
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
     const gpa = sema.gpa;
@@ -21955,7 +21946,6 @@ fn reifyUnion(
         name_strategy,
         "union",
         inst,
-        src_line,
     );
     mod.declPtr(new_decl_index).owns_tv = true;
     errdefer mod.abortAnonDecl(new_decl_index);
@@ -22051,7 +22041,7 @@ fn reifyUnion(
             }
         }
 
-        const enum_tag_ty = try sema.generateUnionTagTypeSimple(block, field_names.keys(), mod.declPtr(new_decl_index), src_line);
+        const enum_tag_ty = try sema.generateUnionTagTypeSimple(block, field_names.keys(), mod.declPtr(new_decl_index));
         break :tag_ty .{ enum_tag_ty, false };
     };
     errdefer if (!has_explicit_tag) ip.remove(enum_tag_ty); // remove generated tag type on error
@@ -22112,7 +22102,6 @@ fn reifyStruct(
     fields_val: Value,
     name_strategy: Zir.Inst.NameStrategy,
     is_tuple: bool,
-    src_line: u32,
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
     const gpa = sema.gpa;
@@ -22213,7 +22202,6 @@ fn reifyStruct(
         name_strategy,
         "struct",
         inst,
-        src_line,
     );
     mod.declPtr(new_decl_index).owns_tv = true;
     errdefer mod.abortAnonDecl(new_decl_index);
@@ -26347,7 +26335,6 @@ fn zirBuiltinExtern(
     const new_decl = mod.declPtr(new_decl_index);
     try mod.initNewAnonDecl(
         new_decl_index,
-        sema.owner_decl.src_line,
         Value.fromInterned(
             if (Type.fromInterned(ptr_info.child).zigTypeTag(mod) == .Fn)
                 try ip.getExternFunc(sema.gpa, .{
@@ -36745,10 +36732,10 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded
             return sema.failWithOwnedErrorMsg(&block_scope, msg);
         }
     } else if (enum_field_vals.count() > 0) {
-        const enum_ty = try sema.generateUnionTagTypeNumbered(&block_scope, enum_field_names, enum_field_vals.keys(), mod.declPtr(union_type.decl), extra.data.src_line);
+        const enum_ty = try sema.generateUnionTagTypeNumbered(&block_scope, enum_field_names, enum_field_vals.keys(), mod.declPtr(union_type.decl));
         union_type.tagTypePtr(ip).* = enum_ty;
     } else {
-        const enum_ty = try sema.generateUnionTagTypeSimple(&block_scope, enum_field_names, mod.declPtr(union_type.decl), extra.data.src_line);
+        const enum_ty = try sema.generateUnionTagTypeSimple(&block_scope, enum_field_names, mod.declPtr(union_type.decl));
         union_type.tagTypePtr(ip).* = enum_ty;
     }
 }
@@ -36766,7 +36753,6 @@ fn generateUnionTagTypeNumbered(
     enum_field_names: []const InternPool.NullTerminatedString,
     enum_field_vals: []const InternPool.Index,
     union_owner_decl: *Module.Decl,
-    src_line: u32,
 ) !InternPool.Index {
     const mod = sema.mod;
     const gpa = sema.gpa;
@@ -36783,7 +36769,6 @@ fn generateUnionTagTypeNumbered(
     );
     try mod.initNewAnonDecl(
         new_decl_index,
-        src_line,
         Value.@"unreachable",
         name,
     );
@@ -36816,7 +36801,6 @@ fn generateUnionTagTypeSimple(
     block: *Block,
     enum_field_names: []const InternPool.NullTerminatedString,
     union_owner_decl: *Module.Decl,
-    src_line: u32,
 ) !InternPool.Index {
     const mod = sema.mod;
     const ip = &mod.intern_pool;
@@ -36834,7 +36818,6 @@ fn generateUnionTagTypeSimple(
         );
         try mod.initNewAnonDecl(
             new_decl_index,
-            src_line,
             Value.@"unreachable",
             name,
         );
src/type.zig
@@ -11,6 +11,7 @@ const target_util = @import("target.zig");
 const Sema = @import("Sema.zig");
 const InternPool = @import("InternPool.zig");
 const Alignment = InternPool.Alignment;
+const Zir = std.zig.Zir;
 
 /// Both types and values are canonically represented by a single 32-bit integer
 /// which is an index into an `InternPool` data structure.
@@ -3340,7 +3341,7 @@ pub const Type = struct {
                 .struct_type, .union_type, .opaque_type, .enum_type => |info| switch (info) {
                     .declared => |d| d.zir_index,
                     .reified => |r| r.zir_index,
-                    .generated_tag => |gt| ip.loadUnionType(gt.union_type).zir_index, // must be declared since we can't generate tags when reifying
+                    .generated_tag => |gt| ip.loadUnionType(gt.union_type).zir_index,
                     .empty_struct => return null,
                 },
                 else => return null,
@@ -3440,6 +3441,33 @@ pub const Type = struct {
         };
     }
 
+    pub fn typeDeclSrcLine(ty: Type, zcu: *const Zcu) ?u32 {
+        const ip = &zcu.intern_pool;
+        const tracked = switch (ip.indexToKey(ty.toIntern())) {
+            .struct_type, .union_type, .opaque_type, .enum_type => |info| switch (info) {
+                .declared => |d| d.zir_index,
+                .reified => |r| r.zir_index,
+                .generated_tag => |gt| ip.loadUnionType(gt.union_type).zir_index,
+                .empty_struct => return null,
+            },
+            else => return null,
+        };
+        const info = tracked.resolveFull(&zcu.intern_pool);
+        const file = zcu.import_table.values()[zcu.path_digest_map.getIndex(info.path_digest).?];
+        assert(file.zir_loaded);
+        const zir = file.zir;
+        const inst = zir.instructions.get(@intFromEnum(info.inst));
+        assert(inst.tag == .extended);
+        return switch (inst.data.extended.opcode) {
+            .struct_decl => zir.extraData(Zir.Inst.StructDecl, inst.data.extended.operand).data.src_line,
+            .union_decl => zir.extraData(Zir.Inst.UnionDecl, inst.data.extended.operand).data.src_line,
+            .enum_decl => zir.extraData(Zir.Inst.EnumDecl, inst.data.extended.operand).data.src_line,
+            .opaque_decl => zir.extraData(Zir.Inst.OpaqueDecl, inst.data.extended.operand).data.src_line,
+            .reify => zir.extraData(Zir.Inst.Reify, inst.data.extended.operand).data.src_line,
+            else => unreachable,
+        };
+    }
+
     /// Given a namespace type, returns its list of caotured values.
     pub fn getCaptures(ty: Type, zcu: *const Zcu) InternPool.CaptureValue.Slice {
         const ip = &zcu.intern_pool;
src/Zcu.zig
@@ -347,10 +347,6 @@ pub const Decl = struct {
     /// there is no parent.
     src_namespace: Namespace.Index,
 
-    /// Line number corresponding to `src_node`. Stored separately so that source files
-    /// do not need to be loaded into memory in order to compute debug line numbers.
-    /// This value is absolute.
-    src_line: u32,
     /// Index of the ZIR `declaration` instruction from which this `Decl` was created.
     /// For the root `Decl` of a `File` and legacy anonymous decls, this is `.none`.
     zir_decl_index: InternPool.TrackedInst.Index.Optional,
@@ -564,6 +560,33 @@ pub const Decl = struct {
             .offset = LazySrcLoc.Offset.nodeOffset(0),
         };
     }
+
+    pub fn navSrcLine(decl: Decl, zcu: *Zcu) u32 {
+        const tracked = decl.zir_decl_index.unwrap() orelse inst: {
+            // generic instantiation
+            assert(decl.has_tv);
+            assert(decl.owns_tv);
+            const generic_owner_func = switch (zcu.intern_pool.indexToKey(decl.val.toIntern())) {
+                .func => |func| func.generic_owner,
+                else => return 0, // TODO: this is probably a `variable` or something; figure this out when we finish sorting out `Decl`.
+            };
+            const generic_owner_decl = zcu.declPtr(zcu.funcInfo(generic_owner_func).owner_decl);
+            break :inst generic_owner_decl.zir_decl_index.unwrap().?;
+        };
+        const info = tracked.resolveFull(&zcu.intern_pool);
+        const file = zcu.import_table.values()[zcu.path_digest_map.getIndex(info.path_digest).?];
+        assert(file.zir_loaded);
+        const zir = file.zir;
+        const inst = zir.instructions.get(@intFromEnum(info.inst));
+        assert(inst.tag == .declaration);
+        return zir.extraData(Zir.Inst.Declaration, inst.data.declaration.payload_index).data.src_line;
+    }
+
+    pub fn typeSrcLine(decl: Decl, zcu: *Zcu) u32 {
+        assert(decl.has_tv);
+        assert(decl.owns_tv);
+        return decl.val.toType().typeDeclSrcLine(zcu).?;
+    }
 };
 
 /// This state is attached to every Decl when Module emit_h is non-null.
@@ -3944,7 +3967,6 @@ fn semaFile(mod: *Module, file: *File) SemaError!void {
 
     new_decl.name = try file.fullyQualifiedName(mod);
     new_decl.name_fully_qualified = true;
-    new_decl.src_line = 0;
     new_decl.is_pub = true;
     new_decl.is_exported = false;
     new_decl.alignment = .none;
@@ -4762,8 +4784,6 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
     const extra = zir.extraData(Zir.Inst.Declaration, inst_data.payload_index);
     const declaration = extra.data;
 
-    const line = iter.parent_decl.src_line + declaration.line_offset;
-
     // Every Decl needs a name.
     const decl_name: InternPool.NullTerminatedString, const kind: Decl.Kind, const is_named_test: bool = switch (declaration.name) {
         .@"comptime" => info: {
@@ -4850,7 +4870,6 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
         const was_exported = decl.is_exported;
         assert(decl.kind == kind); // ZIR tracking should preserve this
         decl.name = decl_name;
-        decl.src_line = line;
         decl.is_pub = declaration.flags.is_pub;
         decl.is_exported = declaration.flags.is_export;
         break :decl_index .{ was_exported, decl_index };
@@ -4860,7 +4879,6 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
         const new_decl = zcu.declPtr(new_decl_index);
         new_decl.kind = kind;
         new_decl.name = decl_name;
-        new_decl.src_line = line;
         new_decl.is_pub = declaration.flags.is_pub;
         new_decl.is_exported = declaration.flags.is_export;
         new_decl.zir_decl_index = tracked_inst.toOptional();
@@ -5263,7 +5281,6 @@ pub fn allocateNewDecl(zcu: *Zcu, namespace: Namespace.Index) !Decl.Index {
     const decl_index = try zcu.intern_pool.createDecl(gpa, .{
         .name = undefined,
         .src_namespace = namespace,
-        .src_line = undefined,
         .has_tv = false,
         .owns_tv = false,
         .val = undefined,
@@ -5311,14 +5328,12 @@ pub fn errorSetBits(mod: *Module) u16 {
 pub fn initNewAnonDecl(
     mod: *Module,
     new_decl_index: Decl.Index,
-    src_line: u32,
     val: Value,
     name: InternPool.NullTerminatedString,
 ) Allocator.Error!void {
     const new_decl = mod.declPtr(new_decl_index);
 
     new_decl.name = name;
-    new_decl.src_line = src_line;
     new_decl.val = val;
     new_decl.alignment = .none;
     new_decl.@"linksection" = .none;