diff --git a/lib/std/Thread/WaitGroup.zig b/lib/std/Thread/WaitGroup.zig index bdc49587bf..52e9c379c2 100644 --- a/lib/std/Thread/WaitGroup.zig +++ b/lib/std/Thread/WaitGroup.zig @@ -14,6 +14,11 @@ pub fn start(self: *WaitGroup) void { assert((state / one_pending) < (std.math.maxInt(usize) / one_pending)); } +pub fn startMany(self: *WaitGroup, n: usize) void { + const state = self.state.fetchAdd(one_pending * n, .monotonic); + assert((state / one_pending) < (std.math.maxInt(usize) / one_pending)); +} + pub fn finish(self: *WaitGroup) void { const state = self.state.fetchSub(one_pending, .acq_rel); assert((state / one_pending) > 0); diff --git a/src/Compilation.zig b/src/Compilation.zig index 32d657d386..3b236f80f0 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -111,7 +111,9 @@ win32_resource_table: if (dev.env.supports(.win32_resource)) std.AutoArrayHashMa } = .{}, link_diags: link.Diags, -link_task_queue: ThreadSafeQueue(link.File.Task) = .empty, +link_task_queue: ThreadSafeQueue(link.Task) = .empty, +/// Ensure only 1 simultaneous call to `flushTaskQueue`. +link_task_queue_safety: std.debug.SafetyLock = .{}, work_queues: [ len: { @@ -123,14 +125,6 @@ work_queues: [ } ]std.fifo.LinearFifo(Job, .Dynamic), -codegen_work: if (InternPool.single_threaded) void else struct { - mutex: std.Thread.Mutex, - cond: std.Thread.Condition, - queue: std.fifo.LinearFifo(CodegenJob, .Dynamic), - job_error: ?JobError, - done: bool, -}, - /// These jobs are to invoke the Clang compiler to create an object file, which /// gets linked with the Compilation. c_object_work_queue: std.fifo.LinearFifo(*CObject, .Dynamic), @@ -267,7 +261,7 @@ emit_asm: ?EmitLoc, emit_llvm_ir: ?EmitLoc, emit_llvm_bc: ?EmitLoc, -work_queue_wait_group: WaitGroup = .{}, +link_task_wait_group: WaitGroup = .{}, work_queue_progress_node: std.Progress.Node = .none, llvm_opt_bisect_limit: c_int, @@ -347,16 +341,14 @@ pub const RcIncludes = enum { }; const Job = union(enum) { - /// Write the constant value for a Decl to the output file. + /// 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, - /// Write the machine code for a function to the output file. - codegen_func: struct { - /// This will either be a non-generic `func_decl` or a `func_instance`. - func: InternPool.Index, - /// This `Air` is owned by the `Job` and allocated with `gpa`. - /// It must be deinited when the job is processed. - air: Air, - }, + /// 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, /// The `Cau` must be semantically analyzed (and possibly export itself). /// This may be its first time being analyzed, or it may be outdated. @@ -408,17 +400,6 @@ const Job = union(enum) { } }; -const CodegenJob = union(enum) { - nav: InternPool.Nav.Index, - func: struct { - func: InternPool.Index, - /// This `Air` is owned by the `Job` and allocated with `gpa`. - /// It must be deinited when the job is processed. - air: Air, - }, - type: InternPool.Index, -}; - pub const CObject = struct { /// Relative to cwd. Owned by arena. src: CSourceFile, @@ -1465,13 +1446,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .emit_llvm_ir = options.emit_llvm_ir, .emit_llvm_bc = options.emit_llvm_bc, .work_queues = .{std.fifo.LinearFifo(Job, .Dynamic).init(gpa)} ** @typeInfo(std.meta.FieldType(Compilation, .work_queues)).array.len, - .codegen_work = if (InternPool.single_threaded) {} else .{ - .mutex = .{}, - .cond = .{}, - .queue = std.fifo.LinearFifo(CodegenJob, .Dynamic).init(gpa), - .job_error = null, - .done = false, - }, .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa), .win32_resource_work_queue = if (dev.env.supports(.win32_resource)) std.fifo.LinearFifo(*Win32Resource, .Dynamic).init(gpa) else .{}, .astgen_work_queue = std.fifo.LinearFifo(Zcu.File.Index, .Dynamic).init(gpa), @@ -1923,7 +1897,6 @@ pub fn destroy(comp: *Compilation) void { if (comp.zcu) |zcu| zcu.deinit(); comp.cache_use.deinit(); for (comp.work_queues) |work_queue| work_queue.deinit(); - if (!InternPool.single_threaded) comp.codegen_work.queue.deinit(); comp.c_object_work_queue.deinit(); comp.win32_resource_work_queue.deinit(); comp.astgen_work_queue.deinit(); @@ -3485,7 +3458,6 @@ pub fn performAllTheWork( zcu.generation += 1; }; try comp.performAllTheWorkInner(main_progress_node); - if (!InternPool.single_threaded) if (comp.codegen_work.job_error) |job_error| return job_error; } fn performAllTheWorkInner( @@ -3497,36 +3469,35 @@ fn performAllTheWorkInner( // (at least for now) single-threaded main work queue. However, C object compilation // only needs to be finished by the end of this function. - const work_queue_wait_group = &comp.work_queue_wait_group; - - work_queue_wait_group.reset(); + var work_queue_wait_group: WaitGroup = .{}; defer work_queue_wait_group.wait(); - if (comp.bin_file) |lf| { - if (comp.link_task_queue.start()) { - comp.thread_pool.spawnWg(work_queue_wait_group, link.File.flushTaskQueue, .{ lf, main_progress_node }); - } + comp.link_task_wait_group.reset(); + defer comp.link_task_wait_group.wait(); + + if (comp.link_task_queue.start()) { + comp.thread_pool.spawnWgId(&comp.link_task_wait_group, link.flushTaskQueue, .{comp}); } if (comp.docs_emit != null) { dev.check(.docs_emit); - comp.thread_pool.spawnWg(work_queue_wait_group, workerDocsCopy, .{comp}); + comp.thread_pool.spawnWg(&work_queue_wait_group, workerDocsCopy, .{comp}); work_queue_wait_group.spawnManager(workerDocsWasm, .{ comp, main_progress_node }); } if (comp.job_queued_compiler_rt_lib) { comp.job_queued_compiler_rt_lib = false; - work_queue_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Lib, &comp.compiler_rt_lib, main_progress_node }); + comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Lib, &comp.compiler_rt_lib, main_progress_node }); } if (comp.job_queued_compiler_rt_obj) { comp.job_queued_compiler_rt_obj = false; - work_queue_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Obj, &comp.compiler_rt_obj, main_progress_node }); + comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Obj, &comp.compiler_rt_obj, main_progress_node }); } if (comp.job_queued_fuzzer_lib) { comp.job_queued_fuzzer_lib = false; - work_queue_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, &comp.fuzzer_lib, main_progress_node }); + comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, &comp.fuzzer_lib, main_progress_node }); } { @@ -3591,13 +3562,13 @@ fn performAllTheWorkInner( } while (comp.c_object_work_queue.readItem()) |c_object| { - comp.thread_pool.spawnWg(work_queue_wait_group, workerUpdateCObject, .{ + comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateCObject, .{ comp, c_object, main_progress_node, }); } while (comp.win32_resource_work_queue.readItem()) |win32_resource| { - comp.thread_pool.spawnWg(work_queue_wait_group, workerUpdateWin32Resource, .{ + comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateWin32Resource, .{ comp, win32_resource, main_progress_node, }); } @@ -3617,18 +3588,12 @@ fn performAllTheWorkInner( zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0); } - if (!InternPool.single_threaded) { - comp.codegen_work.done = false; // may be `true` from a prior update - comp.thread_pool.spawnWgId(work_queue_wait_group, codegenThread, .{comp}); + if (!comp.separateCodegenThreadOk()) { + // Waits until all input files have been parsed. + comp.link_task_wait_group.wait(); + comp.link_task_wait_group.reset(); + std.log.scoped(.link).debug("finished waiting for link_task_wait_group", .{}); } - defer if (!InternPool.single_threaded) { - { - comp.codegen_work.mutex.lock(); - defer comp.codegen_work.mutex.unlock(); - comp.codegen_work.done = true; - } - comp.codegen_work.cond.signal(); - }; work: while (true) { for (&comp.work_queues) |*work_queue| if (work_queue.readItem()) |job| { @@ -3672,16 +3637,14 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre } } assert(nav.status == .resolved); - try comp.queueCodegenJob(tid, .{ .nav = nav_index }); + comp.dispatchCodegenTask(tid, .{ .codegen_nav = nav_index }); }, .codegen_func => |func| { - // This call takes ownership of `func.air`. - try comp.queueCodegenJob(tid, .{ .func = .{ - .func = func.func, - .air = func.air, - } }); + comp.dispatchCodegenTask(tid, .{ .codegen_func = func }); + }, + .codegen_type => |ty| { + comp.dispatchCodegenTask(tid, .{ .codegen_type = ty }); }, - .codegen_type => |ty| try comp.queueCodegenJob(tid, .{ .type = ty }), .analyze_func => |func| { const named_frame = tracy.namedFrame("analyze_func"); defer named_frame.end(); @@ -3894,66 +3857,20 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre } } -fn queueCodegenJob(comp: *Compilation, tid: usize, codegen_job: CodegenJob) !void { - if (InternPool.single_threaded or - !comp.zcu.?.backendSupportsFeature(.separate_thread)) - return processOneCodegenJob(tid, comp, codegen_job); - - { - comp.codegen_work.mutex.lock(); - defer comp.codegen_work.mutex.unlock(); - try comp.codegen_work.queue.writeItem(codegen_job); - } - comp.codegen_work.cond.signal(); -} - -fn codegenThread(tid: usize, comp: *Compilation) void { - comp.codegen_work.mutex.lock(); - defer comp.codegen_work.mutex.unlock(); - - while (true) { - if (comp.codegen_work.queue.readItem()) |codegen_job| { - comp.codegen_work.mutex.unlock(); - defer comp.codegen_work.mutex.lock(); - - processOneCodegenJob(tid, comp, codegen_job) catch |job_error| { - comp.codegen_work.job_error = job_error; - break; - }; - continue; - } - - if (comp.codegen_work.done) break; - - comp.codegen_work.cond.wait(&comp.codegen_work.mutex); +/// 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 { + if (comp.separateCodegenThreadOk()) { + comp.queueLinkTasks(&.{link_task}); + } else { + link.doTask(comp, tid, link_task); } } -fn processOneCodegenJob(tid: usize, comp: *Compilation, codegen_job: CodegenJob) JobError!void { - switch (codegen_job) { - .nav => |nav_index| { - const named_frame = tracy.namedFrame("codegen_nav"); - defer named_frame.end(); - - const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) }; - try pt.linkerUpdateNav(nav_index); - }, - .func => |func| { - const named_frame = tracy.namedFrame("codegen_func"); - defer named_frame.end(); - - const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) }; - // This call takes ownership of `func.air`. - try pt.linkerUpdateFunc(func.func, func.air); - }, - .type => |ty| { - const named_frame = tracy.namedFrame("codegen_type"); - defer named_frame.end(); - - const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) }; - try pt.linkerUpdateContainerType(ty); - }, - } +fn separateCodegenThreadOk(comp: *const Compilation) bool { + if (InternPool.single_threaded) return false; + const zcu = comp.zcu orelse return true; + return zcu.backendSupportsFeature(.separate_thread); } fn workerDocsCopy(comp: *Compilation) void { @@ -6465,17 +6382,11 @@ pub fn queueLinkTaskMode(comp: *Compilation, path: Path, output_mode: std.builti /// Only valid to call during `update`. Automatically handles queuing up a /// linker worker task if there is not already one. -pub fn queueLinkTasks(comp: *Compilation, tasks: []const link.File.Task) void { - const use_lld = build_options.have_llvm and comp.config.use_lld; - if (use_lld) return; - const target = comp.root_mod.resolved_target.result; - if (target.ofmt != .elf) return; +pub fn queueLinkTasks(comp: *Compilation, tasks: []const link.Task) void { if (comp.link_task_queue.enqueue(comp.gpa, tasks) catch |err| switch (err) { error.OutOfMemory => return comp.setAllocFailure(), }) { - comp.thread_pool.spawnWg(&comp.work_queue_wait_group, link.File.flushTaskQueue, .{ - comp.bin_file.?, comp.work_queue_progress_node, - }); + comp.thread_pool.spawnWgId(&comp.link_task_wait_group, link.flushTaskQueue, .{comp}); } } diff --git a/src/Sema.zig b/src/Sema.zig index 5e2760cf1f..63af9686a0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2899,6 +2899,7 @@ fn zirStructDecl( codegen_type: { 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 sema.declareDependency(.{ .interned = wip_ty.index }); @@ -3149,6 +3150,7 @@ fn zirEnumDecl( codegen_type: { 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 }); } return Air.internedToRef(wip_ty.index); @@ -3272,6 +3274,7 @@ fn zirUnionDecl( codegen_type: { 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 sema.declareDependency(.{ .interned = wip_ty.index }); @@ -3357,6 +3360,7 @@ fn zirOpaqueDecl( codegen_type: { 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 sema.addTypeReferenceEntry(src, wip_ty.index); @@ -22456,6 +22460,7 @@ fn reifyEnum( codegen_type: { 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 }); } return Air.internedToRef(wip_ty.index); @@ -22713,6 +22718,7 @@ fn reifyUnion( codegen_type: { 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 sema.declareDependency(.{ .interned = wip_ty.index }); @@ -22997,6 +23003,7 @@ fn reifyStruct( codegen_type: { 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 sema.declareDependency(.{ .interned = wip_ty.index }); diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 4ec9306792..db1ee319dc 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -845,6 +845,7 @@ fn ensureFuncBodyAnalyzedInner( return .{ .ies_outdated = ies_outdated }; } + // This job depends on any resolve_type_fully jobs queued up before it. try comp.queueJob(.{ .codegen_func = .{ .func = func_index, .air = air, @@ -1016,6 +1017,7 @@ fn createFileRootStruct( codegen_type: { 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 }); } zcu.setFileRootType(file_index, wip_ty.index); @@ -1362,6 +1364,7 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { if (file.mod.strip) break :queue_codegen; } + // This job depends on any resolve_type_fully jobs queued up before it. try zcu.comp.queueJob(.{ .codegen_nav = nav_index }); } @@ -2593,7 +2596,7 @@ pub fn populateTestFunctions( } } -pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { +pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) error{OutOfMemory}!void { const zcu = pt.zcu; const comp = zcu.comp; const ip = &zcu.intern_pool; @@ -3163,6 +3166,7 @@ pub fn navPtrType(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) Allocator. pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error!InternPool.Index { 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 }); } return result.index; diff --git a/src/glibc.zig b/src/glibc.zig index 65d3cc3b01..a7736a9827 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -1222,7 +1222,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void { assert(comp.glibc_so_files == null); comp.glibc_so_files = so_files; - var task_buffer: [libs.len]link.File.Task = undefined; + var task_buffer: [libs.len]link.Task = undefined; var task_buffer_i: usize = 0; { diff --git a/src/link.zig b/src/link.zig index fdad8a6369..6f18141404 100644 --- a/src/link.zig +++ b/src/link.zig @@ -370,9 +370,6 @@ pub const File = struct { lock: ?Cache.Lock = null, child_pid: ?std.process.Child.Id = null, - /// Ensure only 1 simultaneous call to `flushTaskQueue`. - task_queue_safety: std.debug.SafetyLock = .{}, - pub const OpenOptions = struct { symbol_count_hint: u64 = 32, program_code_size_hint: u64 = 256 * 1024, @@ -1085,6 +1082,8 @@ pub const File = struct { } pub fn loadInput(base: *File, input: Input) anyerror!void { + const use_lld = build_options.have_llvm and base.comp.config.use_lld; + if (use_lld) return; switch (base.tag) { inline .elf => |tag| { dev.check(tag.devFeature()); @@ -1360,151 +1359,182 @@ pub const File = struct { pub const Wasm = @import("link/Wasm.zig"); pub const NvPtx = @import("link/NvPtx.zig"); pub const Dwarf = @import("link/Dwarf.zig"); +}; - /// Does all the tasks in the queue. Runs in exactly one separate thread - /// from the rest of compilation. All tasks performed here are - /// single-threaded with respect to one another. - pub fn flushTaskQueue(base: *File, parent_prog_node: std.Progress.Node) void { - const comp = base.comp; - base.task_queue_safety.lock(); - defer base.task_queue_safety.unlock(); - const prog_node = parent_prog_node.start("Parse Linker Inputs", 0); - defer prog_node.end(); - while (comp.link_task_queue.check()) |tasks| { - for (tasks) |task| doTask(base, task); - } +/// Does all the tasks in the queue. Runs in exactly one separate thread +/// from the rest of compilation. All tasks performed here are +/// single-threaded with respect to one another. +pub fn flushTaskQueue(tid: usize, comp: *Compilation) void { + comp.link_task_queue_safety.lock(); + defer comp.link_task_queue_safety.unlock(); + const prog_node = comp.work_queue_progress_node.start("Parse Linker Inputs", 0); + defer prog_node.end(); + while (comp.link_task_queue.check()) |tasks| { + for (tasks) |task| doTask(comp, tid, task); } +} - pub const Task = union(enum) { - /// Loads the objects, shared objects, and archives that are already - /// known from the command line. - load_explicitly_provided, - /// Loads the shared objects and archives by resolving - /// `target_util.libcFullLinkFlags()` against the host libc - /// installation. - load_host_libc, - /// Tells the linker to load an object file by path. - load_object: Path, - /// Tells the linker to load a static library by path. - load_archive: Path, - /// Tells the linker to load a shared library, possibly one that is a - /// GNU ld script. - load_dso: Path, - /// Tells the linker to load an input which could be an object file, - /// archive, or shared library. - load_input: Input, +pub const Task = union(enum) { + /// Loads the objects, shared objects, and archives that are already + /// known from the command line. + load_explicitly_provided, + /// Loads the shared objects and archives by resolving + /// `target_util.libcFullLinkFlags()` against the host libc + /// installation. + load_host_libc, + /// Tells the linker to load an object file by path. + load_object: Path, + /// Tells the linker to load a static library by path. + load_archive: Path, + /// Tells the linker to load a shared library, possibly one that is a + /// GNU ld script. + load_dso: Path, + /// Tells the linker to load an input which could be an object file, + /// archive, or shared library. + load_input: Input, + + /// Write the constant value for a Decl to the output file. + codegen_nav: InternPool.Nav.Index, + /// Write the machine code for a function to the output file. + codegen_func: CodegenFunc, + codegen_type: InternPool.Index, + + pub const CodegenFunc = struct { + /// This will either be a non-generic `func_decl` or a `func_instance`. + func: InternPool.Index, + /// This `Air` is owned by the `Job` and allocated with `gpa`. + /// It must be deinited when the job is processed. + air: Air, }; +}; - fn doTask(base: *File, task: Task) void { - const comp = base.comp; - switch (task) { - .load_explicitly_provided => { - for (comp.link_inputs) |input| { - base.loadInput(input) catch |err| switch (err) { - error.LinkFailure => return, // error reported via link_diags - else => |e| switch (input) { - .dso => |dso| comp.link_diags.addParseError(dso.path, "failed to parse shared library: {s}", .{@errorName(e)}), - .object => |obj| comp.link_diags.addParseError(obj.path, "failed to parse object: {s}", .{@errorName(e)}), - .archive => |obj| comp.link_diags.addParseError(obj.path, "failed to parse archive: {s}", .{@errorName(e)}), - .res => |res| comp.link_diags.addParseError(res.path, "failed to parse Windows resource: {s}", .{@errorName(e)}), - .dso_exact => comp.link_diags.addError("failed to handle dso_exact: {s}", .{@errorName(e)}), - }, - }; - } - }, - .load_host_libc => { - const target = comp.root_mod.resolved_target.result; - const flags = target_util.libcFullLinkFlags(target); - const crt_dir = comp.libc_installation.?.crt_dir.?; - const sep = std.fs.path.sep_str; - const diags = &comp.link_diags; - for (flags) |flag| { - assert(mem.startsWith(u8, flag, "-l")); - const lib_name = flag["-l".len..]; - switch (comp.config.link_mode) { - .dynamic => { - const dso_path = Path.initCwd( - std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{ - crt_dir, target.libPrefix(), lib_name, target.dynamicLibSuffix(), - }) catch return diags.setAllocFailure(), - ); - base.openLoadDso(dso_path, .{ - .preferred_mode = .dynamic, - .search_strategy = .paths_first, - }) catch |err| switch (err) { - error.FileNotFound => { - // Also try static. - const archive_path = Path.initCwd( - std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{ - crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(), - }) catch return diags.setAllocFailure(), - ); - base.openLoadArchive(archive_path, .{ - .preferred_mode = .dynamic, - .search_strategy = .paths_first, - }) catch |archive_err| switch (archive_err) { - error.LinkFailure => return, // error reported via diags - else => |e| diags.addParseError(dso_path, "failed to parse archive {}: {s}", .{ archive_path, @errorName(e) }), - }; - }, - error.LinkFailure => return, // error reported via diags - else => |e| diags.addParseError(dso_path, "failed to parse shared library: {s}", .{@errorName(e)}), - }; - }, - .static => { - const path = Path.initCwd( - std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{ - crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(), - }) catch return diags.setAllocFailure(), - ); - // glibc sometimes makes even archive files GNU ld scripts. - base.openLoadArchive(path, .{ - .preferred_mode = .static, - .search_strategy = .no_fallback, - }) catch |err| switch (err) { - error.LinkFailure => return, // error reported via diags - else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}), - }; - }, - } - } - }, - .load_object => |path| { - base.openLoadObject(path) catch |err| switch (err) { - error.LinkFailure => return, // error reported via link_diags - else => |e| comp.link_diags.addParseError(path, "failed to parse object: {s}", .{@errorName(e)}), - }; - }, - .load_archive => |path| { - base.openLoadArchive(path, null) catch |err| switch (err) { - error.LinkFailure => return, // error reported via link_diags - else => |e| comp.link_diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}), - }; - }, - .load_dso => |path| { - base.openLoadDso(path, .{ - .preferred_mode = .dynamic, - .search_strategy = .paths_first, - }) catch |err| switch (err) { - error.LinkFailure => return, // error reported via link_diags - else => |e| comp.link_diags.addParseError(path, "failed to parse shared library: {s}", .{@errorName(e)}), - }; - }, - .load_input => |input| { +pub fn doTask(comp: *Compilation, tid: usize, task: Task) void { + const diags = &comp.link_diags; + switch (task) { + .load_explicitly_provided => if (comp.bin_file) |base| { + for (comp.link_inputs) |input| { base.loadInput(input) catch |err| switch (err) { - error.LinkFailure => return, // error reported via link_diags - else => |e| { - if (input.path()) |path| { - comp.link_diags.addParseError(path, "failed to parse linker input: {s}", .{@errorName(e)}); - } else { - comp.link_diags.addError("failed to {s}: {s}", .{ input.taskName(), @errorName(e) }); - } + error.LinkFailure => return, // error reported via diags + else => |e| switch (input) { + .dso => |dso| diags.addParseError(dso.path, "failed to parse shared library: {s}", .{@errorName(e)}), + .object => |obj| diags.addParseError(obj.path, "failed to parse object: {s}", .{@errorName(e)}), + .archive => |obj| diags.addParseError(obj.path, "failed to parse archive: {s}", .{@errorName(e)}), + .res => |res| diags.addParseError(res.path, "failed to parse Windows resource: {s}", .{@errorName(e)}), + .dso_exact => diags.addError("failed to handle dso_exact: {s}", .{@errorName(e)}), }, }; - }, - } + } + }, + .load_host_libc => if (comp.bin_file) |base| { + const target = comp.root_mod.resolved_target.result; + const flags = target_util.libcFullLinkFlags(target); + const crt_dir = comp.libc_installation.?.crt_dir.?; + const sep = std.fs.path.sep_str; + for (flags) |flag| { + assert(mem.startsWith(u8, flag, "-l")); + const lib_name = flag["-l".len..]; + switch (comp.config.link_mode) { + .dynamic => { + const dso_path = Path.initCwd( + std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{ + crt_dir, target.libPrefix(), lib_name, target.dynamicLibSuffix(), + }) catch return diags.setAllocFailure(), + ); + base.openLoadDso(dso_path, .{ + .preferred_mode = .dynamic, + .search_strategy = .paths_first, + }) catch |err| switch (err) { + error.FileNotFound => { + // Also try static. + const archive_path = Path.initCwd( + std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{ + crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(), + }) catch return diags.setAllocFailure(), + ); + base.openLoadArchive(archive_path, .{ + .preferred_mode = .dynamic, + .search_strategy = .paths_first, + }) catch |archive_err| switch (archive_err) { + error.LinkFailure => return, // error reported via diags + else => |e| diags.addParseError(dso_path, "failed to parse archive {}: {s}", .{ archive_path, @errorName(e) }), + }; + }, + error.LinkFailure => return, // error reported via diags + else => |e| diags.addParseError(dso_path, "failed to parse shared library: {s}", .{@errorName(e)}), + }; + }, + .static => { + const path = Path.initCwd( + std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{ + crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(), + }) catch return diags.setAllocFailure(), + ); + // glibc sometimes makes even archive files GNU ld scripts. + base.openLoadArchive(path, .{ + .preferred_mode = .static, + .search_strategy = .no_fallback, + }) catch |err| switch (err) { + error.LinkFailure => return, // error reported via diags + else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}), + }; + }, + } + } + }, + .load_object => |path| if (comp.bin_file) |base| { + base.openLoadObject(path) catch |err| switch (err) { + error.LinkFailure => return, // error reported via diags + else => |e| diags.addParseError(path, "failed to parse object: {s}", .{@errorName(e)}), + }; + }, + .load_archive => |path| if (comp.bin_file) |base| { + base.openLoadArchive(path, null) catch |err| switch (err) { + error.LinkFailure => return, // error reported via link_diags + else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}), + }; + }, + .load_dso => |path| if (comp.bin_file) |base| { + base.openLoadDso(path, .{ + .preferred_mode = .dynamic, + .search_strategy = .paths_first, + }) catch |err| switch (err) { + error.LinkFailure => return, // error reported via link_diags + else => |e| diags.addParseError(path, "failed to parse shared library: {s}", .{@errorName(e)}), + }; + }, + .load_input => |input| if (comp.bin_file) |base| { + base.loadInput(input) catch |err| switch (err) { + error.LinkFailure => return, // error reported via link_diags + else => |e| { + if (input.path()) |path| { + diags.addParseError(path, "failed to parse linker input: {s}", .{@errorName(e)}); + } else { + diags.addError("failed to {s}: {s}", .{ input.taskName(), @errorName(e) }); + } + }, + }; + }, + .codegen_nav => |nav_index| { + const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) }; + pt.linkerUpdateNav(nav_index) catch |err| switch (err) { + error.OutOfMemory => diags.setAllocFailure(), + }; + }, + .codegen_func => |func| { + const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) }; + // This call takes ownership of `func.air`. + pt.linkerUpdateFunc(func.func, func.air) catch |err| switch (err) { + error.OutOfMemory => diags.setAllocFailure(), + }; + }, + .codegen_type => |ty| { + const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) }; + pt.linkerUpdateContainerType(ty) catch |err| switch (err) { + error.OutOfMemory => diags.setAllocFailure(), + }; + }, } -}; +} pub fn spawnLld( comp: *Compilation,