authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-10-23 00:00:17-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-10-23 16:27:39-07:00
logba71079837a071b53cab289d78e5bacb4925fd25
tree1b1096800e87c4128e09098b467b05f06cdd1cb3
parent9a511b4b273ad4b7ad9289e18de87421ee47b626

combine codegen work queue and linker task queue

these tasks have some shared data dependencies so they cannot be done simultaneously. Future work should untangle these data dependencies so that more can be done in parallel. for now this commit ensures correctness by making linker input parsing and codegen tasks part of the same queue.

6 files changed, 233 insertions(+), 276 deletions(-)

lib/std/Thread/WaitGroup.zig+5
......@@ -14,6 +14,11 @@ pub fn start(self: *WaitGroup) void {
1414 assert((state / one_pending) < (std.math.maxInt(usize) / one_pending));
1515}
1616
17pub fn startMany(self: *WaitGroup, n: usize) void {
18 const state = self.state.fetchAdd(one_pending * n, .monotonic);
19 assert((state / one_pending) < (std.math.maxInt(usize) / one_pending));
20}
21
1722pub fn finish(self: *WaitGroup) void {
1823 const state = self.state.fetchSub(one_pending, .acq_rel);
1924 assert((state / one_pending) > 0);
src/Compilation.zig+46-135
......@@ -111,7 +111,9 @@ win32_resource_table: if (dev.env.supports(.win32_resource)) std.AutoArrayHashMa
111111} = .{},
112112
113113link_diags: link.Diags,
114link_task_queue: ThreadSafeQueue(link.File.Task) = .empty,
114link_task_queue: ThreadSafeQueue(link.Task) = .empty,
115/// Ensure only 1 simultaneous call to `flushTaskQueue`.
116link_task_queue_safety: std.debug.SafetyLock = .{},
115117
116118work_queues: [
117119 len: {
......@@ -123,14 +125,6 @@ work_queues: [
123125 }
124126]std.fifo.LinearFifo(Job, .Dynamic),
125127
126codegen_work: if (InternPool.single_threaded) void else struct {
127 mutex: std.Thread.Mutex,
128 cond: std.Thread.Condition,
129 queue: std.fifo.LinearFifo(CodegenJob, .Dynamic),
130 job_error: ?JobError,
131 done: bool,
132},
133
134128/// These jobs are to invoke the Clang compiler to create an object file, which
135129/// gets linked with the Compilation.
136130c_object_work_queue: std.fifo.LinearFifo(*CObject, .Dynamic),
......@@ -267,7 +261,7 @@ emit_asm: ?EmitLoc,
267261emit_llvm_ir: ?EmitLoc,
268262emit_llvm_bc: ?EmitLoc,
269263
270work_queue_wait_group: WaitGroup = .{},
264link_task_wait_group: WaitGroup = .{},
271265work_queue_progress_node: std.Progress.Node = .none,
272266
273267llvm_opt_bisect_limit: c_int,
......@@ -347,16 +341,14 @@ pub const RcIncludes = enum {
347341};
348342
349343const Job = union(enum) {
350 /// Write the constant value for a Decl to the output file.
344 /// Corresponds to the task in `link.Task`.
345 /// Only needed for backends that haven't yet been updated to not race against Sema.
351346 codegen_nav: InternPool.Nav.Index,
352 /// Write the machine code for a function to the output file.
353 codegen_func: struct {
354 /// This will either be a non-generic `func_decl` or a `func_instance`.
355 func: InternPool.Index,
356 /// This `Air` is owned by the `Job` and allocated with `gpa`.
357 /// It must be deinited when the job is processed.
358 air: Air,
359 },
347 /// Corresponds to the task in `link.Task`.
348 /// Only needed for backends that haven't yet been updated to not race against Sema.
349 codegen_func: link.Task.CodegenFunc,
350 /// Corresponds to the task in `link.Task`.
351 /// Only needed for backends that haven't yet been updated to not race against Sema.
360352 codegen_type: InternPool.Index,
361353 /// The `Cau` must be semantically analyzed (and possibly export itself).
362354 /// This may be its first time being analyzed, or it may be outdated.
......@@ -408,17 +400,6 @@ const Job = union(enum) {
408400 }
409401};
410402
411const CodegenJob = union(enum) {
412 nav: InternPool.Nav.Index,
413 func: struct {
414 func: InternPool.Index,
415 /// This `Air` is owned by the `Job` and allocated with `gpa`.
416 /// It must be deinited when the job is processed.
417 air: Air,
418 },
419 type: InternPool.Index,
420};
421
422403pub const CObject = struct {
423404 /// Relative to cwd. Owned by arena.
424405 src: CSourceFile,
......@@ -1465,13 +1446,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
14651446 .emit_llvm_ir = options.emit_llvm_ir,
14661447 .emit_llvm_bc = options.emit_llvm_bc,
14671448 .work_queues = .{std.fifo.LinearFifo(Job, .Dynamic).init(gpa)} ** @typeInfo(std.meta.FieldType(Compilation, .work_queues)).array.len,
1468 .codegen_work = if (InternPool.single_threaded) {} else .{
1469 .mutex = .{},
1470 .cond = .{},
1471 .queue = std.fifo.LinearFifo(CodegenJob, .Dynamic).init(gpa),
1472 .job_error = null,
1473 .done = false,
1474 },
14751449 .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa),
14761450 .win32_resource_work_queue = if (dev.env.supports(.win32_resource)) std.fifo.LinearFifo(*Win32Resource, .Dynamic).init(gpa) else .{},
14771451 .astgen_work_queue = std.fifo.LinearFifo(Zcu.File.Index, .Dynamic).init(gpa),
......@@ -1923,7 +1897,6 @@ pub fn destroy(comp: *Compilation) void {
19231897 if (comp.zcu) |zcu| zcu.deinit();
19241898 comp.cache_use.deinit();
19251899 for (comp.work_queues) |work_queue| work_queue.deinit();
1926 if (!InternPool.single_threaded) comp.codegen_work.queue.deinit();
19271900 comp.c_object_work_queue.deinit();
19281901 comp.win32_resource_work_queue.deinit();
19291902 comp.astgen_work_queue.deinit();
......@@ -3485,7 +3458,6 @@ pub fn performAllTheWork(
34853458 zcu.generation += 1;
34863459 };
34873460 try comp.performAllTheWorkInner(main_progress_node);
3488 if (!InternPool.single_threaded) if (comp.codegen_work.job_error) |job_error| return job_error;
34893461}
34903462
34913463fn performAllTheWorkInner(
......@@ -3497,36 +3469,35 @@ fn performAllTheWorkInner(
34973469 // (at least for now) single-threaded main work queue. However, C object compilation
34983470 // only needs to be finished by the end of this function.
34993471
3500 const work_queue_wait_group = &comp.work_queue_wait_group;
3501
3502 work_queue_wait_group.reset();
3472 var work_queue_wait_group: WaitGroup = .{};
35033473 defer work_queue_wait_group.wait();
35043474
3505 if (comp.bin_file) |lf| {
3506 if (comp.link_task_queue.start()) {
3507 comp.thread_pool.spawnWg(work_queue_wait_group, link.File.flushTaskQueue, .{ lf, main_progress_node });
3508 }
3475 comp.link_task_wait_group.reset();
3476 defer comp.link_task_wait_group.wait();
3477
3478 if (comp.link_task_queue.start()) {
3479 comp.thread_pool.spawnWgId(&comp.link_task_wait_group, link.flushTaskQueue, .{comp});
35093480 }
35103481
35113482 if (comp.docs_emit != null) {
35123483 dev.check(.docs_emit);
3513 comp.thread_pool.spawnWg(work_queue_wait_group, workerDocsCopy, .{comp});
3484 comp.thread_pool.spawnWg(&work_queue_wait_group, workerDocsCopy, .{comp});
35143485 work_queue_wait_group.spawnManager(workerDocsWasm, .{ comp, main_progress_node });
35153486 }
35163487
35173488 if (comp.job_queued_compiler_rt_lib) {
35183489 comp.job_queued_compiler_rt_lib = false;
3519 work_queue_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Lib, &comp.compiler_rt_lib, main_progress_node });
3490 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Lib, &comp.compiler_rt_lib, main_progress_node });
35203491 }
35213492
35223493 if (comp.job_queued_compiler_rt_obj) {
35233494 comp.job_queued_compiler_rt_obj = false;
3524 work_queue_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Obj, &comp.compiler_rt_obj, main_progress_node });
3495 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Obj, &comp.compiler_rt_obj, main_progress_node });
35253496 }
35263497
35273498 if (comp.job_queued_fuzzer_lib) {
35283499 comp.job_queued_fuzzer_lib = false;
3529 work_queue_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, &comp.fuzzer_lib, main_progress_node });
3500 comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, &comp.fuzzer_lib, main_progress_node });
35303501 }
35313502
35323503 {
......@@ -3591,13 +3562,13 @@ fn performAllTheWorkInner(
35913562 }
35923563
35933564 while (comp.c_object_work_queue.readItem()) |c_object| {
3594 comp.thread_pool.spawnWg(work_queue_wait_group, workerUpdateCObject, .{
3565 comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateCObject, .{
35953566 comp, c_object, main_progress_node,
35963567 });
35973568 }
35983569
35993570 while (comp.win32_resource_work_queue.readItem()) |win32_resource| {
3600 comp.thread_pool.spawnWg(work_queue_wait_group, workerUpdateWin32Resource, .{
3571 comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateWin32Resource, .{
36013572 comp, win32_resource, main_progress_node,
36023573 });
36033574 }
......@@ -3617,18 +3588,12 @@ fn performAllTheWorkInner(
36173588 zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0);
36183589 }
36193590
3620 if (!InternPool.single_threaded) {
3621 comp.codegen_work.done = false; // may be `true` from a prior update
3622 comp.thread_pool.spawnWgId(work_queue_wait_group, codegenThread, .{comp});
3591 if (!comp.separateCodegenThreadOk()) {
3592 // Waits until all input files have been parsed.
3593 comp.link_task_wait_group.wait();
3594 comp.link_task_wait_group.reset();
3595 std.log.scoped(.link).debug("finished waiting for link_task_wait_group", .{});
36233596 }
3624 defer if (!InternPool.single_threaded) {
3625 {
3626 comp.codegen_work.mutex.lock();
3627 defer comp.codegen_work.mutex.unlock();
3628 comp.codegen_work.done = true;
3629 }
3630 comp.codegen_work.cond.signal();
3631 };
36323597
36333598 work: while (true) {
36343599 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
36723637 }
36733638 }
36743639 assert(nav.status == .resolved);
3675 try comp.queueCodegenJob(tid, .{ .nav = nav_index });
3640 comp.dispatchCodegenTask(tid, .{ .codegen_nav = nav_index });
36763641 },
36773642 .codegen_func => |func| {
3678 // This call takes ownership of `func.air`.
3679 try comp.queueCodegenJob(tid, .{ .func = .{
3680 .func = func.func,
3681 .air = func.air,
3682 } });
3643 comp.dispatchCodegenTask(tid, .{ .codegen_func = func });
3644 },
3645 .codegen_type => |ty| {
3646 comp.dispatchCodegenTask(tid, .{ .codegen_type = ty });
36833647 },
3684 .codegen_type => |ty| try comp.queueCodegenJob(tid, .{ .type = ty }),
36853648 .analyze_func => |func| {
36863649 const named_frame = tracy.namedFrame("analyze_func");
36873650 defer named_frame.end();
......@@ -3894,66 +3857,20 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre
38943857 }
38953858}
38963859
3897fn queueCodegenJob(comp: *Compilation, tid: usize, codegen_job: CodegenJob) !void {
3898 if (InternPool.single_threaded or
3899 !comp.zcu.?.backendSupportsFeature(.separate_thread))
3900 return processOneCodegenJob(tid, comp, codegen_job);
3901
3902 {
3903 comp.codegen_work.mutex.lock();
3904 defer comp.codegen_work.mutex.unlock();
3905 try comp.codegen_work.queue.writeItem(codegen_job);
3906 }
3907 comp.codegen_work.cond.signal();
3908}
3909
3910fn codegenThread(tid: usize, comp: *Compilation) void {
3911 comp.codegen_work.mutex.lock();
3912 defer comp.codegen_work.mutex.unlock();
3913
3914 while (true) {
3915 if (comp.codegen_work.queue.readItem()) |codegen_job| {
3916 comp.codegen_work.mutex.unlock();
3917 defer comp.codegen_work.mutex.lock();
3918
3919 processOneCodegenJob(tid, comp, codegen_job) catch |job_error| {
3920 comp.codegen_work.job_error = job_error;
3921 break;
3922 };
3923 continue;
3924 }
3925
3926 if (comp.codegen_work.done) break;
3927
3928 comp.codegen_work.cond.wait(&comp.codegen_work.mutex);
3860/// The reason for the double-queue here is that the first queue ensures any
3861/// resolve_type_fully tasks are complete before this dispatch function is called.
3862fn dispatchCodegenTask(comp: *Compilation, tid: usize, link_task: link.Task) void {
3863 if (comp.separateCodegenThreadOk()) {
3864 comp.queueLinkTasks(&.{link_task});
3865 } else {
3866 link.doTask(comp, tid, link_task);
39293867 }
39303868}
39313869
3932fn processOneCodegenJob(tid: usize, comp: *Compilation, codegen_job: CodegenJob) JobError!void {
3933 switch (codegen_job) {
3934 .nav => |nav_index| {
3935 const named_frame = tracy.namedFrame("codegen_nav");
3936 defer named_frame.end();
3937
3938 const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) };
3939 try pt.linkerUpdateNav(nav_index);
3940 },
3941 .func => |func| {
3942 const named_frame = tracy.namedFrame("codegen_func");
3943 defer named_frame.end();
3944
3945 const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) };
3946 // This call takes ownership of `func.air`.
3947 try pt.linkerUpdateFunc(func.func, func.air);
3948 },
3949 .type => |ty| {
3950 const named_frame = tracy.namedFrame("codegen_type");
3951 defer named_frame.end();
3952
3953 const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) };
3954 try pt.linkerUpdateContainerType(ty);
3955 },
3956 }
3870fn separateCodegenThreadOk(comp: *const Compilation) bool {
3871 if (InternPool.single_threaded) return false;
3872 const zcu = comp.zcu orelse return true;
3873 return zcu.backendSupportsFeature(.separate_thread);
39573874}
39583875
39593876fn workerDocsCopy(comp: *Compilation) void {
......@@ -6465,17 +6382,11 @@ pub fn queueLinkTaskMode(comp: *Compilation, path: Path, output_mode: std.builti
64656382
64666383/// Only valid to call during `update`. Automatically handles queuing up a
64676384/// linker worker task if there is not already one.
6468pub fn queueLinkTasks(comp: *Compilation, tasks: []const link.File.Task) void {
6469 const use_lld = build_options.have_llvm and comp.config.use_lld;
6470 if (use_lld) return;
6471 const target = comp.root_mod.resolved_target.result;
6472 if (target.ofmt != .elf) return;
6385pub fn queueLinkTasks(comp: *Compilation, tasks: []const link.Task) void {
64736386 if (comp.link_task_queue.enqueue(comp.gpa, tasks) catch |err| switch (err) {
64746387 error.OutOfMemory => return comp.setAllocFailure(),
64756388 }) {
6476 comp.thread_pool.spawnWg(&comp.work_queue_wait_group, link.File.flushTaskQueue, .{
6477 comp.bin_file.?, comp.work_queue_progress_node,
6478 });
6389 comp.thread_pool.spawnWgId(&comp.link_task_wait_group, link.flushTaskQueue, .{comp});
64796390 }
64806391}
64816392
src/Sema.zig+7
......@@ -2899,6 +2899,7 @@ fn zirStructDecl(
28992899 codegen_type: {
29002900 if (zcu.comp.config.use_llvm) break :codegen_type;
29012901 if (block.ownerModule().strip) break :codegen_type;
2902 // This job depends on any resolve_type_fully jobs queued up before it.
29022903 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
29032904 }
29042905 try sema.declareDependency(.{ .interned = wip_ty.index });
......@@ -3149,6 +3150,7 @@ fn zirEnumDecl(
31493150 codegen_type: {
31503151 if (zcu.comp.config.use_llvm) break :codegen_type;
31513152 if (block.ownerModule().strip) break :codegen_type;
3153 // This job depends on any resolve_type_fully jobs queued up before it.
31523154 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
31533155 }
31543156 return Air.internedToRef(wip_ty.index);
......@@ -3272,6 +3274,7 @@ fn zirUnionDecl(
32723274 codegen_type: {
32733275 if (zcu.comp.config.use_llvm) break :codegen_type;
32743276 if (block.ownerModule().strip) break :codegen_type;
3277 // This job depends on any resolve_type_fully jobs queued up before it.
32753278 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
32763279 }
32773280 try sema.declareDependency(.{ .interned = wip_ty.index });
......@@ -3357,6 +3360,7 @@ fn zirOpaqueDecl(
33573360 codegen_type: {
33583361 if (zcu.comp.config.use_llvm) break :codegen_type;
33593362 if (block.ownerModule().strip) break :codegen_type;
3363 // This job depends on any resolve_type_fully jobs queued up before it.
33603364 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
33613365 }
33623366 try sema.addTypeReferenceEntry(src, wip_ty.index);
......@@ -22456,6 +22460,7 @@ fn reifyEnum(
2245622460 codegen_type: {
2245722461 if (zcu.comp.config.use_llvm) break :codegen_type;
2245822462 if (block.ownerModule().strip) break :codegen_type;
22463 // This job depends on any resolve_type_fully jobs queued up before it.
2245922464 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
2246022465 }
2246122466 return Air.internedToRef(wip_ty.index);
......@@ -22713,6 +22718,7 @@ fn reifyUnion(
2271322718 codegen_type: {
2271422719 if (zcu.comp.config.use_llvm) break :codegen_type;
2271522720 if (block.ownerModule().strip) break :codegen_type;
22721 // This job depends on any resolve_type_fully jobs queued up before it.
2271622722 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
2271722723 }
2271822724 try sema.declareDependency(.{ .interned = wip_ty.index });
......@@ -22997,6 +23003,7 @@ fn reifyStruct(
2299723003 codegen_type: {
2299823004 if (zcu.comp.config.use_llvm) break :codegen_type;
2299923005 if (block.ownerModule().strip) break :codegen_type;
23006 // This job depends on any resolve_type_fully jobs queued up before it.
2300023007 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
2300123008 }
2300223009 try sema.declareDependency(.{ .interned = wip_ty.index });
src/Zcu/PerThread.zig+5-1
......@@ -845,6 +845,7 @@ fn ensureFuncBodyAnalyzedInner(
845845 return .{ .ies_outdated = ies_outdated };
846846 }
847847
848 // This job depends on any resolve_type_fully jobs queued up before it.
848849 try comp.queueJob(.{ .codegen_func = .{
849850 .func = func_index,
850851 .air = air,
......@@ -1016,6 +1017,7 @@ fn createFileRootStruct(
10161017 codegen_type: {
10171018 if (zcu.comp.config.use_llvm) break :codegen_type;
10181019 if (file.mod.strip) break :codegen_type;
1020 // This job depends on any resolve_type_fully jobs queued up before it.
10191021 try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
10201022 }
10211023 zcu.setFileRootType(file_index, wip_ty.index);
......@@ -1362,6 +1364,7 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
13621364 if (file.mod.strip) break :queue_codegen;
13631365 }
13641366
1367 // This job depends on any resolve_type_fully jobs queued up before it.
13651368 try zcu.comp.queueJob(.{ .codegen_nav = nav_index });
13661369 }
13671370
......@@ -2593,7 +2596,7 @@ pub fn populateTestFunctions(
25932596 }
25942597}
25952598
2596pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
2599pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) error{OutOfMemory}!void {
25972600 const zcu = pt.zcu;
25982601 const comp = zcu.comp;
25992602 const ip = &zcu.intern_pool;
......@@ -3163,6 +3166,7 @@ pub fn navPtrType(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) Allocator.
31633166pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error!InternPool.Index {
31643167 const result = try pt.zcu.intern_pool.getExtern(pt.zcu.gpa, pt.tid, key);
31653168 if (result.new_nav.unwrap()) |nav| {
3169 // This job depends on any resolve_type_fully jobs queued up before it.
31663170 try pt.zcu.comp.queueJob(.{ .codegen_nav = nav });
31673171 }
31683172 return result.index;
src/glibc.zig+1-1
......@@ -1222,7 +1222,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void {
12221222 assert(comp.glibc_so_files == null);
12231223 comp.glibc_so_files = so_files;
12241224
1225 var task_buffer: [libs.len]link.File.Task = undefined;
1225 var task_buffer: [libs.len]link.Task = undefined;
12261226 var task_buffer_i: usize = 0;
12271227
12281228 {
src/link.zig+169-139
......@@ -370,9 +370,6 @@ pub const File = struct {
370370 lock: ?Cache.Lock = null,
371371 child_pid: ?std.process.Child.Id = null,
372372
373 /// Ensure only 1 simultaneous call to `flushTaskQueue`.
374 task_queue_safety: std.debug.SafetyLock = .{},
375
376373 pub const OpenOptions = struct {
377374 symbol_count_hint: u64 = 32,
378375 program_code_size_hint: u64 = 256 * 1024,
......@@ -1085,6 +1082,8 @@ pub const File = struct {
10851082 }
10861083
10871084 pub fn loadInput(base: *File, input: Input) anyerror!void {
1085 const use_lld = build_options.have_llvm and base.comp.config.use_lld;
1086 if (use_lld) return;
10881087 switch (base.tag) {
10891088 inline .elf => |tag| {
10901089 dev.check(tag.devFeature());
......@@ -1360,151 +1359,182 @@ pub const File = struct {
13601359 pub const Wasm = @import("link/Wasm.zig");
13611360 pub const NvPtx = @import("link/NvPtx.zig");
13621361 pub const Dwarf = @import("link/Dwarf.zig");
1362};
13631363
1364 /// Does all the tasks in the queue. Runs in exactly one separate thread
1365 /// from the rest of compilation. All tasks performed here are
1366 /// single-threaded with respect to one another.
1367 pub fn flushTaskQueue(base: *File, parent_prog_node: std.Progress.Node) void {
1368 const comp = base.comp;
1369 base.task_queue_safety.lock();
1370 defer base.task_queue_safety.unlock();
1371 const prog_node = parent_prog_node.start("Parse Linker Inputs", 0);
1372 defer prog_node.end();
1373 while (comp.link_task_queue.check()) |tasks| {
1374 for (tasks) |task| doTask(base, task);
1375 }
1364/// Does all the tasks in the queue. Runs in exactly one separate thread
1365/// from the rest of compilation. All tasks performed here are
1366/// single-threaded with respect to one another.
1367pub fn flushTaskQueue(tid: usize, comp: *Compilation) void {
1368 comp.link_task_queue_safety.lock();
1369 defer comp.link_task_queue_safety.unlock();
1370 const prog_node = comp.work_queue_progress_node.start("Parse Linker Inputs", 0);
1371 defer prog_node.end();
1372 while (comp.link_task_queue.check()) |tasks| {
1373 for (tasks) |task| doTask(comp, tid, task);
13761374 }
1375}
13771376
1378 pub const Task = union(enum) {
1379 /// Loads the objects, shared objects, and archives that are already
1380 /// known from the command line.
1381 load_explicitly_provided,
1382 /// Loads the shared objects and archives by resolving
1383 /// `target_util.libcFullLinkFlags()` against the host libc
1384 /// installation.
1385 load_host_libc,
1386 /// Tells the linker to load an object file by path.
1387 load_object: Path,
1388 /// Tells the linker to load a static library by path.
1389 load_archive: Path,
1390 /// Tells the linker to load a shared library, possibly one that is a
1391 /// GNU ld script.
1392 load_dso: Path,
1393 /// Tells the linker to load an input which could be an object file,
1394 /// archive, or shared library.
1395 load_input: Input,
1377pub const Task = union(enum) {
1378 /// Loads the objects, shared objects, and archives that are already
1379 /// known from the command line.
1380 load_explicitly_provided,
1381 /// Loads the shared objects and archives by resolving
1382 /// `target_util.libcFullLinkFlags()` against the host libc
1383 /// installation.
1384 load_host_libc,
1385 /// Tells the linker to load an object file by path.
1386 load_object: Path,
1387 /// Tells the linker to load a static library by path.
1388 load_archive: Path,
1389 /// Tells the linker to load a shared library, possibly one that is a
1390 /// GNU ld script.
1391 load_dso: Path,
1392 /// Tells the linker to load an input which could be an object file,
1393 /// archive, or shared library.
1394 load_input: Input,
1395
1396 /// Write the constant value for a Decl to the output file.
1397 codegen_nav: InternPool.Nav.Index,
1398 /// Write the machine code for a function to the output file.
1399 codegen_func: CodegenFunc,
1400 codegen_type: InternPool.Index,
1401
1402 pub const CodegenFunc = struct {
1403 /// This will either be a non-generic `func_decl` or a `func_instance`.
1404 func: InternPool.Index,
1405 /// This `Air` is owned by the `Job` and allocated with `gpa`.
1406 /// It must be deinited when the job is processed.
1407 air: Air,
13961408 };
1409};
13971410
1398 fn doTask(base: *File, task: Task) void {
1399 const comp = base.comp;
1400 switch (task) {
1401 .load_explicitly_provided => {
1402 for (comp.link_inputs) |input| {
1403 base.loadInput(input) catch |err| switch (err) {
1404 error.LinkFailure => return, // error reported via link_diags
1405 else => |e| switch (input) {
1406 .dso => |dso| comp.link_diags.addParseError(dso.path, "failed to parse shared library: {s}", .{@errorName(e)}),
1407 .object => |obj| comp.link_diags.addParseError(obj.path, "failed to parse object: {s}", .{@errorName(e)}),
1408 .archive => |obj| comp.link_diags.addParseError(obj.path, "failed to parse archive: {s}", .{@errorName(e)}),
1409 .res => |res| comp.link_diags.addParseError(res.path, "failed to parse Windows resource: {s}", .{@errorName(e)}),
1410 .dso_exact => comp.link_diags.addError("failed to handle dso_exact: {s}", .{@errorName(e)}),
1411 },
1412 };
1413 }
1414 },
1415 .load_host_libc => {
1416 const target = comp.root_mod.resolved_target.result;
1417 const flags = target_util.libcFullLinkFlags(target);
1418 const crt_dir = comp.libc_installation.?.crt_dir.?;
1419 const sep = std.fs.path.sep_str;
1420 const diags = &comp.link_diags;
1421 for (flags) |flag| {
1422 assert(mem.startsWith(u8, flag, "-l"));
1423 const lib_name = flag["-l".len..];
1424 switch (comp.config.link_mode) {
1425 .dynamic => {
1426 const dso_path = Path.initCwd(
1427 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
1428 crt_dir, target.libPrefix(), lib_name, target.dynamicLibSuffix(),
1429 }) catch return diags.setAllocFailure(),
1430 );
1431 base.openLoadDso(dso_path, .{
1432 .preferred_mode = .dynamic,
1433 .search_strategy = .paths_first,
1434 }) catch |err| switch (err) {
1435 error.FileNotFound => {
1436 // Also try static.
1437 const archive_path = Path.initCwd(
1438 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
1439 crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(),
1440 }) catch return diags.setAllocFailure(),
1441 );
1442 base.openLoadArchive(archive_path, .{
1443 .preferred_mode = .dynamic,
1444 .search_strategy = .paths_first,
1445 }) catch |archive_err| switch (archive_err) {
1446 error.LinkFailure => return, // error reported via diags
1447 else => |e| diags.addParseError(dso_path, "failed to parse archive {}: {s}", .{ archive_path, @errorName(e) }),
1448 };
1449 },
1450 error.LinkFailure => return, // error reported via diags
1451 else => |e| diags.addParseError(dso_path, "failed to parse shared library: {s}", .{@errorName(e)}),
1452 };
1453 },
1454 .static => {
1455 const path = Path.initCwd(
1456 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
1457 crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(),
1458 }) catch return diags.setAllocFailure(),
1459 );
1460 // glibc sometimes makes even archive files GNU ld scripts.
1461 base.openLoadArchive(path, .{
1462 .preferred_mode = .static,
1463 .search_strategy = .no_fallback,
1464 }) catch |err| switch (err) {
1465 error.LinkFailure => return, // error reported via diags
1466 else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
1467 };
1468 },
1469 }
1470 }
1471 },
1472 .load_object => |path| {
1473 base.openLoadObject(path) catch |err| switch (err) {
1474 error.LinkFailure => return, // error reported via link_diags
1475 else => |e| comp.link_diags.addParseError(path, "failed to parse object: {s}", .{@errorName(e)}),
1476 };
1477 },
1478 .load_archive => |path| {
1479 base.openLoadArchive(path, null) catch |err| switch (err) {
1480 error.LinkFailure => return, // error reported via link_diags
1481 else => |e| comp.link_diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
1482 };
1483 },
1484 .load_dso => |path| {
1485 base.openLoadDso(path, .{
1486 .preferred_mode = .dynamic,
1487 .search_strategy = .paths_first,
1488 }) catch |err| switch (err) {
1489 error.LinkFailure => return, // error reported via link_diags
1490 else => |e| comp.link_diags.addParseError(path, "failed to parse shared library: {s}", .{@errorName(e)}),
1491 };
1492 },
1493 .load_input => |input| {
1411pub fn doTask(comp: *Compilation, tid: usize, task: Task) void {
1412 const diags = &comp.link_diags;
1413 switch (task) {
1414 .load_explicitly_provided => if (comp.bin_file) |base| {
1415 for (comp.link_inputs) |input| {
14941416 base.loadInput(input) catch |err| switch (err) {
1495 error.LinkFailure => return, // error reported via link_diags
1496 else => |e| {
1497 if (input.path()) |path| {
1498 comp.link_diags.addParseError(path, "failed to parse linker input: {s}", .{@errorName(e)});
1499 } else {
1500 comp.link_diags.addError("failed to {s}: {s}", .{ input.taskName(), @errorName(e) });
1501 }
1417 error.LinkFailure => return, // error reported via diags
1418 else => |e| switch (input) {
1419 .dso => |dso| diags.addParseError(dso.path, "failed to parse shared library: {s}", .{@errorName(e)}),
1420 .object => |obj| diags.addParseError(obj.path, "failed to parse object: {s}", .{@errorName(e)}),
1421 .archive => |obj| diags.addParseError(obj.path, "failed to parse archive: {s}", .{@errorName(e)}),
1422 .res => |res| diags.addParseError(res.path, "failed to parse Windows resource: {s}", .{@errorName(e)}),
1423 .dso_exact => diags.addError("failed to handle dso_exact: {s}", .{@errorName(e)}),
15021424 },
15031425 };
1504 },
1505 }
1426 }
1427 },
1428 .load_host_libc => if (comp.bin_file) |base| {
1429 const target = comp.root_mod.resolved_target.result;
1430 const flags = target_util.libcFullLinkFlags(target);
1431 const crt_dir = comp.libc_installation.?.crt_dir.?;
1432 const sep = std.fs.path.sep_str;
1433 for (flags) |flag| {
1434 assert(mem.startsWith(u8, flag, "-l"));
1435 const lib_name = flag["-l".len..];
1436 switch (comp.config.link_mode) {
1437 .dynamic => {
1438 const dso_path = Path.initCwd(
1439 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
1440 crt_dir, target.libPrefix(), lib_name, target.dynamicLibSuffix(),
1441 }) catch return diags.setAllocFailure(),
1442 );
1443 base.openLoadDso(dso_path, .{
1444 .preferred_mode = .dynamic,
1445 .search_strategy = .paths_first,
1446 }) catch |err| switch (err) {
1447 error.FileNotFound => {
1448 // Also try static.
1449 const archive_path = Path.initCwd(
1450 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
1451 crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(),
1452 }) catch return diags.setAllocFailure(),
1453 );
1454 base.openLoadArchive(archive_path, .{
1455 .preferred_mode = .dynamic,
1456 .search_strategy = .paths_first,
1457 }) catch |archive_err| switch (archive_err) {
1458 error.LinkFailure => return, // error reported via diags
1459 else => |e| diags.addParseError(dso_path, "failed to parse archive {}: {s}", .{ archive_path, @errorName(e) }),
1460 };
1461 },
1462 error.LinkFailure => return, // error reported via diags
1463 else => |e| diags.addParseError(dso_path, "failed to parse shared library: {s}", .{@errorName(e)}),
1464 };
1465 },
1466 .static => {
1467 const path = Path.initCwd(
1468 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
1469 crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(),
1470 }) catch return diags.setAllocFailure(),
1471 );
1472 // glibc sometimes makes even archive files GNU ld scripts.
1473 base.openLoadArchive(path, .{
1474 .preferred_mode = .static,
1475 .search_strategy = .no_fallback,
1476 }) catch |err| switch (err) {
1477 error.LinkFailure => return, // error reported via diags
1478 else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
1479 };
1480 },
1481 }
1482 }
1483 },
1484 .load_object => |path| if (comp.bin_file) |base| {
1485 base.openLoadObject(path) catch |err| switch (err) {
1486 error.LinkFailure => return, // error reported via diags
1487 else => |e| diags.addParseError(path, "failed to parse object: {s}", .{@errorName(e)}),
1488 };
1489 },
1490 .load_archive => |path| if (comp.bin_file) |base| {
1491 base.openLoadArchive(path, null) catch |err| switch (err) {
1492 error.LinkFailure => return, // error reported via link_diags
1493 else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
1494 };
1495 },
1496 .load_dso => |path| if (comp.bin_file) |base| {
1497 base.openLoadDso(path, .{
1498 .preferred_mode = .dynamic,
1499 .search_strategy = .paths_first,
1500 }) catch |err| switch (err) {
1501 error.LinkFailure => return, // error reported via link_diags
1502 else => |e| diags.addParseError(path, "failed to parse shared library: {s}", .{@errorName(e)}),
1503 };
1504 },
1505 .load_input => |input| if (comp.bin_file) |base| {
1506 base.loadInput(input) catch |err| switch (err) {
1507 error.LinkFailure => return, // error reported via link_diags
1508 else => |e| {
1509 if (input.path()) |path| {
1510 diags.addParseError(path, "failed to parse linker input: {s}", .{@errorName(e)});
1511 } else {
1512 diags.addError("failed to {s}: {s}", .{ input.taskName(), @errorName(e) });
1513 }
1514 },
1515 };
1516 },
1517 .codegen_nav => |nav_index| {
1518 const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) };
1519 pt.linkerUpdateNav(nav_index) catch |err| switch (err) {
1520 error.OutOfMemory => diags.setAllocFailure(),
1521 };
1522 },
1523 .codegen_func => |func| {
1524 const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) };
1525 // This call takes ownership of `func.air`.
1526 pt.linkerUpdateFunc(func.func, func.air) catch |err| switch (err) {
1527 error.OutOfMemory => diags.setAllocFailure(),
1528 };
1529 },
1530 .codegen_type => |ty| {
1531 const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) };
1532 pt.linkerUpdateContainerType(ty) catch |err| switch (err) {
1533 error.OutOfMemory => diags.setAllocFailure(),
1534 };
1535 },
15061536 }
1507};
1537}
15081538
15091539pub fn spawnLld(
15101540 comp: *Compilation,