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.
This commit is contained in:
mlugg 2025-05-28 00:31:16 +01:00
parent 4a02e080d1
commit 424e6ac54b
No known key found for this signature in database
GPG Key ID: 3F5B7DCCBF4AF02E
5 changed files with 112 additions and 132 deletions

View File

@ -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.
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_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 {

View File

@ -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);

View File

@ -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);
},

View File

@ -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);

View File

@ -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) {
.link_nav => |nav_index| {
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 (!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 {
comp.link_task_queue_postponed.appendAssumeCapacity(task);
}
},
.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) {
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) {
error.OutOfMemory => diags.setAllocFailure(),
};
} else {
.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)),
};
}
},
.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)}),
};
}
},
}
}