Commit ded5c759f8

mlugg <mlugg@mlugg.co.uk>
2024-06-30 04:00:07
Zcu: store `LazySrcLoc` in error messages
This change modifies `Zcu.ErrorMsg` to store a `Zcu.LazySrcLoc` rather than a `Zcu.SrcLoc`. Everything else is dominoes. The reason for this change is incremental compilation. If a failed `AnalUnit` is up-to-date on an update, we want to re-use the old error messages. However, the file containing the error location may have been modified, and `SrcLoc` cannot survive such a modification. `LazySrcLoc` is designed to be correct across incremental updates. Therefore, we defer source location resolution until `Compilation` gathers the compile errors into the `ErrorBundle`.
1 parent 089bbd6
src/arch/aarch64/CodeGen.zig
@@ -59,7 +59,7 @@ args: []MCValue,
 ret_mcv: MCValue,
 fn_type: Type,
 arg_index: u32,
-src_loc: Module.SrcLoc,
+src_loc: Module.LazySrcLoc,
 stack_align: u32,
 
 /// MIR Instructions
@@ -331,7 +331,7 @@ const Self = @This();
 
 pub fn generate(
     lf: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     func_index: InternPool.Index,
     air: Air,
     liveness: Liveness,
src/arch/aarch64/Emit.zig
@@ -22,7 +22,7 @@ bin_file: *link.File,
 debug_output: DebugInfoOutput,
 target: *const std.Target,
 err_msg: ?*ErrorMsg = null,
-src_loc: Module.SrcLoc,
+src_loc: Module.LazySrcLoc,
 code: *std.ArrayList(u8),
 
 prev_di_line: u32,
src/arch/arm/CodeGen.zig
@@ -59,7 +59,7 @@ args: []MCValue,
 ret_mcv: MCValue,
 fn_type: Type,
 arg_index: u32,
-src_loc: Module.SrcLoc,
+src_loc: Module.LazySrcLoc,
 stack_align: u32,
 
 /// MIR Instructions
@@ -338,7 +338,7 @@ const Self = @This();
 
 pub fn generate(
     lf: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     func_index: InternPool.Index,
     air: Air,
     liveness: Liveness,
src/arch/arm/Emit.zig
@@ -26,7 +26,7 @@ bin_file: *link.File,
 debug_output: DebugInfoOutput,
 target: *const std.Target,
 err_msg: ?*ErrorMsg = null,
-src_loc: Module.SrcLoc,
+src_loc: Module.LazySrcLoc,
 code: *std.ArrayList(u8),
 
 prev_di_line: u32,
src/arch/riscv64/CodeGen.zig
@@ -59,7 +59,7 @@ args: []MCValue,
 ret_mcv: InstTracking,
 fn_type: Type,
 arg_index: usize,
-src_loc: Zcu.SrcLoc,
+src_loc: Zcu.LazySrcLoc,
 
 /// MIR Instructions
 mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
@@ -696,7 +696,7 @@ const CallView = enum(u1) {
 
 pub fn generate(
     bin_file: *link.File,
-    src_loc: Zcu.SrcLoc,
+    src_loc: Zcu.LazySrcLoc,
     func_index: InternPool.Index,
     air: Air,
     liveness: Liveness,
src/arch/riscv64/Lower.zig
@@ -8,7 +8,7 @@ allocator: Allocator,
 mir: Mir,
 cc: std.builtin.CallingConvention,
 err_msg: ?*ErrorMsg = null,
-src_loc: Zcu.SrcLoc,
+src_loc: Zcu.LazySrcLoc,
 result_insts_len: u8 = undefined,
 result_relocs_len: u8 = undefined,
 result_insts: [
src/arch/sparc64/CodeGen.zig
@@ -64,7 +64,7 @@ args: []MCValue,
 ret_mcv: MCValue,
 fn_type: Type,
 arg_index: usize,
-src_loc: Module.SrcLoc,
+src_loc: Module.LazySrcLoc,
 stack_align: Alignment,
 
 /// MIR Instructions
@@ -263,7 +263,7 @@ const BigTomb = struct {
 
 pub fn generate(
     lf: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     func_index: InternPool.Index,
     air: Air,
     liveness: Liveness,
src/arch/sparc64/Emit.zig
@@ -24,7 +24,7 @@ bin_file: *link.File,
 debug_output: DebugInfoOutput,
 target: *const std.Target,
 err_msg: ?*ErrorMsg = null,
-src_loc: Module.SrcLoc,
+src_loc: Module.LazySrcLoc,
 code: *std.ArrayList(u8),
 
 prev_di_line: u32,
src/arch/wasm/CodeGen.zig
@@ -765,7 +765,7 @@ pub fn deinit(func: *CodeGen) void {
 /// Sets `err_msg` on `CodeGen` and returns `error.CodegenFail` which is caught in link/Wasm.zig
 fn fail(func: *CodeGen, comptime fmt: []const u8, args: anytype) InnerError {
     const mod = func.bin_file.base.comp.module.?;
-    const src_loc = func.decl.navSrcLoc(mod).upgrade(mod);
+    const src_loc = func.decl.navSrcLoc(mod);
     func.err_msg = try Zcu.ErrorMsg.create(func.gpa, src_loc, fmt, args);
     return error.CodegenFail;
 }
@@ -1202,7 +1202,7 @@ fn genFunctype(
 
 pub fn generate(
     bin_file: *link.File,
-    src_loc: Zcu.SrcLoc,
+    src_loc: Zcu.LazySrcLoc,
     func_index: InternPool.Index,
     air: Air,
     liveness: Liveness,
@@ -3162,7 +3162,7 @@ fn lowerAnonDeclRef(
     }
 
     const decl_align = mod.intern_pool.indexToKey(anon_decl.orig_ty).ptr_type.flags.alignment;
-    const res = try func.bin_file.lowerAnonDecl(decl_val, decl_align, func.decl.navSrcLoc(mod).upgrade(mod));
+    const res = try func.bin_file.lowerAnonDecl(decl_val, decl_align, func.decl.navSrcLoc(mod));
     switch (res) {
         .ok => {},
         .fail => |em| {
src/arch/wasm/Emit.zig
@@ -257,7 +257,7 @@ fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
     const comp = emit.bin_file.base.comp;
     const zcu = comp.module.?;
     const gpa = comp.gpa;
-    emit.error_msg = try Zcu.ErrorMsg.create(gpa, zcu.declPtr(emit.decl_index).navSrcLoc(zcu).upgrade(zcu), format, args);
+    emit.error_msg = try Zcu.ErrorMsg.create(gpa, zcu.declPtr(emit.decl_index).navSrcLoc(zcu), format, args);
     return error.EmitFail;
 }
 
src/arch/x86_64/CodeGen.zig
@@ -74,7 +74,7 @@ va_info: union {
 ret_mcv: InstTracking,
 fn_type: Type,
 arg_index: u32,
-src_loc: Module.SrcLoc,
+src_loc: Module.LazySrcLoc,
 
 eflags_inst: ?Air.Inst.Index = null,
 
@@ -795,7 +795,7 @@ const Self = @This();
 
 pub fn generate(
     bin_file: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     func_index: InternPool.Index,
     air: Air,
     liveness: Liveness,
@@ -971,7 +971,7 @@ pub fn generate(
 
 pub fn generateLazy(
     bin_file: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     lazy_sym: link.File.LazySymbol,
     code: *std.ArrayList(u8),
     debug_output: DebugInfoOutput,
src/arch/x86_64/Lower.zig
@@ -8,7 +8,7 @@ allocator: Allocator,
 mir: Mir,
 cc: std.builtin.CallingConvention,
 err_msg: ?*ErrorMsg = null,
-src_loc: Module.SrcLoc,
+src_loc: Module.LazySrcLoc,
 result_insts_len: u8 = undefined,
 result_relocs_len: u8 = undefined,
 result_insts: [
src/codegen/c.zig
@@ -637,7 +637,7 @@ pub const DeclGen = struct {
         const zcu = dg.zcu;
         const decl_index = dg.pass.decl;
         const decl = zcu.declPtr(decl_index);
-        const src_loc = decl.navSrcLoc(zcu).upgrade(zcu);
+        const src_loc = decl.navSrcLoc(zcu);
         dg.error_msg = try Zcu.ErrorMsg.create(dg.gpa, src_loc, format, args);
         return error.AnalysisFail;
     }
src/codegen/llvm.zig
@@ -4644,7 +4644,7 @@ pub const DeclGen = struct {
         const o = dg.object;
         const gpa = o.gpa;
         const mod = o.module;
-        const src_loc = dg.decl.navSrcLoc(mod).upgrade(mod);
+        const src_loc = dg.decl.navSrcLoc(mod);
         dg.err_msg = try Module.ErrorMsg.create(gpa, src_loc, "TODO (LLVM): " ++ format, args);
         return error.CodegenFail;
     }
src/codegen/spirv.zig
@@ -415,7 +415,7 @@ const DeclGen = struct {
     pub fn fail(self: *DeclGen, comptime format: []const u8, args: anytype) Error {
         @setCold(true);
         const mod = self.module;
-        const src_loc = self.module.declPtr(self.decl_index).navSrcLoc(mod).upgrade(mod);
+        const src_loc = self.module.declPtr(self.decl_index).navSrcLoc(mod);
         assert(self.error_msg == null);
         self.error_msg = try Module.ErrorMsg.create(self.module.gpa, src_loc, format, args);
         return error.CodegenFail;
@@ -6439,7 +6439,7 @@ const DeclGen = struct {
                 // TODO: Translate proper error locations.
                 assert(as.errors.items.len != 0);
                 assert(self.error_msg == null);
-                const src_loc = self.module.declPtr(self.decl_index).navSrcLoc(mod).upgrade(mod);
+                const src_loc = self.module.declPtr(self.decl_index).navSrcLoc(mod);
                 self.error_msg = try Module.ErrorMsg.create(self.module.gpa, src_loc, "failed to assemble SPIR-V inline assembly", .{});
                 const notes = try self.module.gpa.alloc(Module.ErrorMsg, as.errors.items.len);
 
src/link/Elf/ZigObject.zig
@@ -686,7 +686,7 @@ pub fn lowerAnonDecl(
     elf_file: *Elf,
     decl_val: InternPool.Index,
     explicit_alignment: InternPool.Alignment,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
 ) !codegen.Result {
     const gpa = elf_file.base.comp.gpa;
     const mod = elf_file.base.comp.module.?;
@@ -1074,7 +1074,7 @@ pub fn updateFunc(
     const res = if (decl_state) |*ds|
         try codegen.generateFunction(
             &elf_file.base,
-            decl.navSrcLoc(mod).upgrade(mod),
+            decl.navSrcLoc(mod),
             func_index,
             air,
             liveness,
@@ -1084,7 +1084,7 @@ pub fn updateFunc(
     else
         try codegen.generateFunction(
             &elf_file.base,
-            decl.navSrcLoc(mod).upgrade(mod),
+            decl.navSrcLoc(mod),
             func_index,
             air,
             liveness,
@@ -1156,13 +1156,13 @@ pub fn updateDecl(
     // TODO implement .debug_info for global variables
     const decl_val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val;
     const res = if (decl_state) |*ds|
-        try codegen.generateSymbol(&elf_file.base, decl.navSrcLoc(mod).upgrade(mod), decl_val, &code_buffer, .{
+        try codegen.generateSymbol(&elf_file.base, decl.navSrcLoc(mod), decl_val, &code_buffer, .{
             .dwarf = ds,
         }, .{
             .parent_atom_index = sym_index,
         })
     else
-        try codegen.generateSymbol(&elf_file.base, decl.navSrcLoc(mod).upgrade(mod), decl_val, &code_buffer, .none, .{
+        try codegen.generateSymbol(&elf_file.base, decl.navSrcLoc(mod), decl_val, &code_buffer, .none, .{
             .parent_atom_index = sym_index,
         });
 
@@ -1217,14 +1217,7 @@ fn updateLazySymbol(
         break :blk try self.strtab.insert(gpa, name);
     };
 
-    const src = if (sym.ty.srcLocOrNull(mod)) |src|
-        src.upgrade(mod)
-    else
-        Module.SrcLoc{
-            .file_scope = undefined,
-            .base_node = undefined,
-            .lazy = .unneeded,
-        };
+    const src = sym.ty.srcLocOrNull(mod) orelse Module.LazySrcLoc.unneeded;
     const res = try codegen.generateLazySymbol(
         &elf_file.base,
         src,
@@ -1302,7 +1295,7 @@ pub fn lowerUnnamedConst(
         val,
         ty.abiAlignment(mod),
         elf_file.zig_data_rel_ro_section_index.?,
-        decl.navSrcLoc(mod).upgrade(mod),
+        decl.navSrcLoc(mod),
     )) {
         .ok => |sym_index| sym_index,
         .fail => |em| {
@@ -1329,7 +1322,7 @@ fn lowerConst(
     val: Value,
     required_alignment: InternPool.Alignment,
     output_section_index: u32,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
 ) !LowerConstResult {
     const gpa = elf_file.base.comp.gpa;
 
@@ -1395,7 +1388,7 @@ pub fn updateExports(
         },
         .value => |value| self.anon_decls.getPtr(value) orelse blk: {
             const first_exp = mod.all_exports.items[export_indices[0]];
-            const res = try self.lowerAnonDecl(elf_file, value, .none, first_exp.getSrcLoc(mod));
+            const res = try self.lowerAnonDecl(elf_file, value, .none, first_exp.src);
             switch (res) {
                 .ok => {},
                 .fail => |em| {
@@ -1421,7 +1414,7 @@ pub fn updateExports(
                 try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
                 mod.failed_exports.putAssumeCapacityNoClobber(export_idx, try Module.ErrorMsg.create(
                     gpa,
-                    exp.getSrcLoc(mod),
+                    exp.src,
                     "Unimplemented: ExportOptions.section",
                     .{},
                 ));
@@ -1436,7 +1429,7 @@ pub fn updateExports(
                 try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
                 mod.failed_exports.putAssumeCapacityNoClobber(export_idx, try Module.ErrorMsg.create(
                     gpa,
-                    exp.getSrcLoc(mod),
+                    exp.src,
                     "Unimplemented: GlobalLinkage.LinkOnce",
                     .{},
                 ));
src/link/MachO/ZigObject.zig
@@ -572,7 +572,7 @@ pub fn lowerAnonDecl(
     macho_file: *MachO,
     decl_val: InternPool.Index,
     explicit_alignment: Atom.Alignment,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
 ) !codegen.Result {
     const gpa = macho_file.base.comp.gpa;
     const mod = macho_file.base.comp.module.?;
@@ -682,7 +682,7 @@ pub fn updateFunc(
     const dio: codegen.DebugInfoOutput = if (decl_state) |*ds| .{ .dwarf = ds } else .none;
     const res = try codegen.generateFunction(
         &macho_file.base,
-        decl.navSrcLoc(mod).upgrade(mod),
+        decl.navSrcLoc(mod),
         func_index,
         air,
         liveness,
@@ -754,7 +754,7 @@ pub fn updateDecl(
 
     const decl_val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val;
     const dio: codegen.DebugInfoOutput = if (decl_state) |*ds| .{ .dwarf = ds } else .none;
-    const res = try codegen.generateSymbol(&macho_file.base, decl.navSrcLoc(mod).upgrade(mod), decl_val, &code_buffer, dio, .{
+    const res = try codegen.generateSymbol(&macho_file.base, decl.navSrcLoc(mod), decl_val, &code_buffer, dio, .{
         .parent_atom_index = sym_index,
     });
 
@@ -1100,7 +1100,7 @@ pub fn lowerUnnamedConst(
         val,
         val.typeOf(mod).abiAlignment(mod),
         macho_file.zig_const_sect_index.?,
-        decl.navSrcLoc(mod).upgrade(mod),
+        decl.navSrcLoc(mod),
     )) {
         .ok => |sym_index| sym_index,
         .fail => |em| {
@@ -1127,7 +1127,7 @@ fn lowerConst(
     val: Value,
     required_alignment: Atom.Alignment,
     output_section_index: u8,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
 ) !LowerConstResult {
     const gpa = macho_file.base.comp.gpa;
 
@@ -1196,7 +1196,7 @@ pub fn updateExports(
         },
         .value => |value| self.anon_decls.getPtr(value) orelse blk: {
             const first_exp = mod.all_exports.items[export_indices[0]];
-            const res = try self.lowerAnonDecl(macho_file, value, .none, first_exp.getSrcLoc(mod));
+            const res = try self.lowerAnonDecl(macho_file, value, .none, first_exp.src);
             switch (res) {
                 .ok => {},
                 .fail => |em| {
@@ -1221,7 +1221,7 @@ pub fn updateExports(
                 try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
                 mod.failed_exports.putAssumeCapacityNoClobber(export_idx, try Module.ErrorMsg.create(
                     gpa,
-                    exp.getSrcLoc(mod),
+                    exp.src,
                     "Unimplemented: ExportOptions.section",
                     .{},
                 ));
@@ -1231,7 +1231,7 @@ pub fn updateExports(
         if (exp.opts.linkage == .link_once) {
             try mod.failed_exports.putNoClobber(mod.gpa, export_idx, try Module.ErrorMsg.create(
                 gpa,
-                exp.getSrcLoc(mod),
+                exp.src,
                 "Unimplemented: GlobalLinkage.link_once",
                 .{},
             ));
@@ -1291,14 +1291,7 @@ fn updateLazySymbol(
         break :blk try self.strtab.insert(gpa, name);
     };
 
-    const src = if (lazy_sym.ty.srcLocOrNull(mod)) |src|
-        src.upgrade(mod)
-    else
-        Module.SrcLoc{
-            .file_scope = undefined,
-            .base_node = undefined,
-            .lazy = .unneeded,
-        };
+    const src = lazy_sym.ty.srcLocOrNull(mod) orelse Module.LazySrcLoc.unneeded;
     const res = try codegen.generateLazySymbol(
         &macho_file.base,
         src,
src/link/Wasm/ZigObject.zig
@@ -269,7 +269,7 @@ pub fn updateDecl(
 
     const res = try codegen.generateSymbol(
         &wasm_file.base,
-        decl.navSrcLoc(mod).upgrade(mod),
+        decl.navSrcLoc(mod),
         val,
         &code_writer,
         .none,
@@ -308,7 +308,7 @@ pub fn updateFunc(
     defer code_writer.deinit();
     const result = try codegen.generateFunction(
         &wasm_file.base,
-        decl.navSrcLoc(mod).upgrade(mod),
+        decl.navSrcLoc(mod),
         func_index,
         air,
         liveness,
@@ -439,7 +439,7 @@ pub fn lowerAnonDecl(
     wasm_file: *Wasm,
     decl_val: InternPool.Index,
     explicit_alignment: InternPool.Alignment,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
 ) !codegen.Result {
     const gpa = wasm_file.base.comp.gpa;
     const gop = try zig_object.anon_decls.getOrPut(gpa, decl_val);
@@ -494,7 +494,7 @@ pub fn lowerUnnamedConst(zig_object: *ZigObject, wasm_file: *Wasm, val: Value, d
     else
         decl.navSrcLoc(mod);
 
-    switch (try zig_object.lowerConst(wasm_file, name, val, decl_src.upgrade(mod))) {
+    switch (try zig_object.lowerConst(wasm_file, name, val, decl_src)) {
         .ok => |atom_index| {
             try wasm_file.getAtomPtr(parent_atom_index).locals.append(gpa, atom_index);
             return @intFromEnum(wasm_file.getAtom(atom_index).sym_index);
@@ -512,7 +512,7 @@ const LowerConstResult = union(enum) {
     fail: *Module.ErrorMsg,
 };
 
-fn lowerConst(zig_object: *ZigObject, wasm_file: *Wasm, name: []const u8, val: Value, src_loc: Module.SrcLoc) !LowerConstResult {
+fn lowerConst(zig_object: *ZigObject, wasm_file: *Wasm, name: []const u8, val: Value, src_loc: Module.LazySrcLoc) !LowerConstResult {
     const gpa = wasm_file.base.comp.gpa;
     const mod = wasm_file.base.comp.module.?;
 
@@ -882,7 +882,7 @@ pub fn updateExports(
         if (exp.opts.section.toSlice(&mod.intern_pool)) |section| {
             try mod.failed_exports.putNoClobber(gpa, export_idx, try Module.ErrorMsg.create(
                 gpa,
-                decl.navSrcLoc(mod).upgrade(mod),
+                decl.navSrcLoc(mod),
                 "Unimplemented: ExportOptions.section '{s}'",
                 .{section},
             ));
@@ -915,7 +915,7 @@ pub fn updateExports(
             .link_once => {
                 try mod.failed_exports.putNoClobber(gpa, export_idx, try Module.ErrorMsg.create(
                     gpa,
-                    decl.navSrcLoc(mod).upgrade(mod),
+                    decl.navSrcLoc(mod),
                     "Unimplemented: LinkOnce",
                     .{},
                 ));
src/link/Coff.zig
@@ -1144,7 +1144,7 @@ pub fn updateFunc(self: *Coff, mod: *Module, func_index: InternPool.Index, air:
 
     const res = try codegen.generateFunction(
         &self.base,
-        decl.navSrcLoc(mod).upgrade(mod),
+        decl.navSrcLoc(mod),
         func_index,
         air,
         liveness,
@@ -1179,7 +1179,7 @@ pub fn lowerUnnamedConst(self: *Coff, val: Value, decl_index: InternPool.DeclInd
     const sym_name = try std.fmt.allocPrint(gpa, "__unnamed_{}_{d}", .{ decl_name.fmt(&mod.intern_pool), index });
     defer gpa.free(sym_name);
     const ty = val.typeOf(mod);
-    const atom_index = switch (try self.lowerConst(sym_name, val, ty.abiAlignment(mod), self.rdata_section_index.?, decl.navSrcLoc(mod).upgrade(mod))) {
+    const atom_index = switch (try self.lowerConst(sym_name, val, ty.abiAlignment(mod), self.rdata_section_index.?, decl.navSrcLoc(mod))) {
         .ok => |atom_index| atom_index,
         .fail => |em| {
             decl.analysis = .codegen_failure;
@@ -1197,7 +1197,7 @@ const LowerConstResult = union(enum) {
     fail: *Module.ErrorMsg,
 };
 
-fn lowerConst(self: *Coff, name: []const u8, val: Value, required_alignment: InternPool.Alignment, sect_id: u16, src_loc: Module.SrcLoc) !LowerConstResult {
+fn lowerConst(self: *Coff, name: []const u8, val: Value, required_alignment: InternPool.Alignment, sect_id: u16, src_loc: Module.LazySrcLoc) !LowerConstResult {
     const gpa = self.base.comp.gpa;
 
     var code_buffer = std.ArrayList(u8).init(gpa);
@@ -1270,7 +1270,7 @@ pub fn updateDecl(
     defer code_buffer.deinit();
 
     const decl_val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val;
-    const res = try codegen.generateSymbol(&self.base, decl.navSrcLoc(mod).upgrade(mod), decl_val, &code_buffer, .none, .{
+    const res = try codegen.generateSymbol(&self.base, decl.navSrcLoc(mod), decl_val, &code_buffer, .none, .{
         .parent_atom_index = atom.getSymbolIndex().?,
     });
     const code = switch (res) {
@@ -1309,14 +1309,7 @@ fn updateLazySymbolAtom(
     const atom = self.getAtomPtr(atom_index);
     const local_sym_index = atom.getSymbolIndex().?;
 
-    const src = if (sym.ty.srcLocOrNull(mod)) |src|
-        src.upgrade(mod)
-    else
-        Module.SrcLoc{
-            .file_scope = undefined,
-            .base_node = undefined,
-            .lazy = .unneeded,
-        };
+    const src = sym.ty.srcLocOrNull(mod) orelse Module.LazySrcLoc.unneeded;
     const res = try codegen.generateLazySymbol(
         &self.base,
         src,
@@ -1560,7 +1553,7 @@ pub fn updateExports(
         },
         .value => |value| self.anon_decls.getPtr(value) orelse blk: {
             const first_exp = mod.all_exports.items[export_indices[0]];
-            const res = try self.lowerAnonDecl(value, .none, first_exp.getSrcLoc(mod));
+            const res = try self.lowerAnonDecl(value, .none, first_exp.src);
             switch (res) {
                 .ok => {},
                 .fail => |em| {
@@ -1585,7 +1578,7 @@ pub fn updateExports(
             if (!mem.eql(u8, section_name, ".text")) {
                 try mod.failed_exports.putNoClobber(gpa, export_idx, try Module.ErrorMsg.create(
                     gpa,
-                    exp.getSrcLoc(mod),
+                    exp.src,
                     "Unimplemented: ExportOptions.section",
                     .{},
                 ));
@@ -1596,7 +1589,7 @@ pub fn updateExports(
         if (exp.opts.linkage == .link_once) {
             try mod.failed_exports.putNoClobber(gpa, export_idx, try Module.ErrorMsg.create(
                 gpa,
-                exp.getSrcLoc(mod),
+                exp.src,
                 "Unimplemented: GlobalLinkage.link_once",
                 .{},
             ));
@@ -1867,7 +1860,7 @@ pub fn lowerAnonDecl(
     self: *Coff,
     decl_val: InternPool.Index,
     explicit_alignment: InternPool.Alignment,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
 ) !codegen.Result {
     const gpa = self.base.comp.gpa;
     const mod = self.base.comp.module.?;
src/link/Elf.zig
@@ -552,7 +552,7 @@ pub fn lowerAnonDecl(
     self: *Elf,
     decl_val: InternPool.Index,
     explicit_alignment: InternPool.Alignment,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
 ) !codegen.Result {
     return self.zigObjectPtr().?.lowerAnonDecl(self, decl_val, explicit_alignment, src_loc);
 }
src/link/MachO.zig
@@ -3228,7 +3228,7 @@ pub fn lowerAnonDecl(
     self: *MachO,
     decl_val: InternPool.Index,
     explicit_alignment: InternPool.Alignment,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
 ) !codegen.Result {
     return self.getZigObject().?.lowerAnonDecl(self, decl_val, explicit_alignment, src_loc);
 }
src/link/Plan9.zig
@@ -439,7 +439,7 @@ pub fn updateFunc(self: *Plan9, mod: *Module, func_index: InternPool.Index, air:
 
     const res = try codegen.generateFunction(
         &self.base,
-        decl.navSrcLoc(mod).upgrade(mod),
+        decl.navSrcLoc(mod),
         func_index,
         air,
         liveness,
@@ -505,7 +505,7 @@ pub fn lowerUnnamedConst(self: *Plan9, val: Value, decl_index: InternPool.DeclIn
     };
     self.syms.items[info.sym_index.?] = sym;
 
-    const res = try codegen.generateSymbol(&self.base, decl.navSrcLoc(mod).upgrade(mod), val, &code_buffer, .{
+    const res = try codegen.generateSymbol(&self.base, decl.navSrcLoc(mod), val, &code_buffer, .{
         .none = {},
     }, .{
         .parent_atom_index = new_atom_idx,
@@ -544,7 +544,7 @@ pub fn updateDecl(self: *Plan9, mod: *Module, decl_index: InternPool.DeclIndex)
     defer code_buffer.deinit();
     const decl_val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val;
     // TODO we need the symbol index for symbol in the table of locals for the containing atom
-    const res = try codegen.generateSymbol(&self.base, decl.navSrcLoc(mod).upgrade(mod), decl_val, &code_buffer, .{ .none = {} }, .{
+    const res = try codegen.generateSymbol(&self.base, decl.navSrcLoc(mod), decl_val, &code_buffer, .{ .none = {} }, .{
         .parent_atom_index = @as(Atom.Index, @intCast(atom_idx)),
     });
     const code = switch (res) {
@@ -1027,7 +1027,7 @@ fn addDeclExports(
             {
                 try mod.failed_exports.put(mod.gpa, export_idx, try Module.ErrorMsg.create(
                     gpa,
-                    mod.declPtr(decl_index).navSrcLoc(mod).upgrade(mod),
+                    mod.declPtr(decl_index).navSrcLoc(mod),
                     "plan9 does not support extra sections",
                     .{},
                 ));
@@ -1225,14 +1225,7 @@ fn updateLazySymbolAtom(self: *Plan9, sym: File.LazySymbol, atom_index: Atom.Ind
     self.syms.items[self.getAtomPtr(atom_index).sym_index.?] = symbol;
 
     // generate the code
-    const src = if (sym.ty.srcLocOrNull(mod)) |src|
-        src.upgrade(mod)
-    else
-        Module.SrcLoc{
-            .file_scope = undefined,
-            .base_node = undefined,
-            .lazy = .unneeded,
-        };
+    const src = sym.ty.srcLocOrNull(mod) orelse Module.LazySrcLoc.unneeded;
     const res = try codegen.generateLazySymbol(
         &self.base,
         src,
@@ -1553,7 +1546,7 @@ pub fn lowerAnonDecl(
     self: *Plan9,
     decl_val: InternPool.Index,
     explicit_alignment: InternPool.Alignment,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
 ) !codegen.Result {
     _ = explicit_alignment;
     // This is basically the same as lowerUnnamedConst.
src/link/Wasm.zig
@@ -1533,7 +1533,7 @@ pub fn lowerAnonDecl(
     wasm: *Wasm,
     decl_val: InternPool.Index,
     explicit_alignment: Alignment,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
 ) !codegen.Result {
     return wasm.zigObjectPtr().?.lowerAnonDecl(wasm, decl_val, explicit_alignment, src_loc);
 }
src/codegen.zig
@@ -47,7 +47,7 @@ pub const DebugInfoOutput = union(enum) {
 
 pub fn generateFunction(
     lf: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     func_index: InternPool.Index,
     air: Air,
     liveness: Liveness,
@@ -79,7 +79,7 @@ pub fn generateFunction(
 
 pub fn generateLazyFunction(
     lf: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     lazy_sym: link.File.LazySymbol,
     code: *std.ArrayList(u8),
     debug_output: DebugInfoOutput,
@@ -105,7 +105,7 @@ fn writeFloat(comptime F: type, f: F, target: Target, endian: std.builtin.Endian
 
 pub fn generateLazySymbol(
     bin_file: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     lazy_sym: link.File.LazySymbol,
     // TODO don't use an "out" parameter like this; put it in the result instead
     alignment: *Alignment,
@@ -171,7 +171,7 @@ pub fn generateLazySymbol(
 
 pub fn generateSymbol(
     bin_file: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     val: Value,
     code: *std.ArrayList(u8),
     debug_output: DebugInfoOutput,
@@ -618,7 +618,7 @@ pub fn generateSymbol(
 
 fn lowerPtr(
     bin_file: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     ptr_val: InternPool.Index,
     code: *std.ArrayList(u8),
     debug_output: DebugInfoOutput,
@@ -683,7 +683,7 @@ const RelocInfo = struct {
 
 fn lowerAnonDeclRef(
     lf: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl,
     code: *std.ArrayList(u8),
     debug_output: DebugInfoOutput,
@@ -730,7 +730,7 @@ fn lowerAnonDeclRef(
 
 fn lowerDeclRef(
     lf: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     decl_index: InternPool.DeclIndex,
     code: *std.ArrayList(u8),
     debug_output: DebugInfoOutput,
@@ -814,7 +814,7 @@ pub const GenResult = union(enum) {
 
     fn fail(
         gpa: Allocator,
-        src_loc: Module.SrcLoc,
+        src_loc: Module.LazySrcLoc,
         comptime format: []const u8,
         args: anytype,
     ) Allocator.Error!GenResult {
@@ -825,7 +825,7 @@ pub const GenResult = union(enum) {
 
 fn genDeclRef(
     lf: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     val: Value,
     ptr_decl_index: InternPool.DeclIndex,
 ) CodeGenError!GenResult {
@@ -931,7 +931,7 @@ fn genDeclRef(
 
 fn genUnnamedConst(
     lf: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     val: Value,
     owner_decl_index: InternPool.DeclIndex,
 ) CodeGenError!GenResult {
@@ -970,7 +970,7 @@ fn genUnnamedConst(
 
 pub fn genTypedValue(
     lf: *link.File,
-    src_loc: Module.SrcLoc,
+    src_loc: Module.LazySrcLoc,
     val: Value,
     owner_decl_index: InternPool.DeclIndex,
 ) CodeGenError!GenResult {
src/Compilation.zig
@@ -2629,22 +2629,24 @@ fn reportMultiModuleErrors(mod: *Module) !void {
             for (notes[0..num_notes], file.references.items[0..num_notes], 0..) |*note, ref, i| {
                 errdefer for (notes[0..i]) |*n| n.deinit(mod.gpa);
                 note.* = switch (ref) {
-                    .import => |loc| blk: {
-                        break :blk try Module.ErrorMsg.init(
-                            mod.gpa,
-                            loc,
-                            "imported from module {s}",
-                            .{loc.file_scope.mod.fully_qualified_name},
-                        );
-                    },
-                    .root => |pkg| blk: {
-                        break :blk try Module.ErrorMsg.init(
-                            mod.gpa,
-                            .{ .file_scope = file, .base_node = 0, .lazy = .entire_file },
-                            "root of module {s}",
-                            .{pkg.fully_qualified_name},
-                        );
-                    },
+                    .import => |import| try Module.ErrorMsg.init(
+                        mod.gpa,
+                        .{
+                            .base_node_inst = try mod.intern_pool.trackZir(mod.gpa, import.file, .main_struct_inst),
+                            .offset = .{ .token_abs = import.token },
+                        },
+                        "imported from module {s}",
+                        .{import.file.mod.fully_qualified_name},
+                    ),
+                    .root => |pkg| try Module.ErrorMsg.init(
+                        mod.gpa,
+                        .{
+                            .base_node_inst = try mod.intern_pool.trackZir(mod.gpa, file, .main_struct_inst),
+                            .offset = .entire_file,
+                        },
+                        "root of module {s}",
+                        .{pkg.fully_qualified_name},
+                    ),
                 };
             }
             errdefer for (notes[0..num_notes]) |*n| n.deinit(mod.gpa);
@@ -2652,7 +2654,10 @@ fn reportMultiModuleErrors(mod: *Module) !void {
             if (omitted > 0) {
                 notes[num_notes] = try Module.ErrorMsg.init(
                     mod.gpa,
-                    .{ .file_scope = file, .base_node = 0, .lazy = .entire_file },
+                    .{
+                        .base_node_inst = try mod.intern_pool.trackZir(mod.gpa, file, .main_struct_inst),
+                        .offset = .entire_file,
+                    },
                     "{} more references omitted",
                     .{omitted},
                 );
@@ -2661,7 +2666,10 @@ fn reportMultiModuleErrors(mod: *Module) !void {
 
             const err = try Module.ErrorMsg.create(
                 mod.gpa,
-                .{ .file_scope = file, .base_node = 0, .lazy = .entire_file },
+                .{
+                    .base_node_inst = try mod.intern_pool.trackZir(mod.gpa, file, .main_struct_inst),
+                    .offset = .entire_file,
+                },
                 "file exists in multiple modules",
                 .{},
             );
@@ -3060,7 +3068,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
 
             const values = zcu.compile_log_sources.values();
             // First one will be the error; subsequent ones will be notes.
-            const src_loc = values[0].src().upgrade(zcu);
+            const src_loc = values[0].src();
             const err_msg: Module.ErrorMsg = .{
                 .src_loc = src_loc,
                 .msg = "found compile log statement",
@@ -3070,7 +3078,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
 
             for (values[1..], err_msg.notes) |src_info, *note| {
                 note.* = .{
-                    .src_loc = src_info.src().upgrade(zcu),
+                    .src_loc = src_info.src(),
                     .msg = "also here",
                 };
             }
@@ -3139,8 +3147,9 @@ pub fn addModuleErrorMsg(
 ) !void {
     const gpa = eb.gpa;
     const ip = &mod.intern_pool;
-    const err_source = module_err_msg.src_loc.file_scope.getSource(gpa) catch |err| {
-        const file_path = try module_err_msg.src_loc.file_scope.fullPath(gpa);
+    const err_src_loc = module_err_msg.src_loc.upgrade(mod);
+    const err_source = err_src_loc.file_scope.getSource(gpa) catch |err| {
+        const file_path = try err_src_loc.file_scope.fullPath(gpa);
         defer gpa.free(file_path);
         try eb.addRootErrorMessage(.{
             .msg = try eb.printString("unable to load '{s}': {s}", .{
@@ -3149,9 +3158,9 @@ pub fn addModuleErrorMsg(
         });
         return;
     };
-    const err_span = try module_err_msg.src_loc.span(gpa);
+    const err_span = try err_src_loc.span(gpa);
     const err_loc = std.zig.findLineColumn(err_source.bytes, err_span.main);
-    const file_path = try module_err_msg.src_loc.file_scope.fullPath(gpa);
+    const file_path = try err_src_loc.file_scope.fullPath(gpa);
     defer gpa.free(file_path);
 
     var ref_traces: std.ArrayListUnmanaged(ErrorBundle.ReferenceTrace) = .{};
@@ -3208,7 +3217,7 @@ pub fn addModuleErrorMsg(
         .span_end = err_span.end,
         .line = @intCast(err_loc.line),
         .column = @intCast(err_loc.column),
-        .source_line = if (module_err_msg.src_loc.lazy == .entire_file)
+        .source_line = if (err_src_loc.lazy == .entire_file)
             0
         else
             try eb.addString(err_loc.source_line),
@@ -3225,10 +3234,11 @@ pub fn addModuleErrorMsg(
     defer notes.deinit(gpa);
 
     for (module_err_msg.notes) |module_note| {
-        const source = try module_note.src_loc.file_scope.getSource(gpa);
-        const span = try module_note.src_loc.span(gpa);
+        const note_src_loc = module_note.src_loc.upgrade(mod);
+        const source = try note_src_loc.file_scope.getSource(gpa);
+        const span = try note_src_loc.span(gpa);
         const loc = std.zig.findLineColumn(source.bytes, span.main);
-        const note_file_path = try module_note.src_loc.file_scope.fullPath(gpa);
+        const note_file_path = try note_src_loc.file_scope.fullPath(gpa);
         defer gpa.free(note_file_path);
 
         const gop = try notes.getOrPutContext(gpa, .{
@@ -3522,7 +3532,7 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: std.Progress.Node) !vo
                     InternPool.AnalUnit.wrap(.{ .decl = decl_index }),
                     try Module.ErrorMsg.create(
                         gpa,
-                        decl.navSrcLoc(module).upgrade(module),
+                        decl.navSrcLoc(module),
                         "unable to update line number: {s}",
                         .{@errorName(err)},
                     ),
@@ -4023,9 +4033,8 @@ fn workerAstGenFile(
                 const res = mod.importFile(file, import_path) catch continue;
                 if (!res.is_pkg) {
                     res.file.addReference(mod.*, .{ .import = .{
-                        .file_scope = file,
-                        .base_node = 0,
-                        .lazy = .{ .token_abs = item.data.token },
+                        .file = file,
+                        .token = item.data.token,
                     } }) catch continue;
                 }
                 break :blk res;
@@ -4398,20 +4407,14 @@ fn reportRetryableAstGenError(
 
     file.status = .retryable_failure;
 
-    const src_loc: Module.SrcLoc = switch (src) {
+    const src_loc: Module.LazySrcLoc = switch (src) {
         .root => .{
-            .file_scope = file,
-            .base_node = 0,
-            .lazy = .entire_file,
+            .base_node_inst = try mod.intern_pool.trackZir(gpa, file, .main_struct_inst),
+            .offset = .entire_file,
         },
-        .import => |info| blk: {
-            const importing_file = info.importing_file;
-
-            break :blk .{
-                .file_scope = importing_file,
-                .base_node = 0,
-                .lazy = .{ .token_abs = info.import_tok },
-            };
+        .import => |info| .{
+            .base_node_inst = try mod.intern_pool.trackZir(gpa, info.importing_file, .main_struct_inst),
+            .offset = .{ .token_abs = info.import_tok },
         },
     };
 
src/link.zig
@@ -646,7 +646,7 @@ pub const File = struct {
         base: *File,
         decl_val: InternPool.Index,
         decl_align: InternPool.Alignment,
-        src_loc: Module.SrcLoc,
+        src_loc: Module.LazySrcLoc,
     ) !LowerResult {
         if (build_options.only_c) @compileError("unreachable");
         switch (base.tag) {
src/Sema.zig
@@ -2425,8 +2425,7 @@ pub fn errNote(
     comptime format: []const u8,
     args: anytype,
 ) error{OutOfMemory}!void {
-    const zcu = sema.mod;
-    return zcu.errNoteNonLazy(src.upgrade(zcu), parent, format, args);
+    return sema.mod.errNote(src, parent, format, args);
 }
 
 fn addFieldErrNote(
@@ -2454,7 +2453,7 @@ pub fn errMsg(
     args: anytype,
 ) Allocator.Error!*Module.ErrorMsg {
     assert(src.offset != .unneeded);
-    return Module.ErrorMsg.create(sema.gpa, src.upgrade(sema.mod), format, args);
+    return Module.ErrorMsg.create(sema.gpa, src, format, args);
 }
 
 pub fn fail(
@@ -2542,7 +2541,6 @@ fn reparentOwnedErrorMsg(
     args: anytype,
 ) !void {
     const mod = sema.mod;
-    const resolved_src = src.upgrade(mod);
     const msg_str = try std.fmt.allocPrint(mod.gpa, format, args);
 
     const orig_notes = msg.notes.len;
@@ -2553,7 +2551,7 @@ fn reparentOwnedErrorMsg(
         .msg = msg.msg,
     };
 
-    msg.src_loc = resolved_src;
+    msg.src_loc = src;
     msg.msg = msg_str;
 }
 
@@ -13883,7 +13881,7 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
         return sema.fail(block, operand_src, "file path name cannot be empty", .{});
     }
 
-    const val = mod.embedFile(block.getFileScope(mod), name, operand_src.upgrade(mod)) catch |err| switch (err) {
+    const val = mod.embedFile(block.getFileScope(mod), name, operand_src) catch |err| switch (err) {
         error.ImportOutsideModulePath => {
             return sema.fail(block, operand_src, "embed of file outside package path: '{s}'", .{name});
         },
src/Zcu.zig
@@ -289,10 +289,6 @@ pub const Export = struct {
         section: InternPool.OptionalNullTerminatedString = .none,
         visibility: std.builtin.SymbolVisibility = .default,
     };
-
-    pub fn getSrcLoc(exp: Export, mod: *Module) SrcLoc {
-        return exp.src.upgrade(mod);
-    }
 };
 
 pub const Reference = struct {
@@ -746,7 +742,10 @@ pub const File = struct {
     /// A single reference to a file.
     pub const Reference = union(enum) {
         /// The file is imported directly (i.e. not as a package) with @import.
-        import: SrcLoc,
+        import: struct {
+            file: *File,
+            token: Ast.TokenIndex,
+        },
         /// The file is the root of a module.
         root: *Package.Module,
     };
@@ -900,7 +899,7 @@ pub const File = struct {
     }
 
     /// Add a reference to this file during AstGen.
-    pub fn addReference(file: *File, mod: Module, ref: File.Reference) !void {
+    pub fn addReference(file: *File, zcu: Zcu, ref: File.Reference) !void {
         // Don't add the same module root twice. Note that since we always add module roots at the
         // front of the references array (see below), this loop is actually O(1) on valid code.
         if (ref == .root) {
@@ -917,17 +916,17 @@ pub const File = struct {
             // to make multi-module errors more helpful (since "root-of" notes are generally more
             // informative than "imported-from" notes). This path is hit very rarely, so the speed
             // of the insert operation doesn't matter too much.
-            .root => try file.references.insert(mod.gpa, 0, ref),
+            .root => try file.references.insert(zcu.gpa, 0, ref),
 
             // Other references we'll just put at the end.
-            else => try file.references.append(mod.gpa, ref),
+            else => try file.references.append(zcu.gpa, ref),
         }
 
-        const pkg = switch (ref) {
-            .import => |loc| loc.file_scope.mod,
-            .root => |pkg| pkg,
+        const mod = switch (ref) {
+            .import => |import| import.file.mod,
+            .root => |mod| mod,
         };
-        if (pkg != file.mod) file.multi_pkg = true;
+        if (mod != file.mod) file.multi_pkg = true;
     }
 
     /// Mark this file and every file referenced by it as multi_pkg and report an
@@ -967,30 +966,25 @@ pub const EmbedFile = struct {
     owner: *Package.Module,
     stat: Cache.File.Stat,
     val: InternPool.Index,
-    src_loc: SrcLoc,
+    src_loc: LazySrcLoc,
 };
 
 /// This struct holds data necessary to construct API-facing `AllErrors.Message`.
 /// Its memory is managed with the general purpose allocator so that they
 /// can be created and destroyed in response to incremental updates.
 pub const ErrorMsg = struct {
-    src_loc: SrcLoc,
+    src_loc: LazySrcLoc,
     msg: []const u8,
     notes: []ErrorMsg = &.{},
     reference_trace_root: AnalUnit.Optional = .none,
 
-    pub const Trace = struct {
-        decl: InternPool.NullTerminatedString,
-        src_loc: SrcLoc,
-    };
-
     pub fn create(
         gpa: Allocator,
-        src_loc: SrcLoc,
+        src_loc: LazySrcLoc,
         comptime format: []const u8,
         args: anytype,
     ) !*ErrorMsg {
-        assert(src_loc.lazy != .unneeded);
+        assert(src_loc.offset != .unneeded);
         const err_msg = try gpa.create(ErrorMsg);
         errdefer gpa.destroy(err_msg);
         err_msg.* = try ErrorMsg.init(gpa, src_loc, format, args);
@@ -1006,7 +1000,7 @@ pub const ErrorMsg = struct {
 
     pub fn init(
         gpa: Allocator,
-        src_loc: SrcLoc,
+        src_loc: LazySrcLoc,
         comptime format: []const u8,
         args: anytype,
     ) !ErrorMsg {
@@ -1994,15 +1988,12 @@ pub const LazySrcLoc = struct {
         entire_file,
         /// The source location points to a byte offset within a source file,
         /// offset from 0. The source file is determined contextually.
-        /// Inside a `SrcLoc`, the `file_scope` union field will be active.
         byte_abs: u32,
         /// The source location points to a token within a source file,
         /// offset from 0. The source file is determined contextually.
-        /// Inside a `SrcLoc`, the `file_scope` union field will be active.
         token_abs: u32,
         /// The source location points to an AST node within a source file,
         /// offset from 0. The source file is determined contextually.
-        /// Inside a `SrcLoc`, the `file_scope` union field will be active.
         node_abs: u32,
         /// The source location points to a byte offset within a source file,
         /// offset from the byte offset of the base node within the file.
@@ -2373,8 +2364,7 @@ pub const LazySrcLoc = struct {
     }
 
     /// Resolve the file and AST node of `base_node_inst` to get a resolved `SrcLoc`.
-    /// TODO: it is incorrect to store a `SrcLoc` anywhere due to incremental compilation.
-    /// Probably the type should be removed entirely and this resolution performed on-the-fly when needed.
+    /// The resulting `SrcLoc` should only be used ephemerally, as it is not correct across incremental updates.
     pub fn upgrade(lazy: LazySrcLoc, zcu: *Zcu) SrcLoc {
         const file, const base_node = resolveBaseNode(lazy.base_node_inst, zcu);
         return .{
@@ -3478,7 +3468,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
                 try mod.retryable_failures.append(mod.gpa, AnalUnit.wrap(.{ .decl = decl_index }));
                 mod.failed_analysis.putAssumeCapacityNoClobber(AnalUnit.wrap(.{ .decl = decl_index }), try ErrorMsg.create(
                     mod.gpa,
-                    decl.navSrcLoc(mod).upgrade(mod),
+                    decl.navSrcLoc(mod),
                     "unable to analyze: {s}",
                     .{@errorName(e)},
                 ));
@@ -3655,7 +3645,7 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, maybe_coerced_func_index: InternPool.In
                     AnalUnit.wrap(.{ .decl = decl_index }),
                     try Module.ErrorMsg.create(
                         gpa,
-                        decl.navSrcLoc(zcu).upgrade(zcu),
+                        decl.navSrcLoc(zcu),
                         "invalid liveness: {s}",
                         .{@errorName(err)},
                     ),
@@ -3679,7 +3669,7 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, maybe_coerced_func_index: InternPool.In
                 try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
                 zcu.failed_analysis.putAssumeCapacityNoClobber(AnalUnit.wrap(.{ .decl = decl_index }), try Module.ErrorMsg.create(
                     gpa,
-                    decl.navSrcLoc(zcu).upgrade(zcu),
+                    decl.navSrcLoc(zcu),
                     "unable to codegen: {s}",
                     .{@errorName(err)},
                 ));
@@ -4480,7 +4470,7 @@ pub fn embedFile(
     mod: *Module,
     cur_file: *File,
     import_string: []const u8,
-    src_loc: SrcLoc,
+    src_loc: LazySrcLoc,
 ) !InternPool.Index {
     const gpa = mod.gpa;
 
@@ -4555,7 +4545,7 @@ fn newEmbedFile(
     sub_file_path: []const u8,
     resolved_path: []const u8,
     result: **EmbedFile,
-    src_loc: SrcLoc,
+    src_loc: LazySrcLoc,
 ) !InternPool.Index {
     const gpa = mod.gpa;
     const ip = &mod.intern_pool;
@@ -5320,17 +5310,13 @@ pub fn initNewAnonDecl(
     new_decl.analysis = .complete;
 }
 
-pub fn errNoteNonLazy(
+pub fn errNote(
     mod: *Module,
-    src_loc: SrcLoc,
+    src_loc: LazySrcLoc,
     parent: *ErrorMsg,
     comptime format: []const u8,
     args: anytype,
 ) error{OutOfMemory}!void {
-    if (src_loc.lazy == .unneeded) {
-        assert(parent.src_loc.lazy == .unneeded);
-        return;
-    }
     const msg = try std.fmt.allocPrint(mod.gpa, format, args);
     errdefer mod.gpa.free(msg);
 
@@ -5458,14 +5444,12 @@ fn processExportsInner(
         if (gop.found_existing) {
             new_export.status = .failed_retryable;
             try zcu.failed_exports.ensureUnusedCapacity(gpa, 1);
-            const src_loc = new_export.getSrcLoc(zcu);
-            const msg = try ErrorMsg.create(gpa, src_loc, "exported symbol collision: {}", .{
+            const msg = try ErrorMsg.create(gpa, new_export.src, "exported symbol collision: {}", .{
                 new_export.opts.name.fmt(&zcu.intern_pool),
             });
             errdefer msg.destroy(gpa);
             const other_export = zcu.all_exports.items[gop.value_ptr.*];
-            const other_src_loc = other_export.getSrcLoc(zcu);
-            try zcu.errNoteNonLazy(other_src_loc, msg, "other symbol here", .{});
+            try zcu.errNote(other_export.src, msg, "other symbol here", .{});
             zcu.failed_exports.putAssumeCapacityNoClobber(export_idx, msg);
             new_export.status = .failed;
         } else {
@@ -5493,8 +5477,7 @@ fn handleUpdateExports(
             const new_export = &zcu.all_exports.items[export_idx];
             new_export.status = .failed_retryable;
             try zcu.failed_exports.ensureUnusedCapacity(gpa, 1);
-            const src_loc = new_export.getSrcLoc(zcu);
-            const msg = try ErrorMsg.create(gpa, src_loc, "unable to export: {s}", .{
+            const msg = try ErrorMsg.create(gpa, new_export.src, "unable to export: {s}", .{
                 @errorName(err),
             });
             zcu.failed_exports.putAssumeCapacityNoClobber(export_idx, msg);
@@ -5658,7 +5641,7 @@ pub fn linkerUpdateDecl(zcu: *Zcu, decl_index: Decl.Index) !void {
                 try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
                 zcu.failed_analysis.putAssumeCapacityNoClobber(AnalUnit.wrap(.{ .decl = decl_index }), try ErrorMsg.create(
                     gpa,
-                    decl.navSrcLoc(zcu).upgrade(zcu),
+                    decl.navSrcLoc(zcu),
                     "unable to codegen: {s}",
                     .{@errorName(err)},
                 ));
@@ -5685,9 +5668,8 @@ fn reportRetryableFileError(
     const err_msg = try ErrorMsg.create(
         mod.gpa,
         .{
-            .file_scope = file,
-            .base_node = 0,
-            .lazy = .entire_file,
+            .base_node_inst = try mod.intern_pool.trackZir(mod.gpa, file, .main_struct_inst),
+            .offset = .entire_file,
         },
         format,
         args,