Commit 424e6ac54b

mlugg <mlugg@mlugg.co.uk>
2025-05-28 01:31:16
compiler: minor refactors to ZCU linking
* The `codegen_nav`, `codegen_func`, `codegen_type` tasks are renamed to `link_nav`, `link_func`, and `link_type`, to more accurately reflect their purpose of sending data to the *linker*. Currently, `link_func` remains responsible for codegen; this will change in an upcoming commit. * Don't go on a pointless detour through `PerThread` when linking ZCU functions/`Nav`s; so, the `linkerUpdateNav` etc logic now lives in `link.zig`. Currently, `linkerUpdateFunc` is an exception, because it has broader responsibilities including codegen, but this will be solved in an upcoming commit.
1 parent 4a02e08
src/Sema/LowerZon.zig
@@ -194,7 +194,7 @@ fn lowerExprAnonResTy(self: *LowerZon, node: Zoir.Node.Index) CompileError!Inter
                     codegen_type: {
                         if (pt.zcu.comp.config.use_llvm) break :codegen_type;
                         if (self.block.ownerModule().strip) break :codegen_type;
-                        try pt.zcu.comp.queueJob(.{ .codegen_type = wip.index });
+                        try pt.zcu.comp.queueJob(.{ .link_type = wip.index });
                     }
                     break :ty wip.finish(ip, new_namespace_index);
                 },
src/Zcu/PerThread.zig
@@ -1320,7 +1320,7 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr
         }
 
         // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_nav = nav_id });
+        try zcu.comp.queueJob(.{ .link_nav = nav_id });
     }
 
     switch (old_nav.status) {
@@ -1716,7 +1716,7 @@ fn analyzeFuncBody(
     }
 
     // This job depends on any resolve_type_fully jobs queued up before it.
-    try comp.queueJob(.{ .codegen_func = .{
+    try comp.queueJob(.{ .link_func = .{
         .func = func_index,
         .air = air,
     } });
@@ -1880,7 +1880,7 @@ fn createFileRootStruct(
         if (zcu.comp.config.use_llvm) break :codegen_type;
         if (file.mod.?.strip) break :codegen_type;
         // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
+        try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
     }
     zcu.setFileRootType(file_index, wip_ty.index);
     if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
@@ -3457,73 +3457,8 @@ pub fn populateTestFunctions(
             zcu.codegen_prog_node = std.Progress.Node.none;
         }
 
-        try pt.linkerUpdateNav(nav_index);
-    }
-}
-
-pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) error{OutOfMemory}!void {
-    const zcu = pt.zcu;
-    const comp = zcu.comp;
-    const gpa = zcu.gpa;
-    const ip = &zcu.intern_pool;
-
-    const nav = zcu.intern_pool.getNav(nav_index);
-    const codegen_prog_node = zcu.codegen_prog_node.start(nav.fqn.toSlice(ip), 0);
-    defer codegen_prog_node.end();
-
-    if (!Air.valFullyResolved(zcu.navValue(nav_index), zcu)) {
-        // The value of this nav failed to resolve. This is a transitive failure.
-        // TODO: do we need to mark this failure anywhere? I don't think so, since compilation
-        // will fail due to the type error anyway.
-    } else if (comp.bin_file) |lf| {
-        lf.updateNav(pt, nav_index) catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-            error.CodegenFail => assert(zcu.failed_codegen.contains(nav_index)),
-            error.Overflow, error.RelocationNotByteAligned => {
-                try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create(
-                    gpa,
-                    zcu.navSrcLoc(nav_index),
-                    "unable to codegen: {s}",
-                    .{@errorName(err)},
-                ));
-                // Not a retryable failure.
-            },
-        };
-    } else if (zcu.llvm_object) |llvm_object| {
-        llvm_object.updateNav(pt, nav_index) catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-        };
-    }
-}
-
-pub fn linkerUpdateContainerType(pt: Zcu.PerThread, ty: InternPool.Index) error{OutOfMemory}!void {
-    const zcu = pt.zcu;
-    const gpa = zcu.gpa;
-    const comp = zcu.comp;
-    const ip = &zcu.intern_pool;
-
-    const codegen_prog_node = zcu.codegen_prog_node.start(Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), 0);
-    defer codegen_prog_node.end();
-
-    if (zcu.failed_types.fetchSwapRemove(ty)) |*entry| entry.value.deinit(gpa);
-
-    if (!Air.typeFullyResolved(Type.fromInterned(ty), zcu)) {
-        // This type failed to resolve. This is a transitive failure.
-        return;
-    }
-
-    if (comp.bin_file) |lf| lf.updateContainerType(pt, ty) catch |err| switch (err) {
-        error.OutOfMemory => return error.OutOfMemory,
-        error.TypeFailureReported => assert(zcu.failed_types.contains(ty)),
-    };
-}
-
-pub fn linkerUpdateLineNumber(pt: Zcu.PerThread, ti: InternPool.TrackedInst.Index) !void {
-    if (pt.zcu.comp.bin_file) |lf| {
-        lf.updateLineNumber(pt, ti) catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-            else => |e| log.err("update line number failed: {s}", .{@errorName(e)}),
-        };
+        // The linker thread is not running, so we actually need to dispatch this task directly.
+        @import("../link.zig").doTask(zcu.comp, @intFromEnum(pt.tid), .{ .link_nav = nav_index });
     }
 }
 
@@ -3984,7 +3919,7 @@ pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error!
     const result = try pt.zcu.intern_pool.getExtern(pt.zcu.gpa, pt.tid, key);
     if (result.new_nav.unwrap()) |nav| {
         // This job depends on any resolve_type_fully jobs queued up before it.
-        try pt.zcu.comp.queueJob(.{ .codegen_nav = nav });
+        try pt.zcu.comp.queueJob(.{ .link_nav = nav });
         if (pt.zcu.comp.debugIncremental()) try pt.zcu.incremental_debug_state.newNav(pt.zcu, nav);
     }
     return result.index;
@@ -4132,7 +4067,7 @@ fn recreateStructType(
         if (zcu.comp.config.use_llvm) break :codegen_type;
         if (file.mod.?.strip) break :codegen_type;
         // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
+        try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
     }
 
     if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
@@ -4225,7 +4160,7 @@ fn recreateUnionType(
         if (zcu.comp.config.use_llvm) break :codegen_type;
         if (file.mod.?.strip) break :codegen_type;
         // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
+        try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
     }
 
     if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
src/Compilation.zig
@@ -848,17 +848,18 @@ pub const RcIncludes = enum {
 const Job = union(enum) {
     /// Corresponds to the task in `link.Task`.
     /// Only needed for backends that haven't yet been updated to not race against Sema.
-    codegen_nav: InternPool.Nav.Index,
+    link_nav: InternPool.Nav.Index,
     /// Corresponds to the task in `link.Task`.
+    /// TODO: this is currently also responsible for performing codegen.
     /// Only needed for backends that haven't yet been updated to not race against Sema.
-    codegen_func: link.Task.CodegenFunc,
+    link_func: link.Task.CodegenFunc,
     /// Corresponds to the task in `link.Task`.
     /// Only needed for backends that haven't yet been updated to not race against Sema.
-    codegen_type: InternPool.Index,
+    link_type: InternPool.Index,
     update_line_number: InternPool.TrackedInst.Index,
     /// The `AnalUnit`, which is *not* a `func`, must be semantically analyzed.
     /// This may be its first time being analyzed, or it may be outdated.
-    /// If the unit is a function, a `codegen_func` job will then be queued.
+    /// If the unit is a test function, an `analyze_func` job will then be queued.
     analyze_comptime_unit: InternPool.AnalUnit,
     /// This function must be semantically analyzed.
     /// This may be its first time being analyzed, or it may be outdated.
@@ -879,13 +880,13 @@ const Job = union(enum) {
         return switch (tag) {
             // Prioritize functions so that codegen can get to work on them on a
             // separate thread, while Sema goes back to its own work.
-            .resolve_type_fully, .analyze_func, .codegen_func => 0,
+            .resolve_type_fully, .analyze_func, .link_func => 0,
             else => 1,
         };
     }
     comptime {
         // Job dependencies
-        assert(stage(.resolve_type_fully) <= stage(.codegen_func));
+        assert(stage(.resolve_type_fully) <= stage(.link_func));
     }
 };
 
@@ -4552,7 +4553,7 @@ pub fn queueJobs(comp: *Compilation, jobs: []const Job) !void {
 
 fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void {
     switch (job) {
-        .codegen_nav => |nav_index| {
+        .link_nav => |nav_index| {
             const zcu = comp.zcu.?;
             const nav = zcu.intern_pool.getNav(nav_index);
             if (nav.analysis != null) {
@@ -4562,16 +4563,16 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void {
                 }
             }
             assert(nav.status == .fully_resolved);
-            comp.dispatchCodegenTask(tid, .{ .codegen_nav = nav_index });
+            comp.dispatchLinkTask(tid, .{ .link_nav = nav_index });
         },
-        .codegen_func => |func| {
-            comp.dispatchCodegenTask(tid, .{ .codegen_func = func });
+        .link_func => |func| {
+            comp.dispatchLinkTask(tid, .{ .link_func = func });
         },
-        .codegen_type => |ty| {
-            comp.dispatchCodegenTask(tid, .{ .codegen_type = ty });
+        .link_type => |ty| {
+            comp.dispatchLinkTask(tid, .{ .link_type = ty });
         },
         .update_line_number => |ti| {
-            comp.dispatchCodegenTask(tid, .{ .update_line_number = ti });
+            comp.dispatchLinkTask(tid, .{ .update_line_number = ti });
         },
         .analyze_func => |func| {
             const named_frame = tracy.namedFrame("analyze_func");
@@ -4665,7 +4666,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void {
 
 /// The reason for the double-queue here is that the first queue ensures any
 /// resolve_type_fully tasks are complete before this dispatch function is called.
-fn dispatchCodegenTask(comp: *Compilation, tid: usize, link_task: link.Task) void {
+fn dispatchLinkTask(comp: *Compilation, tid: usize, link_task: link.Task) void {
     if (comp.separateCodegenThreadOk()) {
         comp.queueLinkTasks(&.{link_task});
     } else {
src/link.zig
@@ -704,7 +704,7 @@ pub const File = struct {
     }
 
     /// May be called before or after updateExports for any given Nav.
-    pub fn updateNav(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) UpdateNavError!void {
+    fn updateNav(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) UpdateNavError!void {
         const nav = pt.zcu.intern_pool.getNav(nav_index);
         assert(nav.status == .fully_resolved);
         switch (base.tag) {
@@ -721,7 +721,7 @@ pub const File = struct {
         TypeFailureReported,
     };
 
-    pub fn updateContainerType(base: *File, pt: Zcu.PerThread, ty: InternPool.Index) UpdateContainerTypeError!void {
+    fn updateContainerType(base: *File, pt: Zcu.PerThread, ty: InternPool.Index) UpdateContainerTypeError!void {
         switch (base.tag) {
             else => {},
             inline .elf => |tag| {
@@ -732,6 +732,7 @@ pub const File = struct {
     }
 
     /// May be called before or after updateExports for any given Decl.
+    /// TODO: currently `pub` because `Zcu.PerThread` is calling this.
     pub fn updateFunc(
         base: *File,
         pt: Zcu.PerThread,
@@ -755,7 +756,7 @@ pub const File = struct {
 
     /// On an incremental update, fixup the line number of all `Nav`s at the given `TrackedInst`, because
     /// its line number has changed. The ZIR instruction `ti_id` has tag `.declaration`.
-    pub fn updateLineNumber(base: *File, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) UpdateLineNumberError!void {
+    fn updateLineNumber(base: *File, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) UpdateLineNumberError!void {
         {
             const ti = ti_id.resolveFull(&pt.zcu.intern_pool).?;
             const file = pt.zcu.fileByIndex(ti.file);
@@ -1435,10 +1436,10 @@ pub const Task = union(enum) {
     load_input: Input,
 
     /// Write the constant value for a Decl to the output file.
-    codegen_nav: InternPool.Nav.Index,
+    link_nav: InternPool.Nav.Index,
     /// Write the machine code for a function to the output file.
-    codegen_func: CodegenFunc,
-    codegen_type: InternPool.Index,
+    link_func: CodegenFunc,
+    link_type: InternPool.Index,
 
     update_line_number: InternPool.TrackedInst.Index,
 
@@ -1585,47 +1586,90 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void {
                 },
             };
         },
-        .codegen_nav => |nav_index| {
-            if (comp.remaining_prelink_tasks == 0) {
-                const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
-                defer pt.deactivate();
-                pt.linkerUpdateNav(nav_index) catch |err| switch (err) {
-                    error.OutOfMemory => diags.setAllocFailure(),
-                };
-            } else {
+        .link_nav => |nav_index| {
+            if (comp.remaining_prelink_tasks != 0) {
                 comp.link_task_queue_postponed.appendAssumeCapacity(task);
+                return;
             }
-        },
-        .codegen_func => |func| {
-            if (comp.remaining_prelink_tasks == 0) {
-                const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
-                defer pt.deactivate();
-                var air = func.air;
-                defer air.deinit(comp.gpa);
-                pt.linkerUpdateFunc(func.func, &air) catch |err| switch (err) {
+            const zcu = comp.zcu.?;
+            const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid));
+            defer pt.deactivate();
+            if (!Air.valFullyResolved(zcu.navValue(nav_index), zcu)) {
+                // Type resolution failed in a way which affects this `Nav`. This is a transitive
+                // failure, but it doesn't need recording, because this `Nav` semantically depends
+                // on the failed type, so when it is changed the `Nav` will be updated.
+                return;
+            }
+            if (comp.bin_file) |lf| {
+                lf.updateNav(pt, nav_index) catch |err| switch (err) {
+                    error.OutOfMemory => diags.setAllocFailure(),
+                    error.CodegenFail => assert(zcu.failed_codegen.contains(nav_index)),
+                    error.Overflow, error.RelocationNotByteAligned => {
+                        zcu.failed_codegen.ensureUnusedCapacity(zcu.gpa, 1) catch return diags.setAllocFailure();
+                        const msg = Zcu.ErrorMsg.create(
+                            zcu.gpa,
+                            zcu.navSrcLoc(nav_index),
+                            "unable to codegen: {s}",
+                            .{@errorName(err)},
+                        ) catch return diags.setAllocFailure();
+                        zcu.failed_codegen.putAssumeCapacityNoClobber(nav_index, msg);
+                        // Not a retryable failure.
+                    },
+                };
+            } else if (zcu.llvm_object) |llvm_object| {
+                llvm_object.updateNav(pt, nav_index) catch |err| switch (err) {
                     error.OutOfMemory => diags.setAllocFailure(),
                 };
-            } else {
+            }
+        },
+        .link_func => |func| {
+            if (comp.remaining_prelink_tasks != 0) {
                 comp.link_task_queue_postponed.appendAssumeCapacity(task);
+                return;
             }
+            const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
+            defer pt.deactivate();
+            var air = func.air;
+            defer air.deinit(comp.gpa);
+            pt.linkerUpdateFunc(func.func, &air) catch |err| switch (err) {
+                error.OutOfMemory => diags.setAllocFailure(),
+            };
         },
-        .codegen_type => |ty| {
-            if (comp.remaining_prelink_tasks == 0) {
-                const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
-                defer pt.deactivate();
-                pt.linkerUpdateContainerType(ty) catch |err| switch (err) {
+        .link_type => |ty| {
+            if (comp.remaining_prelink_tasks != 0) {
+                comp.link_task_queue_postponed.appendAssumeCapacity(task);
+                return;
+            }
+            const zcu = comp.zcu.?;
+            const pt: Zcu.PerThread = .activate(zcu, @enumFromInt(tid));
+            defer pt.deactivate();
+            if (zcu.failed_types.fetchSwapRemove(ty)) |*entry| entry.value.deinit(zcu.gpa);
+            if (!Air.typeFullyResolved(.fromInterned(ty), zcu)) {
+                // Type resolution failed in a way which affects this type. This is a transitive
+                // failure, but it doesn't need recording, because this type semantically depends
+                // on the failed type, so when that is changed, this type will be updated.
+                return;
+            }
+            if (comp.bin_file) |lf| {
+                lf.updateContainerType(pt, ty) catch |err| switch (err) {
                     error.OutOfMemory => diags.setAllocFailure(),
+                    error.TypeFailureReported => assert(zcu.failed_types.contains(ty)),
                 };
-            } else {
-                comp.link_task_queue_postponed.appendAssumeCapacity(task);
             }
         },
         .update_line_number => |ti| {
+            if (comp.remaining_prelink_tasks != 0) {
+                comp.link_task_queue_postponed.appendAssumeCapacity(task);
+                return;
+            }
             const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
             defer pt.deactivate();
-            pt.linkerUpdateLineNumber(ti) catch |err| switch (err) {
-                error.OutOfMemory => diags.setAllocFailure(),
-            };
+            if (comp.bin_file) |lf| {
+                lf.updateLineNumber(pt, ti) catch |err| switch (err) {
+                    error.OutOfMemory => diags.setAllocFailure(),
+                    else => |e| log.err("update line number failed: {s}", .{@errorName(e)}),
+                };
+            }
         },
     }
 }
src/Sema.zig
@@ -2991,7 +2991,7 @@ fn zirStructDecl(
         if (zcu.comp.config.use_llvm) break :codegen_type;
         if (block.ownerModule().strip) break :codegen_type;
         // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
+        try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
     }
     try sema.declareDependency(.{ .interned = wip_ty.index });
     try sema.addTypeReferenceEntry(src, wip_ty.index);
@@ -3250,7 +3250,7 @@ fn zirEnumDecl(
         if (zcu.comp.config.use_llvm) break :codegen_type;
         if (block.ownerModule().strip) break :codegen_type;
         // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
+        try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
     }
     return Air.internedToRef(wip_ty.index);
 }
@@ -3368,7 +3368,7 @@ fn zirUnionDecl(
         if (zcu.comp.config.use_llvm) break :codegen_type;
         if (block.ownerModule().strip) break :codegen_type;
         // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
+        try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
     }
     try sema.declareDependency(.{ .interned = wip_ty.index });
     try sema.addTypeReferenceEntry(src, wip_ty.index);
@@ -3455,7 +3455,7 @@ fn zirOpaqueDecl(
         if (zcu.comp.config.use_llvm) break :codegen_type;
         if (block.ownerModule().strip) break :codegen_type;
         // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
+        try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
     }
     try sema.addTypeReferenceEntry(src, wip_ty.index);
     if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
@@ -20086,7 +20086,7 @@ fn structInitAnon(
             codegen_type: {
                 if (zcu.comp.config.use_llvm) break :codegen_type;
                 if (block.ownerModule().strip) break :codegen_type;
-                try zcu.comp.queueJob(.{ .codegen_type = wip.index });
+                try zcu.comp.queueJob(.{ .link_type = wip.index });
             }
             if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip.index);
             break :ty wip.finish(ip, new_namespace_index);
@@ -21396,7 +21396,7 @@ fn reifyEnum(
         if (zcu.comp.config.use_llvm) break :codegen_type;
         if (block.ownerModule().strip) break :codegen_type;
         // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
+        try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
     }
     return Air.internedToRef(wip_ty.index);
 }
@@ -21650,7 +21650,7 @@ fn reifyUnion(
         if (zcu.comp.config.use_llvm) break :codegen_type;
         if (block.ownerModule().strip) break :codegen_type;
         // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
+        try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
     }
     try sema.declareDependency(.{ .interned = wip_ty.index });
     try sema.addTypeReferenceEntry(src, wip_ty.index);
@@ -22004,7 +22004,7 @@ fn reifyStruct(
         if (zcu.comp.config.use_llvm) break :codegen_type;
         if (block.ownerModule().strip) break :codegen_type;
         // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
+        try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
     }
     try sema.declareDependency(.{ .interned = wip_ty.index });
     try sema.addTypeReferenceEntry(src, wip_ty.index);