1//! Stores and manages the queue of link tasks. Each task is either a `PrelinkTask` or a `ZcuTask`.
2//!
3//! There are two `std.Io.Queue`s, for prelink and ZCU tasks respectively. The compiler writes tasks
4//! to these queues, and a single concurrent linker task receives and processes them. `Compilation`
5//! is responsible for calling `finishPrelinkQueue` and `finishZcuQueue` once all relevant tasks
6//! have been queued. All prelink tasks must be queued and completed before any ZCU tasks can be
7//! processed.
8//!
9//! If concurrency is unavailable, the `enqueuePrelink` and `enqueueZcu` functions will instead run
10//! the given tasks immediately---the queues are unused.
11//!
12//! If the codegen backend does not permit concurrency, then `Compilation` will call `finishZcuQueue`
13//! early so that the concurrent linker task exists after prelink and ZCU tasks will run
14//! non-concurrently in `enqueueZcu`.
15
16/// This is the concurrent call to `runLinkTasks`. It may be set to non-`null` in `start`, and is
17/// set to `null` by the main thread after it is canceled. It is not otherwise modified; as such, it
18/// may be checked non-atomically. If a task is being queued and this is `null`, tasks must be run
19/// eagerly.
20future: ?std.Io.Future(void),
21
22/// This is only used if `future == null` during prelink. In that case, it is used to ensure that
23/// only one prelink task is run at a time.
24prelink_mutex: std.Io.Mutex,
25
26/// Only valid if `future != null`.
27prelink_queue: std.Io.Queue(PrelinkTask),
28/// Only valid if `future != null`.
29zcu_queue: std.Io.Queue(ZcuTask),
30
31/// The capacity of the task queue buffers.
32pub const buffer_size = 512;
33
34/// The initial `Queue` state, containing no tasks, expecting no prelink tasks, and with no running worker thread.
35/// The `queued_prelink` field may be appended to before calling `start`.
36pub const empty: Queue = .{
37 .future = null,
38 .prelink_mutex = .init,
39 .prelink_queue = undefined, // set in `start` if needed
40 .zcu_queue = undefined, // set in `start` if needed
41};
42
43pub fn cancel(q: *Queue, io: Io) void {
44 if (q.future) |*f| {
45 f.cancel(io);
46 q.future = null;
47 }
48}
49
50pub fn wait(q: *Queue, io: Io) void {
51 if (q.future) |*f| {
52 f.await(io);
53 q.future = null;
54 }
55}
56
57/// This is expected to be called exactly once, after which the caller must not directly access
58/// `queued_prelink` any longer. This will spawn the link thread if necessary.
59pub fn start(
60 q: *Queue,
61 comp: *Compilation,
62 arena: Allocator,
63) Allocator.Error!void {
64 assert(q.future == null);
65 q.prelink_queue = .init(try arena.alloc(PrelinkTask, buffer_size));
66 q.zcu_queue = .init(try arena.alloc(ZcuTask, buffer_size));
67 if (comp.io.concurrent(runLinkTasks, .{ q, comp })) |future| {
68 // We will run link tasks concurrently.
69 q.future = future;
70 } else |err| switch (err) {
71 error.ConcurrencyUnavailable => {
72 // We will run link tasks on the main thread.
73 q.prelink_queue = undefined;
74 q.zcu_queue = undefined;
75 },
76 }
77}
78
79/// Enqueues all prelink tasks in `tasks`. Asserts that they were expected, i.e. that
80/// the queue is not yet closed. Also asserts that `tasks.len` is not 0.
81pub fn enqueuePrelink(q: *Queue, comp: *Compilation, tasks: []const PrelinkTask) Io.Cancelable!void {
82 const io = comp.io;
83
84 if (q.future != null) {
85 q.prelink_queue.putAll(io, tasks) catch |err| switch (err) {
86 error.Canceled => |e| return e,
87 error.Closed => unreachable,
88 };
89 } else {
90 try q.prelink_mutex.lock(io);
91 defer q.prelink_mutex.unlock(io);
92 for (tasks) |task| link.doPrelinkTask(comp, task);
93 }
94}
95
96pub fn enqueueZcu(
97 q: *Queue,
98 comp: *Compilation,
99 tid: Zcu.PerThread.Id,
100 task: ZcuTask,
101) Io.Cancelable!void {
102 const io = comp.io;
103
104 if (q.future != null) {
105 if (q.zcu_queue.putOne(io, task)) |_| {
106 return;
107 } else |err| switch (err) {
108 error.Canceled => |e| return e,
109 error.Closed => {
110 // The linker is still processing prelink tasks. Wait for those
111 // to finish, after which the linker task will exist, and ZCU
112 // tasks will be run non-concurrently. This logic exists for
113 // backends which do not support `Zcu.Feature.separate_thread`.
114 q.wait(io);
115 },
116 }
117 }
118
119 link.doZcuTask(comp, tid, task);
120}
121
122pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) Io.Cancelable!void {
123 if (q.future != null) {
124 q.prelink_queue.close(comp.io);
125 return;
126 }
127 // If linking non-concurrently, we must run prelink.
128 prelink: {
129 const lf = comp.bin_file orelse break :prelink;
130 if (lf.post_prelink) break :prelink;
131 if (comp.zcu != null and comp.zcu.?.llvm_object != null) {
132 // Don't call `prelink` just yet. It will be the frontend's responsibility instead,
133 // after it sends the ZCU object emitted by LLVM as the final link input.
134 break :prelink;
135 }
136
137 lf.prelink() catch |err| switch (err) {
138 error.OutOfMemory => comp.link_diags.setAllocFailure(),
139 error.AlreadyReported => {},
140 error.Canceled => |e| return e,
141 };
142 }
143}
144
145pub fn finishZcuQueue(q: *Queue, comp: *Compilation) void {
146 if (q.future != null) {
147 q.zcu_queue.close(comp.io);
148 }
149}
150
151fn runLinkTasks(q: *Queue, comp: *Compilation) void {
152 const io = comp.io;
153 const tid: Zcu.PerThread.Id = .acquire(io);
154 defer tid.release(io);
155
156 var have_idle_tasks = true;
157
158 prelink_tasks: while (true) {
159 var task_buf: [128]PrelinkTask = undefined;
160 const limit: usize = if (have_idle_tasks) 0 else 1;
161 const n = q.prelink_queue.get(io, &task_buf, limit) catch |err| switch (err) {
162 error.Canceled => return,
163 error.Closed => break :prelink_tasks,
164 };
165 if (n == 0) {
166 assert(have_idle_tasks);
167 have_idle_tasks = runIdleTask(comp, tid);
168 } else for (task_buf[0..n]) |task| {
169 link.doPrelinkTask(comp, task);
170 have_idle_tasks = true;
171 }
172 }
173
174 // We've finished the prelink tasks, so run prelink if necessary.
175 prelink: {
176 const lf = comp.bin_file orelse break :prelink;
177 if (lf.post_prelink) break :prelink;
178 if (comp.zcu != null and comp.zcu.?.llvm_object != null) {
179 // Don't call `prelink` just yet. It will be the frontend's responsibility instead,
180 // after it sends the ZCU object emitted by LLVM as the final link input.
181 break :prelink;
182 }
183 lf.prelink() catch |err| switch (err) {
184 error.OutOfMemory => comp.link_diags.setAllocFailure(),
185 error.Canceled => @panic("TODO"),
186 error.AlreadyReported => {},
187 };
188 }
189
190 zcu_tasks: while (true) {
191 var task_buf: [128]ZcuTask = undefined;
192 const limit: usize = if (have_idle_tasks) 0 else 1;
193 const n = q.zcu_queue.get(io, &task_buf, limit) catch |err| switch (err) {
194 error.Canceled => return,
195 error.Closed => break :zcu_tasks,
196 };
197 if (n == 0) {
198 assert(have_idle_tasks);
199 have_idle_tasks = runIdleTask(comp, tid);
200 } else for (task_buf[0..n]) |task| {
201 link.doZcuTask(comp, tid, task);
202 have_idle_tasks = true;
203 }
204 }
205}
206fn runIdleTask(comp: *Compilation, tid: Zcu.PerThread.Id) bool {
207 return link.doIdleTask(comp, tid) catch |err| switch (err) {
208 error.OutOfMemory => have_more: {
209 comp.link_diags.setAllocFailure();
210 break :have_more false;
211 },
212 error.AlreadyReported => false,
213 error.Canceled => {
214 comp.io.recancel();
215 return false;
216 },
217 };
218}
219
220const std = @import("std");
221const assert = std.debug.assert;
222const Allocator = std.mem.Allocator;
223const Io = std.Io;
224
225const Compilation = @import("../Compilation.zig");
226const InternPool = @import("../InternPool.zig");
227const link = @import("../link.zig");
228const PrelinkTask = link.PrelinkTask;
229const Queue = @This();
230const Zcu = @import("../Zcu.zig");
231const ZcuTask = link.ZcuTask;