authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-20 21:19:05-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-12-20 21:19:05-05:00
log4918605176c2e48c178847ea281b790334207733
tree4adb4cbf5e6764df65747fb3bae2c1640060eb89
parent4964bb3282bf13de03a79fad1fb9bca104dc1930
parent1d94a6893689ad1fb8e06308ae51603a6c8708a8
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7462 from ziglang/parallel-c-objects

Introduce a ThreadPool and parallel execution of some of the compilation work items

19 files changed, 731 insertions(+), 383 deletions(-)

CMakeLists.txt+11-1
......@@ -75,6 +75,7 @@ set(ZIG_TARGET_TRIPLE "native" CACHE STRING "arch-os-abi to output binaries for"
7575set(ZIG_TARGET_MCPU "baseline" CACHE STRING "-mcpu parameter to output binaries for")
7676set(ZIG_EXECUTABLE "" CACHE STRING "(when cross compiling) path to already-built zig binary")
7777set(ZIG_PREFER_LLVM_CONFIG off CACHE BOOL "(when cross compiling) use llvm-config to find target llvm dependencies if needed")
78set(ZIG_SINGLE_THREADED off CACHE BOOL "limit the zig compiler to use only 1 thread")
7879
7980find_package(llvm)
8081find_package(clang)
......@@ -410,7 +411,7 @@ set(ZIG_STAGE2_SOURCES
410411 "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig"
411412 "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig"
412413 "${CMAKE_SOURCE_DIR}/lib/std/process.zig"
413 "${CMAKE_SOURCE_DIR}/lib/std/progress.zig"
414 "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig"
414415 "${CMAKE_SOURCE_DIR}/lib/std/rand.zig"
415416 "${CMAKE_SOURCE_DIR}/lib/std/reset_event.zig"
416417 "${CMAKE_SOURCE_DIR}/lib/std/sort.zig"
......@@ -510,10 +511,13 @@ set(ZIG_STAGE2_SOURCES
510511 "${CMAKE_SOURCE_DIR}/src/Cache.zig"
511512 "${CMAKE_SOURCE_DIR}/src/Compilation.zig"
512513 "${CMAKE_SOURCE_DIR}/src/DepTokenizer.zig"
514 "${CMAKE_SOURCE_DIR}/src/Event.zig"
513515 "${CMAKE_SOURCE_DIR}/src/Module.zig"
514516 "${CMAKE_SOURCE_DIR}/src/Package.zig"
515517 "${CMAKE_SOURCE_DIR}/src/RangeSet.zig"
518 "${CMAKE_SOURCE_DIR}/src/ThreadPool.zig"
516519 "${CMAKE_SOURCE_DIR}/src/TypedValue.zig"
520 "${CMAKE_SOURCE_DIR}/src/WaitGroup.zig"
517521 "${CMAKE_SOURCE_DIR}/src/astgen.zig"
518522 "${CMAKE_SOURCE_DIR}/src/clang.zig"
519523 "${CMAKE_SOURCE_DIR}/src/clang_options.zig"
......@@ -713,6 +717,11 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
713717else()
714718 set(ZIG1_RELEASE_ARG -OReleaseFast --strip)
715719endif()
720if(ZIG_SINGLE_THREADED)
721 set(ZIG1_SINGLE_THREADED_ARG "--single-threaded")
722else()
723 set(ZIG1_SINGLE_THREADED_ARG "")
724endif()
716725
717726set(BUILD_ZIG1_ARGS
718727 "src/stage1.zig"
......@@ -722,6 +731,7 @@ set(BUILD_ZIG1_ARGS
722731 --override-lib-dir "${CMAKE_SOURCE_DIR}/lib"
723732 "-femit-bin=${ZIG1_OBJECT}"
724733 "${ZIG1_RELEASE_ARG}"
734 "${ZIG1_SINGLE_THREADED_ARG}"
725735 -lc
726736 --pkg-begin build_options "${ZIG_CONFIG_ZIG_OUT}"
727737 --pkg-end
ci/drone/linux_script+2-1
......@@ -17,7 +17,8 @@ git config core.abbrev 9
1717
1818mkdir build
1919cd build
20cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_INSTALL_PREFIX=$DISTDIR" -DZIG_STATIC=ON -DCMAKE_PREFIX_PATH=/deps/local -GNinja
20# TODO figure out why Drone CI is deadlocking and stop passing -DZIG_SINGLE_THREADED=ON
21cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_INSTALL_PREFIX=$DISTDIR" -DZIG_STATIC=ON -DCMAKE_PREFIX_PATH=/deps/local -GNinja -DZIG_SINGLE_THREADED=ON
2122
2223samu install
2324./zig build test -Dskip-release -Dskip-non-native
lib/std/Progress.zig created+345
......@@ -0,0 +1,345 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7//! This API non-allocating, non-fallible, and thread-safe.
8//! The tradeoff is that users of this API must provide the storage
9//! for each `Progress.Node`.
10//!
11//! Initialize the struct directly, overriding these fields as desired:
12//! * `refresh_rate_ms`
13//! * `initial_delay_ms`
14
15const std = @import("std");
16const windows = std.os.windows;
17const testing = std.testing;
18const assert = std.debug.assert;
19const Progress = @This();
20
21/// `null` if the current node (and its children) should
22/// not print on update()
23terminal: ?std.fs.File = undefined,
24
25/// Whether the terminal supports ANSI escape codes.
26supports_ansi_escape_codes: bool = false,
27
28root: Node = undefined,
29
30/// Keeps track of how much time has passed since the beginning.
31/// Used to compare with `initial_delay_ms` and `refresh_rate_ms`.
32timer: std.time.Timer = undefined,
33
34/// When the previous refresh was written to the terminal.
35/// Used to compare with `refresh_rate_ms`.
36prev_refresh_timestamp: u64 = undefined,
37
38/// This buffer represents the maximum number of bytes written to the terminal
39/// with each refresh.
40output_buffer: [100]u8 = undefined,
41
42/// How many nanoseconds between writing updates to the terminal.
43refresh_rate_ns: u64 = 50 * std.time.ns_per_ms,
44
45/// How many nanoseconds to keep the output hidden
46initial_delay_ns: u64 = 500 * std.time.ns_per_ms,
47
48done: bool = true,
49
50/// Protects the `refresh` function, as well as `node.recently_updated_child`.
51/// Without this, callsites would call `Node.end` and then free `Node` memory
52/// while it was still being accessed by the `refresh` function.
53update_lock: std.Mutex = .{},
54
55/// Keeps track of how many columns in the terminal have been output, so that
56/// we can move the cursor back later.
57columns_written: usize = undefined,
58
59/// Represents one unit of progress. Each node can have children nodes, or
60/// one can use integers with `update`.
61pub const Node = struct {
62 context: *Progress,
63 parent: ?*Node,
64 name: []const u8,
65 /// Must be handled atomically to be thread-safe.
66 recently_updated_child: ?*Node = null,
67 /// Must be handled atomically to be thread-safe. 0 means null.
68 unprotected_estimated_total_items: usize,
69 /// Must be handled atomically to be thread-safe.
70 unprotected_completed_items: usize,
71
72 /// Create a new child progress node. Thread-safe.
73 /// Call `Node.end` when done.
74 /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this
75 /// API to set `self.parent.recently_updated_child` with the return value.
76 /// Until that is fixed you probably want to call `activate` on the return value.
77 /// Passing 0 for `estimated_total_items` means unknown.
78 pub fn start(self: *Node, name: []const u8, estimated_total_items: usize) Node {
79 return Node{
80 .context = self.context,
81 .parent = self,
82 .name = name,
83 .unprotected_estimated_total_items = estimated_total_items,
84 .unprotected_completed_items = 0,
85 };
86 }
87
88 /// This is the same as calling `start` and then `end` on the returned `Node`. Thread-safe.
89 pub fn completeOne(self: *Node) void {
90 self.activate();
91 _ = @atomicRmw(usize, &self.unprotected_completed_items, .Add, 1, .Monotonic);
92 self.context.maybeRefresh();
93 }
94
95 /// Finish a started `Node`. Thread-safe.
96 pub fn end(self: *Node) void {
97 self.context.maybeRefresh();
98 if (self.parent) |parent| {
99 {
100 const held = self.context.update_lock.acquire();
101 defer held.release();
102 _ = @cmpxchgStrong(?*Node, &parent.recently_updated_child, self, null, .Monotonic, .Monotonic);
103 }
104 parent.completeOne();
105 } else {
106 self.context.done = true;
107 self.context.refresh();
108 }
109 }
110
111 /// Tell the parent node that this node is actively being worked on. Thread-safe.
112 pub fn activate(self: *Node) void {
113 if (self.parent) |parent| {
114 @atomicStore(?*Node, &parent.recently_updated_child, self, .Release);
115 }
116 }
117
118 /// Thread-safe. 0 means unknown.
119 pub fn setEstimatedTotalItems(self: *Node, count: usize) void {
120 @atomicStore(usize, &self.unprotected_estimated_total_items, count, .Monotonic);
121 }
122
123 /// Thread-safe.
124 pub fn setCompletedItems(self: *Node, completed_items: usize) void {
125 @atomicStore(usize, &self.unprotected_completed_items, completed_items, .Monotonic);
126 }
127};
128
129/// Create a new progress node.
130/// Call `Node.end` when done.
131/// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this
132/// API to return Progress rather than accept it as a parameter.
133/// `estimated_total_items` value of 0 means unknown.
134pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) !*Node {
135 const stderr = std.io.getStdErr();
136 self.terminal = null;
137 if (stderr.supportsAnsiEscapeCodes()) {
138 self.terminal = stderr;
139 self.supports_ansi_escape_codes = true;
140 } else if (std.builtin.os.tag == .windows and stderr.isTty()) {
141 self.terminal = stderr;
142 }
143 self.root = Node{
144 .context = self,
145 .parent = null,
146 .name = name,
147 .unprotected_estimated_total_items = estimated_total_items,
148 .unprotected_completed_items = 0,
149 };
150 self.columns_written = 0;
151 self.prev_refresh_timestamp = 0;
152 self.timer = try std.time.Timer.start();
153 self.done = false;
154 return &self.root;
155}
156
157/// Updates the terminal if enough time has passed since last update. Thread-safe.
158pub fn maybeRefresh(self: *Progress) void {
159 const now = self.timer.read();
160 if (now < self.initial_delay_ns) return;
161 const held = self.update_lock.tryAcquire() orelse return;
162 defer held.release();
163 if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;
164 return self.refreshWithHeldLock();
165}
166
167/// Updates the terminal and resets `self.next_refresh_timestamp`. Thread-safe.
168pub fn refresh(self: *Progress) void {
169 const held = self.update_lock.tryAcquire() orelse return;
170 defer held.release();
171
172 return self.refreshWithHeldLock();
173}
174
175fn refreshWithHeldLock(self: *Progress) void {
176 const file = self.terminal orelse return;
177
178 const prev_columns_written = self.columns_written;
179 var end: usize = 0;
180 if (self.columns_written > 0) {
181 // restore the cursor position by moving the cursor
182 // `columns_written` cells to the left, then clear the rest of the
183 // line
184 if (self.supports_ansi_escape_codes) {
185 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len;
186 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
187 } else if (std.builtin.os.tag == .windows) winapi: {
188 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
189 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE)
190 unreachable;
191
192 var cursor_pos = windows.COORD{
193 .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written),
194 .Y = info.dwCursorPosition.Y,
195 };
196
197 if (cursor_pos.X < 0)
198 cursor_pos.X = 0;
199
200 const fill_chars = @intCast(windows.DWORD, info.dwSize.X - cursor_pos.X);
201
202 var written: windows.DWORD = undefined;
203 if (windows.kernel32.FillConsoleOutputAttribute(
204 file.handle,
205 info.wAttributes,
206 fill_chars,
207 cursor_pos,
208 &written,
209 ) != windows.TRUE) {
210 // Stop trying to write to this file.
211 self.terminal = null;
212 break :winapi;
213 }
214 if (windows.kernel32.FillConsoleOutputCharacterA(
215 file.handle,
216 ' ',
217 fill_chars,
218 cursor_pos,
219 &written,
220 ) != windows.TRUE) unreachable;
221
222 if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE)
223 unreachable;
224 } else unreachable;
225
226 self.columns_written = 0;
227 }
228
229 if (!self.done) {
230 var need_ellipse = false;
231 var maybe_node: ?*Node = &self.root;
232 while (maybe_node) |node| {
233 if (need_ellipse) {
234 self.bufWrite(&end, "... ", .{});
235 }
236 need_ellipse = false;
237 const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .Monotonic);
238 const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .Monotonic);
239 if (node.name.len != 0 or eti > 0) {
240 if (node.name.len != 0) {
241 self.bufWrite(&end, "{s}", .{node.name});
242 need_ellipse = true;
243 }
244 if (eti > 0) {
245 if (need_ellipse) self.bufWrite(&end, " ", .{});
246 self.bufWrite(&end, "[{d}/{d}] ", .{ completed_items + 1, eti });
247 need_ellipse = false;
248 } else if (completed_items != 0) {
249 if (need_ellipse) self.bufWrite(&end, " ", .{});
250 self.bufWrite(&end, "[{d}] ", .{completed_items + 1});
251 need_ellipse = false;
252 }
253 }
254 maybe_node = @atomicLoad(?*Node, &node.recently_updated_child, .Acquire);
255 }
256 if (need_ellipse) {
257 self.bufWrite(&end, "... ", .{});
258 }
259 }
260
261 _ = file.write(self.output_buffer[0..end]) catch |e| {
262 // Stop trying to write to this file once it errors.
263 self.terminal = null;
264 };
265 self.prev_refresh_timestamp = self.timer.read();
266}
267
268pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
269 const file = self.terminal orelse return;
270 self.refresh();
271 file.outStream().print(format, args) catch {
272 self.terminal = null;
273 return;
274 };
275 self.columns_written = 0;
276}
277
278fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {
279 if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
280 const amt = written.len;
281 end.* += amt;
282 self.columns_written += amt;
283 } else |err| switch (err) {
284 error.NoSpaceLeft => {
285 self.columns_written += self.output_buffer.len - end.*;
286 end.* = self.output_buffer.len;
287 },
288 }
289 const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11;
290 const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end;
291 if (end.* > max_end) {
292 const suffix = "... ";
293 self.columns_written = self.columns_written - (end.* - max_end) + suffix.len;
294 std.mem.copy(u8, self.output_buffer[max_end..], suffix);
295 end.* = max_end + suffix.len;
296 }
297}
298
299test "basic functionality" {
300 var disable = true;
301 if (disable) {
302 // This test is disabled because it uses time.sleep() and is therefore slow. It also
303 // prints bogus progress data to stderr.
304 return error.SkipZigTest;
305 }
306 var progress = Progress{};
307 const root_node = try progress.start("", 100);
308 defer root_node.end();
309
310 const sub_task_names = [_][]const u8{
311 "reticulating splines",
312 "adjusting shoes",
313 "climbing towers",
314 "pouring juice",
315 };
316 var next_sub_task: usize = 0;
317
318 var i: usize = 0;
319 while (i < 100) : (i += 1) {
320 var node = root_node.start(sub_task_names[next_sub_task], 5);
321 node.activate();
322 next_sub_task = (next_sub_task + 1) % sub_task_names.len;
323
324 node.completeOne();
325 std.time.sleep(5 * std.time.ns_per_ms);
326 node.completeOne();
327 node.completeOne();
328 std.time.sleep(5 * std.time.ns_per_ms);
329 node.completeOne();
330 node.completeOne();
331 std.time.sleep(5 * std.time.ns_per_ms);
332
333 node.end();
334
335 std.time.sleep(5 * std.time.ns_per_ms);
336 }
337 {
338 var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", 0);
339 node.activate();
340 std.time.sleep(10 * std.time.ns_per_ms);
341 progress.refresh();
342 std.time.sleep(10 * std.time.ns_per_ms);
343 node.end();
344 }
345}
lib/std/auto_reset_event.zig+22-22
......@@ -11,33 +11,33 @@ const assert = std.debug.assert;
1111/// Similar to std.ResetEvent but on `set()` it also (atomically) does `reset()`.
1212/// Unlike std.ResetEvent, `wait()` can only be called by one thread (MPSC-like).
1313pub const AutoResetEvent = struct {
14 // AutoResetEvent has 3 possible states:
15 // - UNSET: the AutoResetEvent is currently unset
16 // - SET: the AutoResetEvent was notified before a wait() was called
17 // - <std.ResetEvent pointer>: there is an active waiter waiting for a notification.
18 //
19 // When attempting to wait:
20 // if the event is unset, it registers a ResetEvent pointer to be notified when the event is set
21 // if the event is already set, then it consumes the notification and resets the event.
22 //
23 // When attempting to notify:
24 // if the event is unset, then we set the event
25 // if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent
26 //
27 // This ensures that the event is automatically reset after a wait() has been issued
28 // and avoids the race condition when using std.ResetEvent in the following scenario:
29 // thread 1 | thread 2
30 // std.ResetEvent.wait() |
31 // | std.ResetEvent.set()
32 // | std.ResetEvent.set()
33 // std.ResetEvent.reset() |
34 // std.ResetEvent.wait() | (missed the second .set() notification above)
14 /// AutoResetEvent has 3 possible states:
15 /// - UNSET: the AutoResetEvent is currently unset
16 /// - SET: the AutoResetEvent was notified before a wait() was called
17 /// - <std.ResetEvent pointer>: there is an active waiter waiting for a notification.
18 ///
19 /// When attempting to wait:
20 /// if the event is unset, it registers a ResetEvent pointer to be notified when the event is set
21 /// if the event is already set, then it consumes the notification and resets the event.
22 ///
23 /// When attempting to notify:
24 /// if the event is unset, then we set the event
25 /// if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent
26 ///
27 /// This ensures that the event is automatically reset after a wait() has been issued
28 /// and avoids the race condition when using std.ResetEvent in the following scenario:
29 /// thread 1 | thread 2
30 /// std.ResetEvent.wait() |
31 /// | std.ResetEvent.set()
32 /// | std.ResetEvent.set()
33 /// std.ResetEvent.reset() |
34 /// std.ResetEvent.wait() | (missed the second .set() notification above)
3535 state: usize = UNSET,
3636
3737 const UNSET = 0;
3838 const SET = 1;
3939
40 // the minimum alignment for the `*std.ResetEvent` created by wait*()
40 /// the minimum alignment for the `*std.ResetEvent` created by wait*()
4141 const event_align = std.math.max(@alignOf(std.ResetEvent), 2);
4242
4343 pub fn wait(self: *AutoResetEvent) void {
lib/std/progress.zig deleted-310
......@@ -1,310 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6const std = @import("std");
7const windows = std.os.windows;
8const testing = std.testing;
9const assert = std.debug.assert;
10
11/// This API is non-allocating and non-fallible. The tradeoff is that users of
12/// this API must provide the storage for each `Progress.Node`.
13/// Initialize the struct directly, overriding these fields as desired:
14/// * `refresh_rate_ms`
15/// * `initial_delay_ms`
16pub const Progress = struct {
17 /// `null` if the current node (and its children) should
18 /// not print on update()
19 terminal: ?std.fs.File = undefined,
20
21 /// Whether the terminal supports ANSI escape codes.
22 supports_ansi_escape_codes: bool = false,
23
24 root: Node = undefined,
25
26 /// Keeps track of how much time has passed since the beginning.
27 /// Used to compare with `initial_delay_ms` and `refresh_rate_ms`.
28 timer: std.time.Timer = undefined,
29
30 /// When the previous refresh was written to the terminal.
31 /// Used to compare with `refresh_rate_ms`.
32 prev_refresh_timestamp: u64 = undefined,
33
34 /// This buffer represents the maximum number of bytes written to the terminal
35 /// with each refresh.
36 output_buffer: [100]u8 = undefined,
37
38 /// How many nanoseconds between writing updates to the terminal.
39 refresh_rate_ns: u64 = 50 * std.time.ns_per_ms,
40
41 /// How many nanoseconds to keep the output hidden
42 initial_delay_ns: u64 = 500 * std.time.ns_per_ms,
43
44 done: bool = true,
45
46 /// Keeps track of how many columns in the terminal have been output, so that
47 /// we can move the cursor back later.
48 columns_written: usize = undefined,
49
50 /// Represents one unit of progress. Each node can have children nodes, or
51 /// one can use integers with `update`.
52 pub const Node = struct {
53 context: *Progress,
54 parent: ?*Node,
55 completed_items: usize,
56 name: []const u8,
57 recently_updated_child: ?*Node = null,
58
59 /// This field may be updated freely.
60 estimated_total_items: ?usize,
61
62 /// Create a new child progress node.
63 /// Call `Node.end` when done.
64 /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this
65 /// API to set `self.parent.recently_updated_child` with the return value.
66 /// Until that is fixed you probably want to call `activate` on the return value.
67 pub fn start(self: *Node, name: []const u8, estimated_total_items: ?usize) Node {
68 return Node{
69 .context = self.context,
70 .parent = self,
71 .completed_items = 0,
72 .name = name,
73 .estimated_total_items = estimated_total_items,
74 };
75 }
76
77 /// This is the same as calling `start` and then `end` on the returned `Node`.
78 pub fn completeOne(self: *Node) void {
79 if (self.parent) |parent| parent.recently_updated_child = self;
80 self.completed_items += 1;
81 self.context.maybeRefresh();
82 }
83
84 pub fn end(self: *Node) void {
85 self.context.maybeRefresh();
86 if (self.parent) |parent| {
87 if (parent.recently_updated_child) |parent_child| {
88 if (parent_child == self) {
89 parent.recently_updated_child = null;
90 }
91 }
92 parent.completeOne();
93 } else {
94 self.context.done = true;
95 self.context.refresh();
96 }
97 }
98
99 /// Tell the parent node that this node is actively being worked on.
100 pub fn activate(self: *Node) void {
101 if (self.parent) |parent| parent.recently_updated_child = self;
102 }
103 };
104
105 /// Create a new progress node.
106 /// Call `Node.end` when done.
107 /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this
108 /// API to return Progress rather than accept it as a parameter.
109 pub fn start(self: *Progress, name: []const u8, estimated_total_items: ?usize) !*Node {
110 const stderr = std.io.getStdErr();
111 self.terminal = null;
112 if (stderr.supportsAnsiEscapeCodes()) {
113 self.terminal = stderr;
114 self.supports_ansi_escape_codes = true;
115 } else if (std.builtin.os.tag == .windows and stderr.isTty()) {
116 self.terminal = stderr;
117 }
118 self.root = Node{
119 .context = self,
120 .parent = null,
121 .completed_items = 0,
122 .name = name,
123 .estimated_total_items = estimated_total_items,
124 };
125 self.columns_written = 0;
126 self.prev_refresh_timestamp = 0;
127 self.timer = try std.time.Timer.start();
128 self.done = false;
129 return &self.root;
130 }
131
132 /// Updates the terminal if enough time has passed since last update.
133 pub fn maybeRefresh(self: *Progress) void {
134 const now = self.timer.read();
135 if (now < self.initial_delay_ns) return;
136 if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;
137 self.refresh();
138 }
139
140 /// Updates the terminal and resets `self.next_refresh_timestamp`.
141 pub fn refresh(self: *Progress) void {
142 const file = self.terminal orelse return;
143
144 const prev_columns_written = self.columns_written;
145 var end: usize = 0;
146 if (self.columns_written > 0) {
147 // restore the cursor position by moving the cursor
148 // `columns_written` cells to the left, then clear the rest of the
149 // line
150 if (self.supports_ansi_escape_codes) {
151 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{}D", .{self.columns_written}) catch unreachable).len;
152 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
153 } else if (std.builtin.os.tag == .windows) winapi: {
154 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
155 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE)
156 unreachable;
157
158 var cursor_pos = windows.COORD{
159 .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written),
160 .Y = info.dwCursorPosition.Y,
161 };
162
163 if (cursor_pos.X < 0)
164 cursor_pos.X = 0;
165
166 const fill_chars = @intCast(windows.DWORD, info.dwSize.X - cursor_pos.X);
167
168 var written: windows.DWORD = undefined;
169 if (windows.kernel32.FillConsoleOutputAttribute(
170 file.handle,
171 info.wAttributes,
172 fill_chars,
173 cursor_pos,
174 &written,
175 ) != windows.TRUE) {
176 // Stop trying to write to this file.
177 self.terminal = null;
178 break :winapi;
179 }
180 if (windows.kernel32.FillConsoleOutputCharacterA(
181 file.handle,
182 ' ',
183 fill_chars,
184 cursor_pos,
185 &written,
186 ) != windows.TRUE) unreachable;
187
188 if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE)
189 unreachable;
190 } else unreachable;
191
192 self.columns_written = 0;
193 }
194
195 if (!self.done) {
196 var need_ellipse = false;
197 var maybe_node: ?*Node = &self.root;
198 while (maybe_node) |node| {
199 if (need_ellipse) {
200 self.bufWrite(&end, "... ", .{});
201 }
202 need_ellipse = false;
203 if (node.name.len != 0 or node.estimated_total_items != null) {
204 if (node.name.len != 0) {
205 self.bufWrite(&end, "{}", .{node.name});
206 need_ellipse = true;
207 }
208 if (node.estimated_total_items) |total| {
209 if (need_ellipse) self.bufWrite(&end, " ", .{});
210 self.bufWrite(&end, "[{}/{}] ", .{ node.completed_items + 1, total });
211 need_ellipse = false;
212 } else if (node.completed_items != 0) {
213 if (need_ellipse) self.bufWrite(&end, " ", .{});
214 self.bufWrite(&end, "[{}] ", .{node.completed_items + 1});
215 need_ellipse = false;
216 }
217 }
218 maybe_node = node.recently_updated_child;
219 }
220 if (need_ellipse) {
221 self.bufWrite(&end, "... ", .{});
222 }
223 }
224
225 _ = file.write(self.output_buffer[0..end]) catch |e| {
226 // Stop trying to write to this file once it errors.
227 self.terminal = null;
228 };
229 self.prev_refresh_timestamp = self.timer.read();
230 }
231
232 pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
233 const file = self.terminal orelse return;
234 self.refresh();
235 file.outStream().print(format, args) catch {
236 self.terminal = null;
237 return;
238 };
239 self.columns_written = 0;
240 }
241
242 fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {
243 if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
244 const amt = written.len;
245 end.* += amt;
246 self.columns_written += amt;
247 } else |err| switch (err) {
248 error.NoSpaceLeft => {
249 self.columns_written += self.output_buffer.len - end.*;
250 end.* = self.output_buffer.len;
251 },
252 }
253 const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11;
254 const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end;
255 if (end.* > max_end) {
256 const suffix = "... ";
257 self.columns_written = self.columns_written - (end.* - max_end) + suffix.len;
258 std.mem.copy(u8, self.output_buffer[max_end..], suffix);
259 end.* = max_end + suffix.len;
260 }
261 }
262};
263
264test "basic functionality" {
265 var disable = true;
266 if (disable) {
267 // This test is disabled because it uses time.sleep() and is therefore slow. It also
268 // prints bogus progress data to stderr.
269 return error.SkipZigTest;
270 }
271 var progress = Progress{};
272 const root_node = try progress.start("", 100);
273 defer root_node.end();
274
275 const sub_task_names = [_][]const u8{
276 "reticulating splines",
277 "adjusting shoes",
278 "climbing towers",
279 "pouring juice",
280 };
281 var next_sub_task: usize = 0;
282
283 var i: usize = 0;
284 while (i < 100) : (i += 1) {
285 var node = root_node.start(sub_task_names[next_sub_task], 5);
286 node.activate();
287 next_sub_task = (next_sub_task + 1) % sub_task_names.len;
288
289 node.completeOne();
290 std.time.sleep(5 * std.time.ns_per_ms);
291 node.completeOne();
292 node.completeOne();
293 std.time.sleep(5 * std.time.ns_per_ms);
294 node.completeOne();
295 node.completeOne();
296 std.time.sleep(5 * std.time.ns_per_ms);
297
298 node.end();
299
300 std.time.sleep(5 * std.time.ns_per_ms);
301 }
302 {
303 var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", null);
304 node.activate();
305 std.time.sleep(10 * std.time.ns_per_ms);
306 progress.refresh();
307 std.time.sleep(10 * std.time.ns_per_ms);
308 node.end();
309 }
310}
lib/std/special/test_runner.zig+1-1
......@@ -36,7 +36,7 @@ pub fn main() anyerror!void {
3636 }
3737 std.testing.log_level = .warn;
3838
39 var test_node = root_node.start(test_fn.name, null);
39 var test_node = root_node.start(test_fn.name, 0);
4040 test_node.activate();
4141 progress.refresh();
4242 if (progress.terminal == null) {
lib/std/std.zig+1-1
......@@ -29,7 +29,7 @@ pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayE
2929pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;
3030pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian;
3131pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
32pub const Progress = @import("progress.zig").Progress;
32pub const Progress = @import("Progress.zig");
3333pub const ResetEvent = @import("reset_event.zig").ResetEvent;
3434pub const SemanticVersion = @import("SemanticVersion.zig");
3535pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
src/Compilation.zig+85-30
......@@ -26,6 +26,8 @@ const Module = @import("Module.zig");
2626const Cache = @import("Cache.zig");
2727const stage1 = @import("stage1.zig");
2828const translate_c = @import("translate_c.zig");
29const ThreadPool = @import("ThreadPool.zig");
30const WaitGroup = @import("WaitGroup.zig");
2931
3032/// General-purpose allocator. Used for both temporary and long-term storage.
3133gpa: *Allocator,
......@@ -41,7 +43,12 @@ link_error_flags: link.File.ErrorFlags = .{},
4143
4244work_queue: std.fifo.LinearFifo(Job, .Dynamic),
4345
46/// These jobs are to invoke the Clang compiler to create an object file, which
47/// gets linked with the Compilation.
48c_object_work_queue: std.fifo.LinearFifo(*CObject, .Dynamic),
49
4450/// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator.
51/// This data is accessed by multiple threads and is protected by `mutex`.
4552failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *ErrorMsg) = .{},
4653
4754keep_source_files_loaded: bool,
......@@ -74,6 +81,7 @@ zig_lib_directory: Directory,
7481local_cache_directory: Directory,
7582global_cache_directory: Directory,
7683libc_include_dir_list: []const []const u8,
84thread_pool: *ThreadPool,
7785
7886/// Populated when we build the libc++ static library. A Job to build this is placed in the queue
7987/// and resolved before calling linker.flush().
......@@ -111,6 +119,9 @@ owned_link_dir: ?std.fs.Dir,
111119/// Don't use this for anything other than stage1 compatibility.
112120color: @import("main.zig").Color = .auto,
113121
122/// This mutex guards all `Compilation` mutable state.
123mutex: std.Mutex = .{},
124
114125test_filter: ?[]const u8,
115126test_name_prefix: ?[]const u8,
116127test_evented_io: bool,
......@@ -150,9 +161,6 @@ const Job = union(enum) {
150161 /// The source file containing the Decl has been updated, and so the
151162 /// Decl may need its line number information updated in the debug info.
152163 update_line_number: *Module.Decl,
153 /// Invoke the Clang compiler to create an object file, which gets linked
154 /// with the Compilation.
155 c_object: *CObject,
156164
157165 /// one of the glibc static objects
158166 glibc_crt_file: glibc.CRTFile,
......@@ -330,6 +338,7 @@ pub const InitOptions = struct {
330338 root_name: []const u8,
331339 root_pkg: ?*Package,
332340 output_mode: std.builtin.OutputMode,
341 thread_pool: *ThreadPool,
333342 dynamic_linker: ?[]const u8 = null,
334343 /// `null` means to not emit a binary file.
335344 emit_bin: ?EmitLoc,
......@@ -971,6 +980,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
971980 .emit_analysis = options.emit_analysis,
972981 .emit_docs = options.emit_docs,
973982 .work_queue = std.fifo.LinearFifo(Job, .Dynamic).init(gpa),
983 .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa),
974984 .keep_source_files_loaded = options.keep_source_files_loaded,
975985 .use_clang = use_clang,
976986 .clang_argv = options.clang_argv,
......@@ -979,6 +989,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
979989 .self_exe_path = options.self_exe_path,
980990 .libc_include_dir_list = libc_dirs.libc_include_dir_list,
981991 .sanitize_c = sanitize_c,
992 .thread_pool = options.thread_pool,
982993 .clang_passthrough_mode = options.clang_passthrough_mode,
983994 .clang_preprocessor_mode = options.clang_preprocessor_mode,
984995 .verbose_cc = options.verbose_cc,
......@@ -1190,11 +1201,13 @@ pub fn update(self: *Compilation) !void {
11901201 const tracy = trace(@src());
11911202 defer tracy.end();
11921203
1204 self.c_object_cache_digest_set.clearRetainingCapacity();
1205
11931206 // For compiling C objects, we rely on the cache hash system to avoid duplicating work.
11941207 // Add a Job for each C object.
1195 try self.work_queue.ensureUnusedCapacity(self.c_object_table.items().len);
1208 try self.c_object_work_queue.ensureUnusedCapacity(self.c_object_table.items().len);
11961209 for (self.c_object_table.items()) |entry| {
1197 self.work_queue.writeItemAssumeCapacity(.{ .c_object = entry.key });
1210 self.c_object_work_queue.writeItemAssumeCapacity(entry.key);
11981211 }
11991212
12001213 const use_stage1 = build_options.is_stage1 and self.bin_file.options.use_llvm;
......@@ -1365,13 +1378,23 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
13651378
13661379pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemory }!void {
13671380 var progress: std.Progress = .{};
1368 var main_progress_node = try progress.start("", null);
1381 var main_progress_node = try progress.start("", 0);
13691382 defer main_progress_node.end();
13701383 if (self.color == .off) progress.terminal = null;
13711384
13721385 var c_comp_progress_node = main_progress_node.start("Compile C Objects", self.c_source_files.len);
13731386 defer c_comp_progress_node.end();
13741387
1388 var wg = WaitGroup{};
1389 defer wg.wait();
1390
1391 while (self.c_object_work_queue.readItem()) |c_object| {
1392 wg.start();
1393 try self.thread_pool.spawn(workerUpdateCObject, .{
1394 self, c_object, &c_comp_progress_node, &wg,
1395 });
1396 }
1397
13751398 while (self.work_queue.readItem()) |work_item| switch (work_item) {
13761399 .codegen_decl => |decl| switch (decl.analysis) {
13771400 .unreferenced => unreachable,
......@@ -1447,21 +1470,6 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
14471470 decl.analysis = .codegen_failure_retryable;
14481471 };
14491472 },
1450 .c_object => |c_object| {
1451 self.updateCObject(c_object, &c_comp_progress_node) catch |err| switch (err) {
1452 error.AnalysisFail => continue,
1453 else => {
1454 try self.failed_c_objects.ensureCapacity(self.gpa, self.failed_c_objects.items().len + 1);
1455 self.failed_c_objects.putAssumeCapacityNoClobber(c_object, try ErrorMsg.create(
1456 self.gpa,
1457 0,
1458 "unable to build C object: {s}",
1459 .{@errorName(err)},
1460 ));
1461 c_object.status = .{ .failure = {} };
1462 },
1463 };
1464 },
14651473 .glibc_crt_file => |crt_file| {
14661474 glibc.buildCRTFile(self, crt_file) catch |err| {
14671475 // TODO Expose this as a normal compile error rather than crashing here.
......@@ -1553,7 +1561,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
15531561 };
15541562}
15551563
1556pub fn obtainCObjectCacheManifest(comp: *Compilation) Cache.Manifest {
1564pub fn obtainCObjectCacheManifest(comp: *const Compilation) Cache.Manifest {
15571565 var man = comp.cache_parent.obtain();
15581566
15591567 // Only things that need to be added on top of the base hash, and only things
......@@ -1708,6 +1716,37 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult {
17081716 };
17091717}
17101718
1719fn workerUpdateCObject(
1720 comp: *Compilation,
1721 c_object: *CObject,
1722 progress_node: *std.Progress.Node,
1723 wg: *WaitGroup,
1724) void {
1725 defer wg.stop();
1726
1727 comp.updateCObject(c_object, progress_node) catch |err| switch (err) {
1728 error.AnalysisFail => return,
1729 else => {
1730 {
1731 const lock = comp.mutex.acquire();
1732 defer lock.release();
1733 comp.failed_c_objects.ensureCapacity(comp.gpa, comp.failed_c_objects.items().len + 1) catch {
1734 fatal("TODO handle this by setting c_object.status = oom failure", .{});
1735 };
1736 comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, ErrorMsg.create(
1737 comp.gpa,
1738 0,
1739 "unable to build C object: {s}",
1740 .{@errorName(err)},
1741 ) catch {
1742 fatal("TODO handle this by setting c_object.status = oom failure", .{});
1743 });
1744 }
1745 c_object.status = .{ .failure = {} };
1746 },
1747 };
1748}
1749
17111750fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *std.Progress.Node) !void {
17121751 if (!build_options.have_llvm) {
17131752 return comp.failCObj(c_object, "clang not available: compiler built without LLVM extensions", .{});
......@@ -1720,6 +1759,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *
17201759
17211760 if (c_object.clearStatus(comp.gpa)) {
17221761 // There was previous failure.
1762 const lock = comp.mutex.acquire();
1763 defer lock.release();
17231764 comp.failed_c_objects.removeAssertDiscard(c_object);
17241765 }
17251766
......@@ -1747,8 +1788,16 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *
17471788 }
17481789
17491790 {
1750 const gop = try comp.c_object_cache_digest_set.getOrPut(comp.gpa, man.hash.peekBin());
1751 if (gop.found_existing) {
1791 const is_collision = blk: {
1792 const bin_digest = man.hash.peekBin();
1793
1794 const lock = comp.mutex.acquire();
1795 defer lock.release();
1796
1797 const gop = try comp.c_object_cache_digest_set.getOrPut(comp.gpa, bin_digest);
1798 break :blk gop.found_existing;
1799 };
1800 if (is_collision) {
17521801 return comp.failCObj(
17531802 c_object,
17541803 "the same source file was already added to the same compilation with the same flags",
......@@ -1764,7 +1813,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *
17641813 const c_source_basename = std.fs.path.basename(c_object.src.src_path);
17651814
17661815 c_comp_progress_node.activate();
1767 var child_progress_node = c_comp_progress_node.start(c_source_basename, null);
1816 var child_progress_node = c_comp_progress_node.start(c_source_basename, 0);
17681817 child_progress_node.activate();
17691818 defer child_progress_node.end();
17701819
......@@ -1929,7 +1978,7 @@ pub fn addTranslateCCArgs(
19291978
19301979/// Add common C compiler args between translate-c and C object compilation.
19311980pub fn addCCArgs(
1932 comp: *Compilation,
1981 comp: *const Compilation,
19331982 arena: *Allocator,
19341983 argv: *std.ArrayList([]const u8),
19351984 ext: FileExt,
......@@ -2164,10 +2213,14 @@ fn failCObj(comp: *Compilation, c_object: *CObject, comptime format: []const u8,
21642213
21652214fn failCObjWithOwnedErrorMsg(comp: *Compilation, c_object: *CObject, err_msg: *ErrorMsg) InnerError {
21662215 {
2167 errdefer err_msg.destroy(comp.gpa);
2168 try comp.failed_c_objects.ensureCapacity(comp.gpa, comp.failed_c_objects.items().len + 1);
2216 const lock = comp.mutex.acquire();
2217 defer lock.release();
2218 {
2219 errdefer err_msg.destroy(comp.gpa);
2220 try comp.failed_c_objects.ensureCapacity(comp.gpa, comp.failed_c_objects.items().len + 1);
2221 }
2222 comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, err_msg);
21692223 }
2170 comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, err_msg);
21712224 c_object.status = .failure;
21722225 return error.AnalysisFail;
21732226}
......@@ -2324,7 +2377,7 @@ test "classifyFileExt" {
23242377 std.testing.expectEqual(FileExt.zir, classifyFileExt("foo.zir"));
23252378}
23262379
2327fn haveFramePointer(comp: *Compilation) bool {
2380fn haveFramePointer(comp: *const Compilation) bool {
23282381 // If you complicate this logic make sure you update the parent cache hash.
23292382 // Right now it's not in the cache hash because the value depends on optimize_mode
23302383 // and strip which are both already part of the hash.
......@@ -2775,6 +2828,7 @@ fn buildOutputFromZig(
27752828 .root_name = root_name,
27762829 .root_pkg = &root_pkg,
27772830 .output_mode = fixed_output_mode,
2831 .thread_pool = comp.thread_pool,
27782832 .libc_installation = comp.bin_file.options.libc_installation,
27792833 .emit_bin = emit_bin,
27802834 .optimize_mode = optimize_mode,
......@@ -3148,6 +3202,7 @@ pub fn build_crt_file(
31483202 .root_name = root_name,
31493203 .root_pkg = null,
31503204 .output_mode = output_mode,
3205 .thread_pool = comp.thread_pool,
31513206 .libc_installation = comp.bin_file.options.libc_installation,
31523207 .emit_bin = emit_bin,
31533208 .optimize_mode = comp.bin_file.options.optimize_mode,
src/Event.zig created+43
......@@ -0,0 +1,43 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6const std = @import("std");
7const Event = @This();
8
9lock: std.Mutex = .{},
10event: std.ResetEvent = undefined,
11state: enum { empty, waiting, notified } = .empty,
12
13pub fn wait(self: *Event) void {
14 const held = self.lock.acquire();
15
16 switch (self.state) {
17 .empty => {
18 self.state = .waiting;
19 self.event = @TypeOf(self.event).init();
20 held.release();
21 self.event.wait();
22 self.event.deinit();
23 },
24 .waiting => unreachable,
25 .notified => held.release(),
26 }
27}
28
29pub fn set(self: *Event) void {
30 const held = self.lock.acquire();
31
32 switch (self.state) {
33 .empty => {
34 self.state = .notified;
35 held.release();
36 },
37 .waiting => {
38 held.release();
39 self.event.set();
40 },
41 .notified => unreachable,
42 }
43}
src/ThreadPool.zig created+126
......@@ -0,0 +1,126 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6const std = @import("std");
7const ThreadPool = @This();
8
9lock: std.Mutex = .{},
10is_running: bool = true,
11allocator: *std.mem.Allocator,
12running: usize = 0,
13threads: []*std.Thread,
14run_queue: RunQueue = .{},
15idle_queue: IdleQueue = .{},
16
17const IdleQueue = std.SinglyLinkedList(std.AutoResetEvent);
18const RunQueue = std.SinglyLinkedList(Runnable);
19const Runnable = struct {
20 runFn: fn (*Runnable) void,
21};
22
23pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void {
24 self.* = .{
25 .allocator = allocator,
26 .threads = &[_]*std.Thread{},
27 };
28 if (std.builtin.single_threaded)
29 return;
30
31 errdefer self.deinit();
32
33 var num_threads = std.Thread.cpuCount() catch 1;
34 if (num_threads > 0)
35 self.threads = try allocator.alloc(*std.Thread, num_threads);
36
37 while (num_threads > 0) : (num_threads -= 1) {
38 const thread = try std.Thread.spawn(self, runWorker);
39 self.threads[self.running] = thread;
40 self.running += 1;
41 }
42}
43
44pub fn deinit(self: *ThreadPool) void {
45 self.shutdown();
46
47 std.debug.assert(!self.is_running);
48 for (self.threads[0..self.running]) |thread|
49 thread.wait();
50
51 defer self.threads = &[_]*std.Thread{};
52 if (self.running > 0)
53 self.allocator.free(self.threads);
54}
55
56pub fn shutdown(self: *ThreadPool) void {
57 const held = self.lock.acquire();
58
59 if (!self.is_running)
60 return held.release();
61
62 var idle_queue = self.idle_queue;
63 self.idle_queue = .{};
64 self.is_running = false;
65 held.release();
66
67 while (idle_queue.popFirst()) |idle_node|
68 idle_node.data.set();
69}
70
71pub fn spawn(self: *ThreadPool, comptime func: anytype, args: anytype) !void {
72 if (std.builtin.single_threaded) {
73 @call(.{}, func, args);
74 return;
75 }
76 const Args = @TypeOf(args);
77 const Closure = struct {
78 arguments: Args,
79 pool: *ThreadPool,
80 run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
81
82 fn runFn(runnable: *Runnable) void {
83 const run_node = @fieldParentPtr(RunQueue.Node, "data", runnable);
84 const closure = @fieldParentPtr(@This(), "run_node", run_node);
85 const result = @call(.{}, func, closure.arguments);
86 closure.pool.allocator.destroy(closure);
87 }
88 };
89
90 const closure = try self.allocator.create(Closure);
91 closure.* = .{
92 .arguments = args,
93 .pool = self,
94 };
95
96 const held = self.lock.acquire();
97 self.run_queue.prepend(&closure.run_node);
98
99 const idle_node = self.idle_queue.popFirst();
100 held.release();
101
102 if (idle_node) |node|
103 node.data.set();
104}
105
106fn runWorker(self: *ThreadPool) void {
107 while (true) {
108 const held = self.lock.acquire();
109
110 if (self.run_queue.popFirst()) |run_node| {
111 held.release();
112 (run_node.data.runFn)(&run_node.data);
113 continue;
114 }
115
116 if (!self.is_running) {
117 held.release();
118 return;
119 }
120
121 var idle_node = IdleQueue.Node{ .data = .{} };
122 self.idle_queue.prepend(&idle_node);
123 held.release();
124 idle_node.data.wait();
125 }
126}
src/WaitGroup.zig created+46
......@@ -0,0 +1,46 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6const std = @import("std");
7const WaitGroup = @This();
8const Event = @import("Event.zig");
9
10lock: std.Mutex = .{},
11counter: usize = 0,
12event: ?*Event = null,
13
14pub fn start(self: *WaitGroup) void {
15 const held = self.lock.acquire();
16 defer held.release();
17
18 self.counter += 1;
19}
20
21pub fn stop(self: *WaitGroup) void {
22 var event: ?*Event = null;
23 defer if (event) |waiter|
24 waiter.set();
25
26 const held = self.lock.acquire();
27 defer held.release();
28
29 self.counter -= 1;
30 if (self.counter == 0)
31 std.mem.swap(?*Event, &self.event, &event);
32}
33
34pub fn wait(self: *WaitGroup) void {
35 var event = Event{};
36 var has_event = false;
37 defer if (has_event)
38 event.wait();
39
40 const held = self.lock.acquire();
41 defer held.release();
42
43 has_event = self.counter != 0;
44 if (has_event)
45 self.event = &event;
46}
src/glibc.zig+1
......@@ -936,6 +936,7 @@ fn buildSharedLib(
936936 .root_pkg = null,
937937 .output_mode = .Lib,
938938 .link_mode = .Dynamic,
939 .thread_pool = comp.thread_pool,
939940 .libc_installation = comp.bin_file.options.libc_installation,
940941 .emit_bin = emit_bin,
941942 .optimize_mode = comp.bin_file.options.optimize_mode,
src/libcxx.zig+2
......@@ -162,6 +162,7 @@ pub fn buildLibCXX(comp: *Compilation) !void {
162162 .root_name = root_name,
163163 .root_pkg = null,
164164 .output_mode = output_mode,
165 .thread_pool = comp.thread_pool,
165166 .libc_installation = comp.bin_file.options.libc_installation,
166167 .emit_bin = emit_bin,
167168 .optimize_mode = comp.bin_file.options.optimize_mode,
......@@ -280,6 +281,7 @@ pub fn buildLibCXXABI(comp: *Compilation) !void {
280281 .root_name = root_name,
281282 .root_pkg = null,
282283 .output_mode = output_mode,
284 .thread_pool = comp.thread_pool,
283285 .libc_installation = comp.bin_file.options.libc_installation,
284286 .emit_bin = emit_bin,
285287 .optimize_mode = comp.bin_file.options.optimize_mode,
src/libunwind.zig+1
......@@ -95,6 +95,7 @@ pub fn buildStaticLib(comp: *Compilation) !void {
9595 .root_name = root_name,
9696 .root_pkg = null,
9797 .output_mode = output_mode,
98 .thread_pool = comp.thread_pool,
9899 .libc_installation = comp.bin_file.options.libc_installation,
99100 .emit_bin = emit_bin,
100101 .optimize_mode = comp.bin_file.options.optimize_mode,
src/main.zig+10
......@@ -19,6 +19,7 @@ const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
1919const translate_c = @import("translate_c.zig");
2020const Cache = @import("Cache.zig");
2121const target_util = @import("target.zig");
22const ThreadPool = @import("ThreadPool.zig");
2223
2324pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
2425 std.log.emerg(format, args);
......@@ -1632,6 +1633,10 @@ fn buildOutputType(
16321633 };
16331634 defer zig_lib_directory.handle.close();
16341635
1636 var thread_pool: ThreadPool = undefined;
1637 try thread_pool.init(gpa);
1638 defer thread_pool.deinit();
1639
16351640 var libc_installation: ?LibCInstallation = null;
16361641 defer if (libc_installation) |*l| l.deinit(gpa);
16371642
......@@ -1747,6 +1752,7 @@ fn buildOutputType(
17471752 .single_threaded = single_threaded,
17481753 .function_sections = function_sections,
17491754 .self_exe_path = self_exe_path,
1755 .thread_pool = &thread_pool,
17501756 .clang_passthrough_mode = arg_mode != .build,
17511757 .clang_preprocessor_mode = clang_preprocessor_mode,
17521758 .version = optional_version,
......@@ -2412,6 +2418,9 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
24122418 .directory = null, // Use the local zig-cache.
24132419 .basename = exe_basename,
24142420 };
2421 var thread_pool: ThreadPool = undefined;
2422 try thread_pool.init(gpa);
2423 defer thread_pool.deinit();
24152424 const comp = Compilation.create(gpa, .{
24162425 .zig_lib_directory = zig_lib_directory,
24172426 .local_cache_directory = local_cache_directory,
......@@ -2427,6 +2436,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
24272436 .emit_h = null,
24282437 .optimize_mode = .Debug,
24292438 .self_exe_path = self_exe_path,
2439 .thread_pool = &thread_pool,
24302440 }) catch |err| {
24312441 fatal("unable to create compilation: {}", .{@errorName(err)});
24322442 };
src/musl.zig+1
......@@ -200,6 +200,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
200200 .root_pkg = null,
201201 .output_mode = .Lib,
202202 .link_mode = .Dynamic,
203 .thread_pool = comp.thread_pool,
203204 .libc_installation = comp.bin_file.options.libc_installation,
204205 .emit_bin = Compilation.EmitLoc{ .directory = null, .basename = "libc.so" },
205206 .optimize_mode = comp.bin_file.options.optimize_mode,
src/stage1.zig+4-4
......@@ -293,7 +293,7 @@ export fn stage2_progress_start_root(
293293) *std.Progress.Node {
294294 return progress.start(
295295 name_ptr[0..name_len],
296 if (estimated_total_items == 0) null else estimated_total_items,
296 estimated_total_items,
297297 ) catch @panic("timer unsupported");
298298}
299299
......@@ -312,7 +312,7 @@ export fn stage2_progress_start(
312312 const child_node = std.heap.c_allocator.create(std.Progress.Node) catch @panic("out of memory");
313313 child_node.* = node.start(
314314 name_ptr[0..name_len],
315 if (estimated_total_items == 0) null else estimated_total_items,
315 estimated_total_items,
316316 );
317317 child_node.activate();
318318 return child_node;
......@@ -333,8 +333,8 @@ export fn stage2_progress_complete_one(node: *std.Progress.Node) void {
333333
334334// ABI warning
335335export fn stage2_progress_update_node(node: *std.Progress.Node, done_count: usize, total_count: usize) void {
336 node.completed_items = done_count;
337 node.estimated_total_items = total_count;
336 node.setCompletedItems(done_count);
337 node.setEstimatedTotalItems(total_count);
338338 node.activate();
339339 node.context.maybeRefresh();
340340}
src/stage1/zig0.cpp+4
......@@ -266,6 +266,7 @@ int main(int argc, char **argv) {
266266 TargetSubsystem subsystem = TargetSubsystemAuto;
267267 const char *override_lib_dir = nullptr;
268268 const char *mcpu = nullptr;
269 bool single_threaded = false;
269270
270271 for (int i = 1; i < argc; i += 1) {
271272 char *arg = argv[i];
......@@ -281,6 +282,8 @@ int main(int argc, char **argv) {
281282 optimize_mode = BuildModeSafeRelease;
282283 } else if (strcmp(arg, "-OReleaseSmall") == 0) {
283284 optimize_mode = BuildModeSmallRelease;
285 } else if (strcmp(arg, "--single-threaded") == 0) {
286 single_threaded = true;
284287 } else if (strcmp(arg, "--help") == 0) {
285288 return print_full_usage(arg0, stdout, EXIT_SUCCESS);
286289 } else if (strcmp(arg, "--strip") == 0) {
......@@ -469,6 +472,7 @@ int main(int argc, char **argv) {
469472 stage1->link_libcpp = link_libcpp;
470473 stage1->subsystem = subsystem;
471474 stage1->pic = true;
475 stage1->is_single_threaded = single_threaded;
472476
473477 zig_stage1_build_object(stage1);
474478
src/test.zig+26-13
......@@ -10,6 +10,7 @@ const enable_qemu: bool = build_options.enable_qemu;
1010const enable_wine: bool = build_options.enable_wine;
1111const enable_wasmtime: bool = build_options.enable_wasmtime;
1212const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_dir;
13const ThreadPool = @import("ThreadPool.zig");
1314
1415const cheader = @embedFile("link/cbe.h");
1516
......@@ -467,6 +468,10 @@ pub const TestContext = struct {
467468 defer zig_lib_directory.handle.close();
468469 defer std.testing.allocator.free(zig_lib_directory.path.?);
469470
471 var thread_pool: ThreadPool = undefined;
472 try thread_pool.init(std.testing.allocator);
473 defer thread_pool.deinit();
474
470475 for (self.cases.items) |case| {
471476 if (build_options.skip_non_native and case.target.getCpuArch() != std.Target.current.cpu.arch)
472477 continue;
......@@ -480,7 +485,13 @@ pub const TestContext = struct {
480485 progress.initial_delay_ns = 0;
481486 progress.refresh_rate_ns = 0;
482487
483 try self.runOneCase(std.testing.allocator, &prg_node, case, zig_lib_directory);
488 try self.runOneCase(
489 std.testing.allocator,
490 &prg_node,
491 case,
492 zig_lib_directory,
493 &thread_pool,
494 );
484495 }
485496 }
486497
......@@ -490,6 +501,7 @@ pub const TestContext = struct {
490501 root_node: *std.Progress.Node,
491502 case: Case,
492503 zig_lib_directory: Compilation.Directory,
504 thread_pool: *ThreadPool,
493505 ) !void {
494506 const target_info = try std.zig.system.NativeTargetInfo.detect(allocator, case.target);
495507 const target = target_info.target;
......@@ -539,6 +551,7 @@ pub const TestContext = struct {
539551 .local_cache_directory = zig_cache_directory,
540552 .global_cache_directory = zig_cache_directory,
541553 .zig_lib_directory = zig_lib_directory,
554 .thread_pool = thread_pool,
542555 .root_name = "test_case",
543556 .target = target,
544557 // TODO: support tests for object file building, and library builds
......@@ -565,12 +578,12 @@ pub const TestContext = struct {
565578 update_node.activate();
566579 defer update_node.end();
567580
568 var sync_node = update_node.start("write", null);
581 var sync_node = update_node.start("write", 0);
569582 sync_node.activate();
570583 try tmp.dir.writeFile(tmp_src_path, update.src);
571584 sync_node.end();
572585
573 var module_node = update_node.start("parse/analysis/codegen", null);
586 var module_node = update_node.start("parse/analysis/codegen", 0);
574587 module_node.activate();
575588 try comp.makeBinFileWritable();
576589 try comp.update();
......@@ -622,21 +635,21 @@ pub const TestContext = struct {
622635 }
623636 }
624637 } else {
625 update_node.estimated_total_items = 5;
626 var emit_node = update_node.start("emit", null);
638 update_node.setEstimatedTotalItems(5);
639 var emit_node = update_node.start("emit", 0);
627640 emit_node.activate();
628641 var new_zir_module = try zir.emit(allocator, comp.bin_file.options.module.?);
629642 defer new_zir_module.deinit(allocator);
630643 emit_node.end();
631644
632 var write_node = update_node.start("write", null);
645 var write_node = update_node.start("write", 0);
633646 write_node.activate();
634647 var out_zir = std.ArrayList(u8).init(allocator);
635648 defer out_zir.deinit();
636649 try new_zir_module.writeToStream(allocator, out_zir.outStream());
637650 write_node.end();
638651
639 var test_node = update_node.start("assert", null);
652 var test_node = update_node.start("assert", 0);
640653 test_node.activate();
641654 defer test_node.end();
642655
......@@ -653,7 +666,7 @@ pub const TestContext = struct {
653666 }
654667 },
655668 .Error => |e| {
656 var test_node = update_node.start("assert", null);
669 var test_node = update_node.start("assert", 0);
657670 test_node.activate();
658671 defer test_node.end();
659672 var handled_errors = try arena.alloc(bool, e.len);
......@@ -710,9 +723,9 @@ pub const TestContext = struct {
710723 .Execution => |expected_stdout| {
711724 std.debug.assert(!case.cbe);
712725
713 update_node.estimated_total_items = 4;
726 update_node.setEstimatedTotalItems(4);
714727 var exec_result = x: {
715 var exec_node = update_node.start("execute", null);
728 var exec_node = update_node.start("execute", 0);
716729 exec_node.activate();
717730 defer exec_node.end();
718731
......@@ -775,7 +788,7 @@ pub const TestContext = struct {
775788 .cwd_dir = tmp.dir,
776789 });
777790 };
778 var test_node = update_node.start("test", null);
791 var test_node = update_node.start("test", 0);
779792 test_node.activate();
780793 defer test_node.end();
781794 defer allocator.free(exec_result.stdout);
......@@ -854,7 +867,7 @@ pub const TestContext = struct {
854867 };
855868
856869 {
857 var load_node = update_node.start("load", null);
870 var load_node = update_node.start("load", 0);
858871 load_node.activate();
859872 defer load_node.end();
860873
......@@ -892,7 +905,7 @@ pub const TestContext = struct {
892905 }
893906 }
894907
895 var exec_node = update_node.start("execute", null);
908 var exec_node = update_node.start("execute", 0);
896909 exec_node.activate();
897910 defer exec_node.end();
898911