| 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. |
| 20 | future: ?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. |
| 24 | prelink_mutex: std.Io.Mutex, |
| 25 | |
| 26 | /// Only valid if `future != null`. |
| 27 | prelink_queue: std.Io.Queue(PrelinkTask), |
| 28 | /// Only valid if `future != null`. |
| 29 | zcu_queue: std.Io.Queue(ZcuTask), |
| 30 | |
| 31 | /// The capacity of the task queue buffers. |
| 32 | pub 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`. |
| 36 | pub 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 | |
| 43 | pub fn cancel(q: *Queue, io: Io) void { |
| 44 | if (q.future) |*f| { |
| 45 | f.cancel(io); |
| 46 | q.future = null; |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | pub 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. |
| 59 | pub 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. |
| 81 | pub 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 | |
| 96 | pub 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 | |
| 122 | pub 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 | |
| 145 | pub fn finishZcuQueue(q: *Queue, comp: *Compilation) void { |
| 146 | if (q.future != null) { |
| 147 | q.zcu_queue.close(comp.io); |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | fn 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 | } |
| 206 | fn 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 | |
| 220 | const std = @import("std"); |
| 221 | const assert = std.debug.assert; |
| 222 | const Allocator = std.mem.Allocator; |
| 223 | const Io = std.Io; |
| 224 | |
| 225 | const Compilation = @import("../Compilation.zig"); |
| 226 | const InternPool = @import("../InternPool.zig"); |
| 227 | const link = @import("../link.zig"); |
| 228 | const PrelinkTask = link.PrelinkTask; |
| 229 | const Queue = @This(); |
| 230 | const Zcu = @import("../Zcu.zig"); |
| 231 | const ZcuTask = link.ZcuTask; |