authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-07-04 22:31:02-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-07-04 22:31:02-04:00
logb7da1b2d45bc42a56eea3a143e4237a0712c4769
tree5474938657d5dfd9273562c160ad5f1e3a02b824
parent5d0dad9acdac854d68e1447b90fd3dbde9ff0b2d
parentc8f90a7e7e10be62634454bf124bef3c6130a0db
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #9175 from kprotty/thread

std.Thread enhancements

19 files changed, 898 insertions(+), 682 deletions(-)

doc/langref.html.in+6-6
......@@ -958,14 +958,14 @@ const assert = std.debug.assert;
958958threadlocal var x: i32 = 1234;
959959
960960test "thread local storage" {
961 const thread1 = try std.Thread.spawn(testTls, {});
962 const thread2 = try std.Thread.spawn(testTls, {});
963 testTls({});
964 thread1.wait();
965 thread2.wait();
961 const thread1 = try std.Thread.spawn(.{}, testTls, .{});
962 const thread2 = try std.Thread.spawn(.{}, testTls, .{});
963 testTls();
964 thread1.join();
965 thread2.join();
966966}
967967
968fn testTls(_: void) void {
968fn testTls() void {
969969 assert(x == 1234);
970970 x += 1;
971971 assert(x == 1235);
lib/std/Thread.zig+662-444
......@@ -8,7 +8,11 @@
88//! primitives that operate on kernel threads. For concurrency primitives that support
99//! both evented I/O and async I/O, see the respective names in the top level std namespace.
1010
11data: Data,
11const std = @import("std.zig");
12const os = std.os;
13const assert = std.debug.assert;
14const target = std.Target.current;
15const Atomic = std.atomic.Atomic;
1216
1317pub const AutoResetEvent = @import("Thread/AutoResetEvent.zig");
1418pub const Futex = @import("Thread/Futex.zig");
......@@ -18,117 +22,51 @@ pub const Mutex = @import("Thread/Mutex.zig");
1822pub const Semaphore = @import("Thread/Semaphore.zig");
1923pub const Condition = @import("Thread/Condition.zig");
2024
21pub const use_pthreads = std.Target.current.os.tag != .windows and builtin.link_libc;
25pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint");
2226
23const Thread = @This();
24const std = @import("std.zig");
25const builtin = std.builtin;
26const os = std.os;
27const mem = std.mem;
28const windows = std.os.windows;
29const c = std.c;
30const assert = std.debug.assert;
27pub const use_pthreads = target.os.tag != .windows and std.builtin.link_libc;
3128
32const bad_startfn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";
29const Thread = @This();
30const Impl = if (target.os.tag == .windows)
31 WindowsThreadImpl
32else if (use_pthreads)
33 PosixThreadImpl
34else if (target.os.tag == .linux)
35 LinuxThreadImpl
36else
37 UnsupportedImpl;
3338
34/// Represents a kernel thread handle.
35/// May be an integer or a pointer depending on the platform.
36/// On Linux and POSIX, this is the same as Id.
37pub const Handle = if (use_pthreads)
38 c.pthread_t
39else switch (std.Target.current.os.tag) {
40 .linux => i32,
41 .windows => windows.HANDLE,
42 else => void,
43};
39impl: Impl,
4440
4541/// Represents a unique ID per thread.
46/// May be an integer or pointer depending on the platform.
47/// On Linux and POSIX, this is the same as Handle.
48pub const Id = switch (std.Target.current.os.tag) {
49 .windows => windows.DWORD,
50 else => Handle,
51};
42pub const Id = u64;
5243
53pub const Data = if (use_pthreads)
54 struct {
55 handle: Thread.Handle,
56 memory: []u8,
57 }
58else switch (std.Target.current.os.tag) {
59 .linux => struct {
60 handle: Thread.Handle,
61 memory: []align(mem.page_size) u8,
62 },
63 .windows => struct {
64 handle: Thread.Handle,
65 alloc_start: *c_void,
66 heap_handle: windows.HANDLE,
67 },
68 else => struct {},
69};
70
71pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint");
72
73/// Returns the ID of the calling thread.
74/// Makes a syscall every time the function is called.
75/// On Linux and POSIX, this Id is the same as a Handle.
44/// Returns the platform ID of the callers thread.
45/// Attempts to use thread locals and avoid syscalls when possible.
7646pub fn getCurrentId() Id {
77 if (use_pthreads) {
78 return c.pthread_self();
79 } else return switch (std.Target.current.os.tag) {
80 .linux => os.linux.gettid(),
81 .windows => windows.kernel32.GetCurrentThreadId(),
82 else => @compileError("Unsupported OS"),
83 };
47 return Impl.getCurrentId();
8448}
8549
86/// Returns the handle of this thread.
87/// On Linux and POSIX, this is the same as Id.
88/// On Linux, it is possible that the thread spawned with `spawn`
89/// finishes executing entirely before the clone syscall completes. In this
90/// case, this function will return 0 rather than the no-longer-existing thread's
91/// pid.
92pub fn handle(self: Thread) Handle {
93 return self.data.handle;
94}
50pub const CpuCountError = error{
51 PermissionDenied,
52 SystemResources,
53 Unexpected,
54};
9555
96pub fn wait(self: *Thread) void {
97 if (use_pthreads) {
98 const err = c.pthread_join(self.data.handle, null);
99 switch (err) {
100 0 => {},
101 os.EINVAL => unreachable,
102 os.ESRCH => unreachable,
103 os.EDEADLK => unreachable,
104 else => unreachable,
105 }
106 std.heap.c_allocator.free(self.data.memory);
107 std.heap.c_allocator.destroy(self);
108 } else switch (std.Target.current.os.tag) {
109 .linux => {
110 while (true) {
111 const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst);
112 if (pid_value == 0) break;
113 const rc = os.linux.futex_wait(&self.data.handle, os.linux.FUTEX_WAIT, pid_value, null);
114 switch (os.linux.getErrno(rc)) {
115 0 => continue,
116 os.EINTR => continue,
117 os.EAGAIN => continue,
118 else => unreachable,
119 }
120 }
121 os.munmap(self.data.memory);
122 },
123 .windows => {
124 windows.WaitForSingleObjectEx(self.data.handle, windows.INFINITE, false) catch unreachable;
125 windows.CloseHandle(self.data.handle);
126 windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start);
127 },
128 else => @compileError("Unsupported OS"),
129 }
56/// Returns the platforms view on the number of logical CPU cores available.
57pub fn getCpuCount() CpuCountError!usize {
58 return Impl.getCpuCount();
13059}
13160
61/// Configuration options for hints on how to spawn threads.
62pub const SpawnConfig = struct {
63 // TODO compile-time call graph analysis to determine stack upper bound
64 // https://github.com/ziglang/zig/issues/157
65
66 /// Size in bytes of the Thread's stack
67 stack_size: usize = 16 * 1024 * 1024,
68};
69
13270pub const SpawnError = error{
13371 /// A system-imposed limit on the number of threads was encountered.
13472 /// There are a number of limits that may trigger this error:
......@@ -159,248 +97,552 @@ pub const SpawnError = error{
15997 Unexpected,
16098};
16199
162// Given `T`, the type of the thread startFn, extract the expected type for the
163// context parameter.
164fn SpawnContextType(comptime T: type) type {
165 const TI = @typeInfo(T);
166 if (TI != .Fn)
167 @compileError("expected function type, found " ++ @typeName(T));
100/// Spawns a new thread which executes `function` using `args` and returns a handle the spawned thread.
101/// `config` can be used as hints to the platform for now to spawn and execute the `function`.
102/// The caller must eventually either call `join()` to wait for the thread to finish and free its resources
103/// or call `detach()` to excuse the caller from calling `join()` and have the thread clean up its resources on completion`.
104pub fn spawn(config: SpawnConfig, comptime function: anytype, args: anytype) SpawnError!Thread {
105 if (std.builtin.single_threaded) {
106 @compileError("Cannot spawn thread when building in single-threaded mode");
107 }
108
109 const impl = try Impl.spawn(config, function, args);
110 return Thread{ .impl = impl };
111}
168112
169 if (TI.Fn.args.len != 1)
170 @compileError("expected function with single argument, found " ++ @typeName(T));
113/// Represents a kernel thread handle.
114/// May be an integer or a pointer depending on the platform.
115pub const Handle = Impl.ThreadHandle;
171116
172 return TI.Fn.args[0].arg_type orelse
173 @compileError("cannot use a generic function as thread startFn");
117/// Retrns the handle of this thread
118pub fn getHandle(self: Thread) Handle {
119 return self.impl.getHandle();
174120}
175121
176/// Spawns a new thread executing startFn, returning an handle for it.
177/// Caller must call wait on the returned thread.
178/// The `startFn` function must take a single argument of type T and return a
179/// value of type u8, noreturn, void or !void.
180/// The `context` parameter is of type T and is passed to the spawned thread.
181pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startFn))) SpawnError!*Thread {
182 if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode");
183 // TODO compile-time call graph analysis to determine stack upper bound
184 // https://github.com/ziglang/zig/issues/157
185 const default_stack_size = 16 * 1024 * 1024;
122/// Release the obligation of the caller to call `join()` and have the thread clean up its own resources on completion.
123/// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior.
124pub fn detach(self: Thread) void {
125 return self.impl.detach();
126}
186127
187 const Context = @TypeOf(context);
128/// Waits for the thread to complete, then deallocates any resources created on `spawn()`.
129/// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior.
130pub fn join(self: Thread) void {
131 return self.impl.join();
132}
188133
189 if (std.Target.current.os.tag == .windows) {
190 const WinThread = struct {
191 const OuterContext = struct {
192 thread: Thread,
193 inner: Context,
194 };
195 fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD {
196 const arg = if (@sizeOf(Context) == 0) undefined //
197 else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*;
198
199 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {
200 .NoReturn => {
201 startFn(arg);
202 },
203 .Void => {
204 startFn(arg);
205 return 0;
206 },
207 .Int => |info| {
208 if (info.bits != 8) {
209 @compileError(bad_startfn_ret);
210 }
211 return startFn(arg);
212 },
213 .ErrorUnion => |info| {
214 if (info.payload != void) {
215 @compileError(bad_startfn_ret);
216 }
217 startFn(arg) catch |err| {
218 std.debug.warn("error: {s}\n", .{@errorName(err)});
219 if (@errorReturnTrace()) |trace| {
220 std.debug.dumpStackTrace(trace.*);
221 }
222 };
223 return 0;
224 },
225 else => @compileError(bad_startfn_ret),
134/// State to synchronize detachment of spawner thread to spawned thread
135const Completion = Atomic(enum(u8) {
136 running,
137 detached,
138 completed,
139});
140
141/// Used by the Thread implementations to call the spawned function with the arguments.
142fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
143 WindowsThreadImpl => std.os.windows.DWORD,
144 LinuxThreadImpl => u8,
145 PosixThreadImpl => ?*c_void,
146 else => unreachable,
147} {
148 const default_value = if (Impl == PosixThreadImpl) null else 0;
149 const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";
150
151 switch (@typeInfo(@typeInfo(@TypeOf(f)).Fn.return_type.?)) {
152 .NoReturn => {
153 @call(.{}, f, args);
154 },
155 .Void => {
156 @call(.{}, f, args);
157 return default_value;
158 },
159 .Int => |info| {
160 if (info.bits != 8) {
161 @compileError(bad_fn_ret);
162 }
163
164 const status = @call(.{}, f, args);
165 if (Impl != PosixThreadImpl) {
166 return status;
167 }
168
169 // pthreads don't support exit status, ignore value
170 _ = status;
171 return default_value;
172 },
173 .ErrorUnion => |info| {
174 if (info.payload != void) {
175 @compileError(bad_fn_ret);
176 }
177
178 @call(.{}, f, args) catch |err| {
179 std.debug.warn("error: {s}\n", .{@errorName(err)});
180 if (@errorReturnTrace()) |trace| {
181 std.debug.dumpStackTrace(trace.*);
226182 }
183 };
184
185 return default_value;
186 },
187 else => {
188 @compileError(bad_fn_ret);
189 },
190 }
191}
192
193/// We can't compile error in the `Impl` switch statement as its eagerly evaluated.
194/// So instead, we compile-error on the methods themselves for platforms which don't support threads.
195const UnsupportedImpl = struct {
196 pub const ThreadHandle = void;
197
198 fn getCurrentId() u64 {
199 return unsupported({});
200 }
201
202 fn getCpuCount() !usize {
203 return unsupported({});
204 }
205
206 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
207 return unsupported(.{ config, f, args });
208 }
209
210 fn getHandle(self: Impl) ThreadHandle {
211 return unsupported(self);
212 }
213
214 fn detach(self: Impl) void {
215 return unsupported(self);
216 }
217
218 fn join(self: Impl) void {
219 return unsupported(self);
220 }
221
222 fn unsupported(unusued: anytype) noreturn {
223 @compileLog("Unsupported operating system", target.os.tag);
224 _ = unusued;
225 unreachable;
226 }
227};
228
229const WindowsThreadImpl = struct {
230 const windows = os.windows;
231
232 pub const ThreadHandle = windows.HANDLE;
233
234 fn getCurrentId() u64 {
235 return windows.kernel32.GetCurrentThreadId();
236 }
237
238 fn getCpuCount() !usize {
239 // Faster than calling into GetSystemInfo(), even if amortized.
240 return windows.peb().NumberOfProcessors;
241 }
242
243 thread: *ThreadCompletion,
244
245 const ThreadCompletion = struct {
246 completion: Completion,
247 heap_ptr: windows.PVOID,
248 heap_handle: windows.HANDLE,
249 thread_handle: windows.HANDLE = undefined,
250
251 fn free(self: ThreadCompletion) void {
252 const status = windows.kernel32.HeapFree(self.heap_handle, 0, self.heap_ptr);
253 assert(status != 0);
254 }
255 };
256
257 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
258 const Args = @TypeOf(args);
259 const Instance = struct {
260 fn_args: Args,
261 thread: ThreadCompletion,
262
263 fn entryFn(raw_ptr: windows.PVOID) callconv(.C) windows.DWORD {
264 const self = @ptrCast(*@This(), @alignCast(@alignOf(@This()), raw_ptr));
265 defer switch (self.thread.completion.swap(.completed, .SeqCst)) {
266 .running => {},
267 .completed => unreachable,
268 .detached => self.thread.free(),
269 };
270 return callFn(f, self.fn_args);
227271 }
228272 };
229273
230274 const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory;
231 const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext);
232 const bytes_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory;
233 errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, bytes_ptr) != 0);
234 const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count];
235 const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable;
236 outer_context.* = WinThread.OuterContext{
237 .thread = Thread{
238 .data = Thread.Data{
239 .heap_handle = heap_handle,
240 .alloc_start = bytes_ptr,
241 .handle = undefined,
242 },
275 const alloc_bytes = @alignOf(Instance) + @sizeOf(Instance);
276 const alloc_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, alloc_bytes) orelse return error.OutOfMemory;
277 errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, alloc_ptr) != 0);
278
279 const instance_bytes = @ptrCast([*]u8, alloc_ptr)[0..alloc_bytes];
280 const instance = std.heap.FixedBufferAllocator.init(instance_bytes).allocator.create(Instance) catch unreachable;
281 instance.* = .{
282 .fn_args = args,
283 .thread = .{
284 .completion = Completion.init(.running),
285 .heap_ptr = alloc_ptr,
286 .heap_handle = heap_handle,
243287 },
244 .inner = context,
245288 };
246289
247 const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner);
248 outer_context.thread.data.handle = windows.kernel32.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse {
249 switch (windows.kernel32.GetLastError()) {
250 else => |err| return windows.unexpectedError(err),
251 }
290 // Windows appears to only support SYSTEM_INFO.dwAllocationGranularity minimum stack size.
291 // Going lower makes it default to that specified in the executable (~1mb).
292 // Its also fine if the limit here is incorrect as stack size is only a hint.
293 var stack_size = std.math.cast(u32, config.stack_size) catch std.math.maxInt(u32);
294 stack_size = std.math.max(64 * 1024, stack_size);
295
296 instance.thread.thread_handle = windows.kernel32.CreateThread(
297 null,
298 stack_size,
299 Instance.entryFn,
300 @ptrCast(*c_void, instance),
301 0,
302 null,
303 ) orelse {
304 const errno = windows.kernel32.GetLastError();
305 return windows.unexpectedError(errno);
252306 };
253 return &outer_context.thread;
307
308 return Impl{ .thread = &instance.thread };
254309 }
255310
256 const MainFuncs = struct {
257 fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 {
258 const arg = if (@sizeOf(Context) == 0) undefined //
259 else @intToPtr(*Context, ctx_addr).*;
311 fn getHandle(self: Impl) ThreadHandle {
312 return self.thread.thread_handle;
313 }
260314
261 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {
262 .NoReturn => {
263 startFn(arg);
264 },
265 .Void => {
266 startFn(arg);
267 return 0;
268 },
269 .Int => |info| {
270 if (info.bits != 8) {
271 @compileError(bad_startfn_ret);
272 }
273 return startFn(arg);
274 },
275 .ErrorUnion => |info| {
276 if (info.payload != void) {
277 @compileError(bad_startfn_ret);
278 }
279 startFn(arg) catch |err| {
280 std.debug.warn("error: {s}\n", .{@errorName(err)});
281 if (@errorReturnTrace()) |trace| {
282 std.debug.dumpStackTrace(trace.*);
283 }
284 };
285 return 0;
286 },
287 else => @compileError(bad_startfn_ret),
288 }
315 fn detach(self: Impl) void {
316 windows.CloseHandle(self.thread.thread_handle);
317 switch (self.thread.completion.swap(.detached, .SeqCst)) {
318 .running => {},
319 .completed => self.thread.free(),
320 .detached => unreachable,
289321 }
290 fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void {
291 const arg = if (@sizeOf(Context) == 0) undefined //
292 else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*;
322 }
293323
294 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {
295 .NoReturn => {
296 startFn(arg);
297 },
298 .Void => {
299 startFn(arg);
300 return null;
301 },
302 .Int => |info| {
303 if (info.bits != 8) {
304 @compileError(bad_startfn_ret);
305 }
306 // pthreads don't support exit status, ignore value
307 _ = startFn(arg);
308 return null;
309 },
310 .ErrorUnion => |info| {
311 if (info.payload != void) {
312 @compileError(bad_startfn_ret);
313 }
314 startFn(arg) catch |err| {
315 std.debug.warn("error: {s}\n", .{@errorName(err)});
316 if (@errorReturnTrace()) |trace| {
317 std.debug.dumpStackTrace(trace.*);
318 }
319 };
320 return null;
321 },
322 else => @compileError(bad_startfn_ret),
323 }
324 fn join(self: Impl) void {
325 windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable;
326 windows.CloseHandle(self.thread.thread_handle);
327 assert(self.thread.completion.load(.SeqCst) == .completed);
328 self.thread.free();
329 }
330};
331
332const PosixThreadImpl = struct {
333 const c = std.c;
334
335 pub const ThreadHandle = c.pthread_t;
336
337 fn getCurrentId() Id {
338 switch (target.os.tag) {
339 .linux => {
340 return LinuxThreadImpl.getCurrentId();
341 },
342 .macos, .ios, .watchos, .tvos => {
343 var thread_id: u64 = undefined;
344 // Pass thread=null to get the current thread ID.
345 assert(c.pthread_threadid_np(null, &thread_id) == 0);
346 return thread_id;
347 },
348 .dragonfly => {
349 return @bitCast(u32, c.lwp_gettid());
350 },
351 .netbsd => {
352 return @bitCast(u32, c._lwp_self());
353 },
354 .freebsd => {
355 return @bitCast(u32, c.pthread_getthreadid_np());
356 },
357 .openbsd => {
358 return @bitCast(u32, c.getthrid());
359 },
360 .haiku => {
361 return @bitCast(u32, c.find_thread(null));
362 },
363 else => {
364 return @ptrToInt(c.pthread_self());
365 },
324366 }
325 };
367 }
368
369 fn getCpuCount() !usize {
370 switch (target.os.tag) {
371 .linux => {
372 return LinuxThreadImpl.getCpuCount();
373 },
374 .openbsd => {
375 var count: c_int = undefined;
376 var count_size: usize = @sizeOf(c_int);
377 const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE };
378 os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) {
379 error.NameTooLong, error.UnknownName => unreachable,
380 else => |e| return e,
381 };
382 return @intCast(usize, count);
383 },
384 .haiku => {
385 var count: u32 = undefined;
386 var system_info: os.system_info = undefined;
387 _ = os.system.get_system_info(&system_info); // always returns B_OK
388 count = system_info.cpu_count;
389 return @intCast(usize, count);
390 },
391 else => {
392 var count: c_int = undefined;
393 var count_len: usize = @sizeOf(c_int);
394 const name = if (comptime target.isDarwin()) "hw.logicalcpu" else "hw.ncpu";
395 os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) {
396 error.NameTooLong, error.UnknownName => unreachable,
397 else => |e| return e,
398 };
399 return @intCast(usize, count);
400 },
401 }
402 }
403
404 handle: ThreadHandle,
405
406 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
407 const Args = @TypeOf(args);
408 const allocator = std.heap.c_allocator;
409
410 const Instance = struct {
411 fn entryFn(raw_arg: ?*c_void) callconv(.C) ?*c_void {
412 // @alignCast() below doesn't support zero-sized-types (ZST)
413 if (@sizeOf(Args) < 1) {
414 return callFn(f, @as(Args, undefined));
415 }
416
417 const args_ptr = @ptrCast(*Args, @alignCast(@alignOf(Args), raw_arg));
418 defer allocator.destroy(args_ptr);
419 return callFn(f, args_ptr.*);
420 }
421 };
422
423 const args_ptr = try allocator.create(Args);
424 args_ptr.* = args;
425 errdefer allocator.destroy(args_ptr);
326426
327 if (Thread.use_pthreads) {
328427 var attr: c.pthread_attr_t = undefined;
329428 if (c.pthread_attr_init(&attr) != 0) return error.SystemResources;
330429 defer assert(c.pthread_attr_destroy(&attr) == 0);
331430
332 const thread_obj = try std.heap.c_allocator.create(Thread);
333 errdefer std.heap.c_allocator.destroy(thread_obj);
334 if (@sizeOf(Context) > 0) {
335 thread_obj.data.memory = try std.heap.c_allocator.allocAdvanced(
336 u8,
337 @alignOf(Context),
338 @sizeOf(Context),
339 .at_least,
340 );
341 errdefer std.heap.c_allocator.free(thread_obj.data.memory);
342 mem.copy(u8, thread_obj.data.memory, mem.asBytes(&context));
343 } else {
344 thread_obj.data.memory = @as([*]u8, undefined)[0..0];
345 }
346
347431 // Use the same set of parameters used by the libc-less impl.
348 assert(c.pthread_attr_setstacksize(&attr, default_stack_size) == 0);
349 assert(c.pthread_attr_setguardsize(&attr, mem.page_size) == 0);
432 const stack_size = std.math.max(config.stack_size, 16 * 1024);
433 assert(c.pthread_attr_setstacksize(&attr, stack_size) == 0);
434 assert(c.pthread_attr_setguardsize(&attr, std.mem.page_size) == 0);
350435
351 const err = c.pthread_create(
352 &thread_obj.data.handle,
436 var handle: c.pthread_t = undefined;
437 switch (c.pthread_create(
438 &handle,
353439 &attr,
354 MainFuncs.posixThreadMain,
355 thread_obj.data.memory.ptr,
356 );
357 switch (err) {
358 0 => return thread_obj,
440 Instance.entryFn,
441 if (@sizeOf(Args) > 1) @ptrCast(*c_void, args_ptr) else undefined,
442 )) {
443 0 => return Impl{ .handle = handle },
359444 os.EAGAIN => return error.SystemResources,
360445 os.EPERM => unreachable,
361446 os.EINVAL => unreachable,
362 else => return os.unexpectedErrno(err),
447 else => |err| return os.unexpectedErrno(err),
448 }
449 }
450
451 fn getHandle(self: Impl) ThreadHandle {
452 return self.handle;
453 }
454
455 fn detach(self: Impl) void {
456 switch (c.pthread_detach(self.handle)) {
457 0 => {},
458 os.EINVAL => unreachable, // thread handle is not joinable
459 os.ESRCH => unreachable, // thread handle is invalid
460 else => unreachable,
461 }
462 }
463
464 fn join(self: Impl) void {
465 switch (c.pthread_join(self.handle, null)) {
466 0 => {},
467 os.EINVAL => unreachable, // thread handle is not joinable (or another thread is already joining in)
468 os.ESRCH => unreachable, // thread handle is invalid
469 os.EDEADLK => unreachable, // two threads tried to join each other
470 else => unreachable,
363471 }
472 }
473};
474
475const LinuxThreadImpl = struct {
476 const linux = os.linux;
364477
365 return thread_obj;
478 pub const ThreadHandle = i32;
479
480 threadlocal var tls_thread_id: ?Id = null;
481
482 fn getCurrentId() Id {
483 return tls_thread_id orelse {
484 const tid = @bitCast(u32, linux.gettid());
485 tls_thread_id = tid;
486 return tid;
487 };
366488 }
367489
368 var guard_end_offset: usize = undefined;
369 var stack_end_offset: usize = undefined;
370 var thread_start_offset: usize = undefined;
371 var context_start_offset: usize = undefined;
372 var tls_start_offset: usize = undefined;
373 const mmap_len = blk: {
374 var l: usize = mem.page_size;
375 // Allocate a guard page right after the end of the stack region
376 guard_end_offset = l;
377 // The stack itself, which grows downwards.
378 l = mem.alignForward(l + default_stack_size, mem.page_size);
379 stack_end_offset = l;
380 // Above the stack, so that it can be in the same mmap call, put the Thread object.
381 l = mem.alignForward(l, @alignOf(Thread));
382 thread_start_offset = l;
383 l += @sizeOf(Thread);
384 // Next, the Context object.
385 if (@sizeOf(Context) != 0) {
386 l = mem.alignForward(l, @alignOf(Context));
387 context_start_offset = l;
388 l += @sizeOf(Context);
490 fn getCpuCount() !usize {
491 const cpu_set = try os.sched_getaffinity(0);
492 // TODO: should not need this usize cast
493 return @as(usize, os.CPU_COUNT(cpu_set));
494 }
495
496 thread: *ThreadCompletion,
497
498 const ThreadCompletion = struct {
499 completion: Completion = Completion.init(.running),
500 child_tid: Atomic(i32) = Atomic(i32).init(1),
501 parent_tid: i32 = undefined,
502 mapped: []align(std.mem.page_size) u8,
503
504 /// Calls `munmap(mapped.ptr, mapped.len)` then `exit(1)` without touching the stack (which lives in `mapped.ptr`).
505 /// Ported over from musl libc's pthread detached implementation:
506 /// https://github.com/ifduyue/musl/search?q=__unmapself
507 fn freeAndExit(self: *ThreadCompletion) noreturn {
508 const unmap_and_exit: []const u8 = switch (target.cpu.arch) {
509 .i386 => (
510 \\ movl $91, %%eax
511 \\ movl %[ptr], %%ebx
512 \\ movl %[len], %%ecx
513 \\ int $128
514 \\ movl $1, %%eax
515 \\ movl $0, %%ebx
516 \\ int $128
517 ),
518 .x86_64 => (
519 \\ movq $11, %%rax
520 \\ movq %[ptr], %%rbx
521 \\ movq %[len], %%rcx
522 \\ syscall
523 \\ movq $60, %%rax
524 \\ movq $1, %%rdi
525 \\ syscall
526 ),
527 .arm, .armeb, .thumb, .thumbeb => (
528 \\ mov r7, #91
529 \\ mov r0, %[ptr]
530 \\ mov r1, %[len]
531 \\ svc 0
532 \\ mov r7, #1
533 \\ mov r0, #0
534 \\ svc 0
535 ),
536 .aarch64, .aarch64_be, .aarch64_32 => (
537 \\ mov x8, #215
538 \\ mov x0, %[ptr]
539 \\ mov x1, %[len]
540 \\ svc 0
541 \\ mov x8, #93
542 \\ mov x0, #0
543 \\ svc 0
544 ),
545 .mips, .mipsel => (
546 \\ move $sp, $25
547 \\ li $2, 4091
548 \\ move $4, %[ptr]
549 \\ move $5, %[len]
550 \\ syscall
551 \\ li $2, 4001
552 \\ li $4, 0
553 \\ syscall
554 ),
555 .mips64, .mips64el => (
556 \\ li $2, 4091
557 \\ move $4, %[ptr]
558 \\ move $5, %[len]
559 \\ syscall
560 \\ li $2, 4001
561 \\ li $4, 0
562 \\ syscall
563 ),
564 .powerpc, .powerpcle, .powerpc64, .powerpc64le => (
565 \\ li 0, 91
566 \\ mr %[ptr], 3
567 \\ mr %[len], 4
568 \\ sc
569 \\ li 0, 1
570 \\ li 3, 0
571 \\ sc
572 \\ blr
573 ),
574 .riscv64 => (
575 \\ li a7, 215
576 \\ mv a0, %[ptr]
577 \\ mv a1, %[len]
578 \\ ecall
579 \\ li a7, 93
580 \\ mv a0, zero
581 \\ ecall
582 ),
583 else => |cpu_arch| {
584 @compileLog("Unsupported linux arch ", cpu_arch);
585 },
586 };
587
588 asm volatile (unmap_and_exit
589 :
590 : [ptr] "r" (@ptrToInt(self.mapped.ptr)),
591 [len] "r" (self.mapped.len)
592 : "memory"
593 );
594
595 unreachable;
389596 }
390 // Finally, the Thread Local Storage, if any.
391 l = mem.alignForward(l, os.linux.tls.tls_image.alloc_align);
392 tls_start_offset = l;
393 l += os.linux.tls.tls_image.alloc_size;
394 // Round the size to the page size.
395 break :blk mem.alignForward(l, mem.page_size);
396597 };
397598
398 const mmap_slice = mem: {
399 // Map the whole stack with no rw permissions to avoid
400 // committing the whole region right away
401 const mmap_slice = os.mmap(
599 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
600 const Args = @TypeOf(args);
601 const Instance = struct {
602 fn_args: Args,
603 thread: ThreadCompletion,
604
605 fn entryFn(raw_arg: usize) callconv(.C) u8 {
606 const self = @intToPtr(*@This(), raw_arg);
607 defer switch (self.thread.completion.swap(.completed, .SeqCst)) {
608 .running => {},
609 .completed => unreachable,
610 .detached => self.thread.freeAndExit(),
611 };
612 return callFn(f, self.fn_args);
613 }
614 };
615
616 var guard_offset: usize = undefined;
617 var stack_offset: usize = undefined;
618 var tls_offset: usize = undefined;
619 var instance_offset: usize = undefined;
620
621 const map_bytes = blk: {
622 var bytes: usize = std.mem.page_size;
623 guard_offset = bytes;
624
625 bytes += std.math.max(std.mem.page_size, config.stack_size);
626 bytes = std.mem.alignForward(bytes, std.mem.page_size);
627 stack_offset = bytes;
628
629 bytes = std.mem.alignForward(bytes, linux.tls.tls_image.alloc_align);
630 tls_offset = bytes;
631 bytes += linux.tls.tls_image.alloc_size;
632
633 bytes = std.mem.alignForward(bytes, @alignOf(Instance));
634 instance_offset = bytes;
635 bytes += @sizeOf(Instance);
636
637 bytes = std.mem.alignForward(bytes, std.mem.page_size);
638 break :blk bytes;
639 };
640
641 // map all memory needed without read/write permissions
642 // to avoid committing the whole region right away
643 const mapped = os.mmap(
402644 null,
403 mmap_len,
645 map_bytes,
404646 os.PROT_NONE,
405647 os.MAP_PRIVATE | os.MAP_ANONYMOUS,
406648 -1,
......@@ -411,73 +653,57 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF
411653 error.PermissionDenied => unreachable,
412654 else => |e| return e,
413655 };
414 errdefer os.munmap(mmap_slice);
656 assert(mapped.len >= map_bytes);
657 errdefer os.munmap(mapped);
415658
416 // Map everything but the guard page as rw
659 // map everything but the guard page as read/write
417660 os.mprotect(
418 mmap_slice[guard_end_offset..],
661 mapped[guard_offset..],
419662 os.PROT_READ | os.PROT_WRITE,
420663 ) catch |err| switch (err) {
421664 error.AccessDenied => unreachable,
422665 else => |e| return e,
423666 };
424667
425 break :mem mmap_slice;
426 };
427
428 const mmap_addr = @ptrToInt(mmap_slice.ptr);
429
430 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset));
431 thread_ptr.data.memory = mmap_slice;
668 // Prepare the TLS segment and prepare a user_desc struct when needed on i386
669 var tls_ptr = os.linux.tls.prepareTLS(mapped[tls_offset..]);
670 var user_desc: if (target.cpu.arch == .i386) os.linux.user_desc else void = undefined;
671 if (target.cpu.arch == .i386) {
672 defer tls_ptr = @ptrToInt(&user_desc);
673 user_desc = .{
674 .entry_number = os.linux.tls.tls_image.gdt_entry_number,
675 .base_addr = tls_ptr,
676 .limit = 0xfffff,
677 .seg_32bit = 1,
678 .contents = 0, // Data
679 .read_exec_only = 0,
680 .limit_in_pages = 1,
681 .seg_not_present = 0,
682 .useable = 1,
683 };
684 }
432685
433 var arg: usize = undefined;
434 if (@sizeOf(Context) != 0) {
435 arg = mmap_addr + context_start_offset;
436 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg));
437 context_ptr.* = context;
438 }
686 const instance = @ptrCast(*Instance, @alignCast(@alignOf(Instance), &mapped[instance_offset]));
687 instance.* = .{
688 .fn_args = args,
689 .thread = .{ .mapped = mapped },
690 };
439691
440 if (std.Target.current.os.tag == .linux) {
441 const flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES |
442 os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM |
692 const flags: u32 = os.CLONE_THREAD | os.CLONE_DETACHED |
693 os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES |
443694 os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID |
444 os.CLONE_DETACHED | os.CLONE_SETTLS;
445 // This structure is only needed when targeting i386
446 var user_desc: if (std.Target.current.cpu.arch == .i386) os.linux.user_desc else void = undefined;
447
448 const tls_area = mmap_slice[tls_start_offset..];
449 const tp_value = os.linux.tls.prepareTLS(tls_area);
450
451 const newtls = blk: {
452 if (std.Target.current.cpu.arch == .i386) {
453 user_desc = os.linux.user_desc{
454 .entry_number = os.linux.tls.tls_image.gdt_entry_number,
455 .base_addr = tp_value,
456 .limit = 0xfffff,
457 .seg_32bit = 1,
458 .contents = 0, // Data
459 .read_exec_only = 0,
460 .limit_in_pages = 1,
461 .seg_not_present = 0,
462 .useable = 1,
463 };
464 break :blk @ptrToInt(&user_desc);
465 } else {
466 break :blk tp_value;
467 }
468 };
695 os.CLONE_SIGHAND | os.CLONE_SYSVSEM | os.CLONE_SETTLS;
469696
470 const rc = os.linux.clone(
471 MainFuncs.linuxThreadMain,
472 mmap_addr + stack_end_offset,
697 switch (linux.getErrno(linux.clone(
698 Instance.entryFn,
699 @ptrToInt(&mapped[stack_offset]),
473700 flags,
474 arg,
475 &thread_ptr.data.handle,
476 newtls,
477 &thread_ptr.data.handle,
478 );
479 switch (os.errno(rc)) {
480 0 => return thread_ptr,
701 @ptrToInt(instance),
702 &instance.thread.parent_tid,
703 tls_ptr,
704 &instance.thread.child_tid.value,
705 ))) {
706 0 => return Impl{ .thread = &instance.thread },
481707 os.EAGAIN => return error.ThreadQuotaExceeded,
482708 os.EINVAL => unreachable,
483709 os.ENOMEM => return error.SystemResources,
......@@ -486,100 +712,92 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF
486712 os.EUSERS => unreachable,
487713 else => |err| return os.unexpectedErrno(err),
488714 }
489 } else {
490 @compileError("Unsupported OS");
491715 }
492}
493716
494pub const CpuCountError = error{
495 PermissionDenied,
496 SystemResources,
497 Unexpected,
498};
717 fn getHandle(self: Impl) ThreadHandle {
718 return self.thread.parent_tid;
719 }
499720
500pub fn cpuCount() CpuCountError!usize {
501 switch (std.Target.current.os.tag) {
502 .linux => {
503 const cpu_set = try os.sched_getaffinity(0);
504 return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast
505 },
506 .windows => {
507 return os.windows.peb().NumberOfProcessors;
508 },
509 .openbsd => {
510 var count: c_int = undefined;
511 var count_size: usize = @sizeOf(c_int);
512 const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE };
513 os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) {
514 error.NameTooLong, error.UnknownName => unreachable,
515 else => |e| return e,
516 };
517 return @intCast(usize, count);
518 },
519 .haiku => {
520 var count: u32 = undefined;
521 // var system_info: os.system_info = undefined;
522 // const rc = os.system.get_system_info(&system_info);
523 count = system_info.cpu_count;
524 return @intCast(usize, count);
525 },
526 else => {
527 var count: c_int = undefined;
528 var count_len: usize = @sizeOf(c_int);
529 const name = if (comptime std.Target.current.isDarwin()) "hw.logicalcpu" else "hw.ncpu";
530 os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) {
531 error.NameTooLong, error.UnknownName => unreachable,
532 else => |e| return e,
533 };
534 return @intCast(usize, count);
535 },
721 fn detach(self: Impl) void {
722 switch (self.thread.completion.swap(.detached, .SeqCst)) {
723 .running => {},
724 .completed => self.join(),
725 .detached => unreachable,
726 }
536727 }
537}
538728
539pub fn getCurrentThreadId() u64 {
540 switch (std.Target.current.os.tag) {
541 .linux => {
542 // Use the syscall directly as musl doesn't provide a wrapper.
543 return @bitCast(u32, os.linux.gettid());
544 },
545 .windows => {
546 return os.windows.kernel32.GetCurrentThreadId();
547 },
548 .macos, .ios, .watchos, .tvos => {
549 var thread_id: u64 = undefined;
550 // Pass thread=null to get the current thread ID.
551 assert(c.pthread_threadid_np(null, &thread_id) == 0);
552 return thread_id;
553 },
554 .dragonfly => {
555 return @bitCast(u32, c.lwp_gettid());
556 },
557 .netbsd => {
558 return @bitCast(u32, c._lwp_self());
559 },
560 .freebsd => {
561 return @bitCast(u32, c.pthread_getthreadid_np());
562 },
563 .openbsd => {
564 return @bitCast(u32, c.getthrid());
565 },
566 .haiku => {
567 return @bitCast(u32, c.find_thread(null));
568 },
569 else => {
570 @compileError("getCurrentThreadId not implemented for this platform");
571 },
729 fn join(self: Impl) void {
730 defer os.munmap(self.thread.mapped);
731
732 var spin: u8 = 10;
733 while (true) {
734 const tid = self.thread.child_tid.load(.SeqCst);
735 if (tid == 0) {
736 break;
737 }
738
739 if (spin > 0) {
740 spin -= 1;
741 std.atomic.spinLoopHint();
742 continue;
743 }
744
745 switch (linux.getErrno(linux.futex_wait(
746 &self.thread.child_tid.value,
747 linux.FUTEX_WAIT,
748 tid,
749 null,
750 ))) {
751 0 => continue,
752 os.EINTR => continue,
753 os.EAGAIN => continue,
754 else => unreachable,
755 }
756 }
572757 }
573}
758};
574759
575760test "std.Thread" {
576 if (!builtin.single_threaded) {
577 _ = AutoResetEvent;
578 _ = Futex;
579 _ = ResetEvent;
580 _ = StaticResetEvent;
581 _ = Mutex;
582 _ = Semaphore;
583 _ = Condition;
584 }
761 // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint.
762 _ = AutoResetEvent;
763 _ = Futex;
764 _ = ResetEvent;
765 _ = StaticResetEvent;
766 _ = Mutex;
767 _ = Semaphore;
768 _ = Condition;
769}
770
771fn testIncrementNotify(value: *usize, event: *ResetEvent) void {
772 value.* += 1;
773 event.set();
774}
775
776test "Thread.join" {
777 if (std.builtin.single_threaded) return error.SkipZigTest;
778
779 var value: usize = 0;
780 var event: ResetEvent = undefined;
781 try event.init();
782 defer event.deinit();
783
784 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
785 thread.join();
786
787 try std.testing.expectEqual(value, 1);
788}
789
790test "Thread.detach" {
791 if (std.builtin.single_threaded) return error.SkipZigTest;
792
793 var value: usize = 0;
794 var event: ResetEvent = undefined;
795 try event.init();
796 defer event.deinit();
797
798 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
799 thread.detach();
800
801 event.wait();
802 try std.testing.expectEqual(value, 1);
585803}
lib/std/Thread/AutoResetEvent.zig+4-4
......@@ -220,9 +220,9 @@ test "basic usage" {
220220 };
221221
222222 var context = Context{};
223 const send_thread = try std.Thread.spawn(Context.sender, &context);
224 const recv_thread = try std.Thread.spawn(Context.receiver, &context);
223 const send_thread = try std.Thread.spawn(.{}, Context.sender, .{&context});
224 const recv_thread = try std.Thread.spawn(.{}, Context.receiver, .{&context});
225225
226 send_thread.wait();
227 recv_thread.wait();
226 send_thread.join();
227 recv_thread.join();
228228}
lib/std/Thread/Futex.zig+116-124
......@@ -64,9 +64,8 @@ pub fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{TimedOut}
6464/// Unblocks at most `num_waiters` callers blocked in a `wait()` call on `ptr`.
6565/// `num_waiters` of 1 unblocks at most one `wait(ptr, ...)` and `maxInt(u32)` unblocks effectively all `wait(ptr, ...)`.
6666pub fn wake(ptr: *const Atomic(u32), num_waiters: u32) void {
67 if (num_waiters == 0 or single_threaded) {
68 return;
69 }
67 if (single_threaded) return;
68 if (num_waiters == 0) return;
7069
7170 return OsFutex.wake(ptr, num_waiters);
7271}
......@@ -80,7 +79,23 @@ else if (target.isDarwin())
8079else if (std.builtin.link_libc)
8180 PosixFutex
8281else
83 @compileError("Operating System unsupported");
82 UnsupportedFutex;
83
84const UnsupportedFutex = struct {
85 fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{TimedOut}!void {
86 return unsupported(.{ ptr, expect, timeout });
87 }
88
89 fn wake(ptr: *const Atomic(u32), num_waiters: u32) void {
90 return unsupported(.{ ptr, num_waiters });
91 }
92
93 fn unsupported(unused: anytype) noreturn {
94 @compileLog("Unsupported operating system", target.os.tag);
95 _ = unused;
96 unreachable;
97 }
98};
8499
85100const WindowsFutex = struct {
86101 const windows = std.os.windows;
......@@ -391,75 +406,73 @@ test "Futex - wait/wake" {
391406}
392407
393408test "Futex - Signal" {
394 if (!single_threaded) {
395 return;
409 if (single_threaded) {
410 return error.SkipZigTest;
396411 }
397412
398 try (struct {
413 const Paddle = struct {
399414 value: Atomic(u32) = Atomic(u32).init(0),
415 current: u32 = 0,
400416
401 const Self = @This();
417 fn run(self: *@This(), hit_to: *@This()) !void {
418 var iterations: usize = 4;
419 while (iterations > 0) : (iterations -= 1) {
420 var value: u32 = undefined;
421 while (true) {
422 value = self.value.load(.Acquire);
423 if (value != self.current) break;
424 Futex.wait(&self.value, self.current, null) catch unreachable;
425 }
402426
403 fn send(self: *Self, value: u32) void {
404 self.value.store(value, .Release);
405 Futex.wake(&self.value, 1);
406 }
427 try testing.expectEqual(value, self.current + 1);
428 self.current = value;
407429
408 fn recv(self: *Self, expected: u32) void {
409 while (true) {
410 const value = self.value.load(.Acquire);
411 if (value == expected) break;
412 Futex.wait(&self.value, value, null) catch unreachable;
430 _ = hit_to.value.fetchAdd(1, .Release);
431 Futex.wake(&hit_to.value, 1);
413432 }
414433 }
434 };
415435
416 const Thread = struct {
417 tx: *Self,
418 rx: *Self,
419
420 const start_value = 1;
421
422 fn run(self: Thread) void {
423 var iterations: u32 = start_value;
424 while (iterations < 10) : (iterations += 1) {
425 self.rx.recv(iterations);
426 self.tx.send(iterations);
427 }
428 }
429 };
430
431 fn run() !void {
432 var ping = Self{};
433 var pong = Self{};
436 var ping = Paddle{};
437 var pong = Paddle{};
434438
435 const t1 = try std.Thread.spawn(Thread.run, .{ .rx = &ping, .tx = &pong });
436 defer t1.wait();
439 const t1 = try std.Thread.spawn(.{}, Paddle.run, .{ &ping, &pong });
440 defer t1.join();
437441
438 const t2 = try std.Thread.spawn(Thread.run, .{ .rx = &pong, .tx = &ping });
439 defer t2.wait();
442 const t2 = try std.Thread.spawn(.{}, Paddle.run, .{ &pong, &ping });
443 defer t2.join();
440444
441 ping.send(Thread.start_value);
442 }
443 }).run();
445 _ = ping.value.fetchAdd(1, .Release);
446 Futex.wake(&ping.value, 1);
444447}
445448
446449test "Futex - Broadcast" {
447 if (!single_threaded) {
448 return;
450 if (single_threaded) {
451 return error.SkipZigTest;
449452 }
450453
451 try (struct {
452 threads: [10]*std.Thread = undefined,
454 const Context = struct {
455 threads: [4]std.Thread = undefined,
453456 broadcast: Atomic(u32) = Atomic(u32).init(0),
454457 notified: Atomic(usize) = Atomic(usize).init(0),
455458
456 const Self = @This();
457
458459 const BROADCAST_EMPTY = 0;
459460 const BROADCAST_SENT = 1;
460461 const BROADCAST_RECEIVED = 2;
461462
462 fn runReceiver(self: *Self) void {
463 fn runSender(self: *@This()) !void {
464 self.broadcast.store(BROADCAST_SENT, .Monotonic);
465 Futex.wake(&self.broadcast, @intCast(u32, self.threads.len));
466
467 while (true) {
468 const broadcast = self.broadcast.load(.Acquire);
469 if (broadcast == BROADCAST_RECEIVED) break;
470 try testing.expectEqual(broadcast, BROADCAST_SENT);
471 Futex.wait(&self.broadcast, broadcast, null) catch unreachable;
472 }
473 }
474
475 fn runReceiver(self: *@This()) void {
463476 while (true) {
464477 const broadcast = self.broadcast.load(.Acquire);
465478 if (broadcast == BROADCAST_SENT) break;
......@@ -473,98 +486,77 @@ test "Futex - Broadcast" {
473486 Futex.wake(&self.broadcast, 1);
474487 }
475488 }
489 };
476490
477 fn run() !void {
478 var self = Self{};
479
480 for (self.threads) |*thread|
481 thread.* = try std.Thread.spawn(runReceiver, &self);
482 defer for (self.threads) |thread|
483 thread.wait();
491 var ctx = Context{};
492 for (ctx.threads) |*thread|
493 thread.* = try std.Thread.spawn(.{}, Context.runReceiver, .{&ctx});
494 defer for (ctx.threads) |thread|
495 thread.join();
484496
485 std.time.sleep(16 * std.time.ns_per_ms);
486 self.broadcast.store(BROADCAST_SENT, .Monotonic);
487 Futex.wake(&self.broadcast, @intCast(u32, self.threads.len));
497 // Try to wait for the threads to start before running runSender().
498 // NOTE: not actually needed for correctness.
499 std.time.sleep(16 * std.time.ns_per_ms);
500 try ctx.runSender();
488501
489 while (true) {
490 const broadcast = self.broadcast.load(.Acquire);
491 if (broadcast == BROADCAST_RECEIVED) break;
492 try testing.expectEqual(broadcast, BROADCAST_SENT);
493 Futex.wait(&self.broadcast, broadcast, null) catch unreachable;
494 }
495
496 const notified = self.notified.load(.Monotonic);
497 try testing.expectEqual(notified, self.threads.len);
498 }
499 }).run();
502 const notified = ctx.notified.load(.Monotonic);
503 try testing.expectEqual(notified, ctx.threads.len);
500504}
501505
502506test "Futex - Chain" {
503 if (!single_threaded) {
504 return;
507 if (single_threaded) {
508 return error.SkipZigTest;
505509 }
506510
507 try (struct {
508 completed: Signal = .{},
509 threads: [10]struct {
510 thread: *std.Thread,
511 signal: Signal,
512 } = undefined,
513
514 const Signal = struct {
515 state: Atomic(u32) = Atomic(u32).init(0),
516
517 fn wait(self: *Signal) void {
518 while (true) {
519 const value = self.value.load(.Acquire);
520 if (value == 1) break;
521 assert(value == 0);
522 Futex.wait(&self.value, 0, null) catch unreachable;
523 }
524 }
511 const Signal = struct {
512 value: Atomic(u32) = Atomic(u32).init(0),
525513
526 fn notify(self: *Signal) void {
527 assert(self.value.load(.Unordered) == 0);
528 self.value.store(1, .Release);
529 Futex.wake(&self.value, 1);
514 fn wait(self: *@This()) void {
515 while (true) {
516 const value = self.value.load(.Acquire);
517 if (value == 1) break;
518 assert(value == 0);
519 Futex.wait(&self.value, 0, null) catch unreachable;
530520 }
531 };
521 }
532522
533 const Self = @This();
534 const Chain = struct {
535 self: *Self,
536 index: usize,
523 fn notify(self: *@This()) void {
524 assert(self.value.load(.Unordered) == 0);
525 self.value.store(1, .Release);
526 Futex.wake(&self.value, 1);
527 }
528 };
537529
538 fn run(chain: Chain) void {
539 const this_signal = &chain.self.threads[chain.index].signal;
530 const Context = struct {
531 completed: Signal = .{},
532 threads: [4]struct {
533 thread: std.Thread,
534 signal: Signal,
535 } = undefined,
540536
541 var next_signal = &chain.self.completed;
542 if (chain.index + 1 < chain.self.threads.len) {
543 next_signal = &chain.self.threads[chain.index + 1].signal;
544 }
537 fn run(self: *@This(), index: usize) void {
538 const this_signal = &self.threads[index].signal;
545539
546 this_signal.wait();
547 next_signal.notify();
540 var next_signal = &self.completed;
541 if (index + 1 < self.threads.len) {
542 next_signal = &self.threads[index + 1].signal;
548543 }
549 };
550544
551 fn run() !void {
552 var self = Self{};
545 this_signal.wait();
546 next_signal.notify();
547 }
548 };
553549
554 for (self.threads) |*entry, index| {
555 entry.signal = .{};
556 entry.thread = try std.Thread.spawn(Chain.run, .{
557 .self = &self,
558 .index = index,
559 });
560 }
550 var ctx = Context{};
551 for (ctx.threads) |*entry, index| {
552 entry.signal = .{};
553 entry.thread = try std.Thread.spawn(.{}, Context.run, .{ &ctx, index });
554 }
561555
562 self.threads[0].signal.notify();
563 self.completed.wait();
556 ctx.threads[0].signal.notify();
557 ctx.completed.wait();
564558
565 for (self.threads) |entry| {
566 entry.thread.wait();
567 }
568 }
569 }).run();
559 for (ctx.threads) |entry| {
560 entry.thread.join();
561 }
570562}
lib/std/Thread/Mutex.zig+3-3
......@@ -297,12 +297,12 @@ test "basic usage" {
297297 try testing.expect(context.data == TestContext.incr_count);
298298 } else {
299299 const thread_count = 10;
300 var threads: [thread_count]*std.Thread = undefined;
300 var threads: [thread_count]std.Thread = undefined;
301301 for (threads) |*t| {
302 t.* = try std.Thread.spawn(worker, &context);
302 t.* = try std.Thread.spawn(.{}, worker, .{&context});
303303 }
304304 for (threads) |t|
305 t.wait();
305 t.join();
306306
307307 try testing.expect(context.data == thread_count * TestContext.incr_count);
308308 }
lib/std/Thread/ResetEvent.zig+4-4
......@@ -281,8 +281,8 @@ test "basic usage" {
281281 var context: Context = undefined;
282282 try context.init();
283283 defer context.deinit();
284 const receiver = try std.Thread.spawn(Context.receiver, &context);
285 defer receiver.wait();
284 const receiver = try std.Thread.spawn(.{}, Context.receiver, .{&context});
285 defer receiver.join();
286286 try context.sender();
287287
288288 if (false) {
......@@ -290,8 +290,8 @@ test "basic usage" {
290290 // https://github.com/ziglang/zig/issues/7009
291291 var timed = Context.init();
292292 defer timed.deinit();
293 const sleeper = try std.Thread.spawn(Context.sleeper, &timed);
294 defer sleeper.wait();
293 const sleeper = try std.Thread.spawn(.{}, Context.sleeper, .{&timed});
294 defer sleeper.join();
295295 try timed.timedWaiter();
296296 }
297297}
lib/std/Thread/StaticResetEvent.zig+4-4
......@@ -384,8 +384,8 @@ test "basic usage" {
384384 };
385385
386386 var context = Context{};
387 const receiver = try std.Thread.spawn(Context.receiver, &context);
388 defer receiver.wait();
387 const receiver = try std.Thread.spawn(.{}, Context.receiver, .{&context});
388 defer receiver.join();
389389 try context.sender();
390390
391391 if (false) {
......@@ -393,8 +393,8 @@ test "basic usage" {
393393 // https://github.com/ziglang/zig/issues/7009
394394 var timed = Context.init();
395395 defer timed.deinit();
396 const sleeper = try std.Thread.spawn(Context.sleeper, &timed);
397 defer sleeper.wait();
396 const sleeper = try std.Thread.spawn(.{}, Context.sleeper, .{&timed});
397 defer sleeper.join();
398398 try timed.timedWaiter();
399399 }
400400}
lib/std/atomic/queue.zig+6-6
......@@ -214,20 +214,20 @@ test "std.atomic.Queue" {
214214 } else {
215215 try expect(context.queue.isEmpty());
216216
217 var putters: [put_thread_count]*std.Thread = undefined;
217 var putters: [put_thread_count]std.Thread = undefined;
218218 for (putters) |*t| {
219 t.* = try std.Thread.spawn(startPuts, &context);
219 t.* = try std.Thread.spawn(.{}, startPuts, .{&context});
220220 }
221 var getters: [put_thread_count]*std.Thread = undefined;
221 var getters: [put_thread_count]std.Thread = undefined;
222222 for (getters) |*t| {
223 t.* = try std.Thread.spawn(startGets, &context);
223 t.* = try std.Thread.spawn(.{}, startGets, .{&context});
224224 }
225225
226226 for (putters) |t|
227 t.wait();
227 t.join();
228228 @atomicStore(bool, &context.puts_done, true, .SeqCst);
229229 for (getters) |t|
230 t.wait();
230 t.join();
231231
232232 try expect(context.queue.isEmpty());
233233 }
lib/std/atomic/stack.zig+6-6
......@@ -121,20 +121,20 @@ test "std.atomic.stack" {
121121 }
122122 }
123123 } else {
124 var putters: [put_thread_count]*std.Thread = undefined;
124 var putters: [put_thread_count]std.Thread = undefined;
125125 for (putters) |*t| {
126 t.* = try std.Thread.spawn(startPuts, &context);
126 t.* = try std.Thread.spawn(.{}, startPuts, .{&context});
127127 }
128 var getters: [put_thread_count]*std.Thread = undefined;
128 var getters: [put_thread_count]std.Thread = undefined;
129129 for (getters) |*t| {
130 t.* = try std.Thread.spawn(startGets, &context);
130 t.* = try std.Thread.spawn(.{}, startGets, .{&context});
131131 }
132132
133133 for (putters) |t|
134 t.wait();
134 t.join();
135135 @atomicStore(bool, &context.puts_done, true, .SeqCst);
136136 for (getters) |t|
137 t.wait();
137 t.join();
138138 }
139139
140140 if (context.put_sum != context.get_sum) {
lib/std/c.zig+1
......@@ -277,6 +277,7 @@ pub extern "c" fn pthread_attr_setguardsize(attr: *pthread_attr_t, guardsize: us
277277pub extern "c" fn pthread_attr_destroy(attr: *pthread_attr_t) c_int;
278278pub extern "c" fn pthread_self() pthread_t;
279279pub extern "c" fn pthread_join(thread: pthread_t, arg_return: ?*?*c_void) c_int;
280pub extern "c" fn pthread_detach(thread: pthread_t) c_int;
280281pub extern "c" fn pthread_atfork(
281282 prepare: ?fn () callconv(.C) void,
282283 parent: ?fn () callconv(.C) void,
lib/std/debug.zig+2-2
......@@ -273,8 +273,8 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
273273 if (builtin.single_threaded) {
274274 stderr.print("panic: ", .{}) catch os.abort();
275275 } else {
276 const current_thread_id = std.Thread.getCurrentThreadId();
277 stderr.print("thread {d} panic: ", .{current_thread_id}) catch os.abort();
276 const current_thread_id = std.Thread.getCurrentId();
277 stderr.print("thread {} panic: ", .{current_thread_id}) catch os.abort();
278278 }
279279 stderr.print(format ++ "\n", args) catch os.abort();
280280 if (trace) |t| {
lib/std/event/loop.zig+18-18
......@@ -21,12 +21,12 @@ pub const Loop = struct {
2121 os_data: OsData,
2222 final_resume_node: ResumeNode,
2323 pending_event_count: usize,
24 extra_threads: []*Thread,
24 extra_threads: []Thread,
2525 /// TODO change this to a pool of configurable number of threads
2626 /// and rename it to be not file-system-specific. it will become
2727 /// a thread pool for turning non-CPU-bound blocking things into
2828 /// async things. A fallback for any missing OS-specific API.
29 fs_thread: *Thread,
29 fs_thread: Thread,
3030 fs_queue: std.atomic.Queue(Request),
3131 fs_end_request: Request.Node,
3232 fs_thread_wakeup: std.Thread.ResetEvent,
......@@ -137,7 +137,7 @@ pub const Loop = struct {
137137 }
138138
139139 /// After initialization, call run().
140 /// This is the same as `initThreadPool` using `Thread.cpuCount` to determine the thread
140 /// This is the same as `initThreadPool` using `Thread.getCpuCount` to determine the thread
141141 /// pool size.
142142 /// TODO copy elision / named return values so that the threads referencing *Loop
143143 /// have the correct pointer value.
......@@ -145,7 +145,7 @@ pub const Loop = struct {
145145 pub fn initMultiThreaded(self: *Loop) !void {
146146 if (builtin.single_threaded)
147147 @compileError("initMultiThreaded unavailable when building in single-threaded mode");
148 const core_count = try Thread.cpuCount();
148 const core_count = try Thread.getCpuCount();
149149 return self.initThreadPool(core_count);
150150 }
151151
......@@ -183,17 +183,17 @@ pub const Loop = struct {
183183 resume_node_count,
184184 );
185185
186 self.extra_threads = try self.arena.allocator.alloc(*Thread, extra_thread_count);
186 self.extra_threads = try self.arena.allocator.alloc(Thread, extra_thread_count);
187187
188188 try self.initOsData(extra_thread_count);
189189 errdefer self.deinitOsData();
190190
191191 if (!builtin.single_threaded) {
192 self.fs_thread = try Thread.spawn(posixFsRun, self);
192 self.fs_thread = try Thread.spawn(.{}, posixFsRun, .{self});
193193 }
194194 errdefer if (!builtin.single_threaded) {
195195 self.posixFsRequest(&self.fs_end_request);
196 self.fs_thread.wait();
196 self.fs_thread.join();
197197 };
198198
199199 if (!std.builtin.single_threaded)
......@@ -264,11 +264,11 @@ pub const Loop = struct {
264264 assert(amt == wakeup_bytes.len);
265265 while (extra_thread_index != 0) {
266266 extra_thread_index -= 1;
267 self.extra_threads[extra_thread_index].wait();
267 self.extra_threads[extra_thread_index].join();
268268 }
269269 }
270270 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
271 self.extra_threads[extra_thread_index] = try Thread.spawn(workerRun, self);
271 self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self});
272272 }
273273 },
274274 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
......@@ -329,11 +329,11 @@ pub const Loop = struct {
329329 _ = os.kevent(self.os_data.kqfd, final_kev_arr, empty_kevs, null) catch unreachable;
330330 while (extra_thread_index != 0) {
331331 extra_thread_index -= 1;
332 self.extra_threads[extra_thread_index].wait();
332 self.extra_threads[extra_thread_index].join();
333333 }
334334 }
335335 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
336 self.extra_threads[extra_thread_index] = try Thread.spawn(workerRun, self);
336 self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self});
337337 }
338338 },
339339 .windows => {
......@@ -378,11 +378,11 @@ pub const Loop = struct {
378378 }
379379 while (extra_thread_index != 0) {
380380 extra_thread_index -= 1;
381 self.extra_threads[extra_thread_index].wait();
381 self.extra_threads[extra_thread_index].join();
382382 }
383383 }
384384 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
385 self.extra_threads[extra_thread_index] = try Thread.spawn(workerRun, self);
385 self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self});
386386 }
387387 },
388388 else => {},
......@@ -651,18 +651,18 @@ pub const Loop = struct {
651651 .netbsd,
652652 .dragonfly,
653653 .openbsd,
654 => self.fs_thread.wait(),
654 => self.fs_thread.join(),
655655 else => {},
656656 }
657657 }
658658
659659 for (self.extra_threads) |extra_thread| {
660 extra_thread.wait();
660 extra_thread.join();
661661 }
662662
663663 @atomicStore(bool, &self.delay_queue.is_running, false, .SeqCst);
664664 self.delay_queue.event.set();
665 self.delay_queue.thread.wait();
665 self.delay_queue.thread.join();
666666 }
667667
668668 /// Runs the provided function asynchronously. The function's frame is allocated
......@@ -787,7 +787,7 @@ pub const Loop = struct {
787787 const DelayQueue = struct {
788788 timer: std.time.Timer,
789789 waiters: Waiters,
790 thread: *std.Thread,
790 thread: std.Thread,
791791 event: std.Thread.AutoResetEvent,
792792 is_running: bool,
793793
......@@ -802,7 +802,7 @@ pub const Loop = struct {
802802 .event = std.Thread.AutoResetEvent{},
803803 .is_running = true,
804804 // Must be last so that it can read the other state, such as `is_running`.
805 .thread = try std.Thread.spawn(DelayQueue.run, self),
805 .thread = try std.Thread.spawn(.{}, DelayQueue.run, .{self}),
806806 };
807807 }
808808
lib/std/fs/test.zig+5-6
......@@ -862,11 +862,10 @@ test "open file with exclusive lock twice, make sure it waits" {
862862 errdefer file.close();
863863
864864 const S = struct {
865 const C = struct { dir: *fs.Dir, evt: *std.Thread.ResetEvent };
866 fn checkFn(ctx: C) !void {
867 const file1 = try ctx.dir.createFile(filename, .{ .lock = .Exclusive });
865 fn checkFn(dir: *fs.Dir, evt: *std.Thread.ResetEvent) !void {
866 const file1 = try dir.createFile(filename, .{ .lock = .Exclusive });
868867 defer file1.close();
869 ctx.evt.set();
868 evt.set();
870869 }
871870 };
872871
......@@ -874,8 +873,8 @@ test "open file with exclusive lock twice, make sure it waits" {
874873 try evt.init();
875874 defer evt.deinit();
876875
877 const t = try std.Thread.spawn(S.checkFn, S.C{ .dir = &tmp.dir, .evt = &evt });
878 defer t.wait();
876 const t = try std.Thread.spawn(.{}, S.checkFn, .{ &tmp.dir, &evt });
877 defer t.join();
879878
880879 const SLEEP_TIMEOUT_NS = 10 * std.time.ns_per_ms;
881880 // Make sure we've slept enough.
lib/std/net/test.zig+5-5
......@@ -161,8 +161,8 @@ test "listen on a port, send bytes, receive bytes" {
161161 }
162162 };
163163
164 const t = try std.Thread.spawn(S.clientFn, server.listen_address);
165 defer t.wait();
164 const t = try std.Thread.spawn(.{}, S.clientFn, .{server.listen_address});
165 defer t.join();
166166
167167 var client = try server.accept();
168168 defer client.stream.close();
......@@ -277,7 +277,7 @@ test "listen on a unix socket, send bytes, receive bytes" {
277277 try server.listen(socket_addr);
278278
279279 const S = struct {
280 fn clientFn(_: void) !void {
280 fn clientFn() !void {
281281 const socket = try net.connectUnixSocket(socket_path);
282282 defer socket.close();
283283
......@@ -285,8 +285,8 @@ test "listen on a unix socket, send bytes, receive bytes" {
285285 }
286286 };
287287
288 const t = try std.Thread.spawn(S.clientFn, {});
289 defer t.wait();
288 const t = try std.Thread.spawn(.{}, S.clientFn, .{});
289 defer t.join();
290290
291291 var client = try server.accept();
292292 defer client.stream.close();
lib/std/once.zig+4-4
......@@ -55,16 +55,16 @@ test "Once executes its function just once" {
5555 global_once.call();
5656 global_once.call();
5757 } else {
58 var threads: [10]*std.Thread = undefined;
59 defer for (threads) |handle| handle.wait();
58 var threads: [10]std.Thread = undefined;
59 defer for (threads) |handle| handle.join();
6060
6161 for (threads) |*handle| {
62 handle.* = try std.Thread.spawn(struct {
62 handle.* = try std.Thread.spawn(.{}, struct {
6363 fn thread_fn(x: u8) void {
6464 _ = x;
6565 global_once.call();
6666 }
67 }.thread_fn, 0);
67 }.thread_fn, .{0});
6868 }
6969 }
7070
lib/std/os/test.zig+19-30
......@@ -320,18 +320,9 @@ test "std.Thread.getCurrentId" {
320320 if (builtin.single_threaded) return error.SkipZigTest;
321321
322322 var thread_current_id: Thread.Id = undefined;
323 const thread = try Thread.spawn(testThreadIdFn, &thread_current_id);
324 const thread_id = thread.handle();
325 thread.wait();
326 if (Thread.use_pthreads) {
327 try expect(thread_current_id == thread_id);
328 } else if (native_os == .windows) {
329 try expect(Thread.getCurrentId() != thread_current_id);
330 } else {
331 // If the thread completes very quickly, then thread_id can be 0. See the
332 // documentation comments for `std.Thread.handle`.
333 try expect(thread_id == 0 or thread_current_id == thread_id);
334 }
323 const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id});
324 thread.join();
325 try expect(Thread.getCurrentId() != thread_current_id);
335326}
336327
337328test "spawn threads" {
......@@ -339,21 +330,20 @@ test "spawn threads" {
339330
340331 var shared_ctx: i32 = 1;
341332
342 const thread1 = try Thread.spawn(start1, {});
343 const thread2 = try Thread.spawn(start2, &shared_ctx);
344 const thread3 = try Thread.spawn(start2, &shared_ctx);
345 const thread4 = try Thread.spawn(start2, &shared_ctx);
333 const thread1 = try Thread.spawn(.{}, start1, .{});
334 const thread2 = try Thread.spawn(.{}, start2, .{&shared_ctx});
335 const thread3 = try Thread.spawn(.{}, start2, .{&shared_ctx});
336 const thread4 = try Thread.spawn(.{}, start2, .{&shared_ctx});
346337
347 thread1.wait();
348 thread2.wait();
349 thread3.wait();
350 thread4.wait();
338 thread1.join();
339 thread2.join();
340 thread3.join();
341 thread4.join();
351342
352343 try expect(shared_ctx == 4);
353344}
354345
355fn start1(ctx: void) u8 {
356 _ = ctx;
346fn start1() u8 {
357347 return 0;
358348}
359349
......@@ -365,22 +355,21 @@ fn start2(ctx: *i32) u8 {
365355test "cpu count" {
366356 if (native_os == .wasi) return error.SkipZigTest;
367357
368 const cpu_count = try Thread.cpuCount();
358 const cpu_count = try Thread.getCpuCount();
369359 try expect(cpu_count >= 1);
370360}
371361
372362test "thread local storage" {
373363 if (builtin.single_threaded) return error.SkipZigTest;
374 const thread1 = try Thread.spawn(testTls, {});
375 const thread2 = try Thread.spawn(testTls, {});
376 try testTls({});
377 thread1.wait();
378 thread2.wait();
364 const thread1 = try Thread.spawn(.{}, testTls, .{});
365 const thread2 = try Thread.spawn(.{}, testTls, .{});
366 try testTls();
367 thread1.join();
368 thread2.join();
379369}
380370
381371threadlocal var x: i32 = 1234;
382fn testTls(context: void) !void {
383 _ = context;
372fn testTls() !void {
384373 if (x != 1234) return error.TlsBadStartValue;
385374 x += 1;
386375 if (x != 1235) return error.TlsBadEndValue;
lib/std/target.zig+19-4
......@@ -69,6 +69,13 @@ pub const Target = struct {
6969 };
7070 }
7171
72 pub fn isBSD(tag: Tag) bool {
73 return tag.isDarwin() or switch (tag) {
74 .kfreebsd, .freebsd, .openbsd, .netbsd, .dragonfly => true,
75 else => false,
76 };
77 }
78
7279 pub fn dynamicLibSuffix(tag: Tag) [:0]const u8 {
7380 if (tag.isDarwin()) {
7481 return ".dylib";
......@@ -787,6 +794,13 @@ pub const Target = struct {
787794 };
788795 }
789796
797 pub fn isAARCH64(arch: Arch) bool {
798 return switch (arch) {
799 .aarch64, .aarch64_be, .aarch64_32 => true,
800 else => false,
801 };
802 }
803
790804 pub fn isThumb(arch: Arch) bool {
791805 return switch (arch) {
792806 .thumb, .thumbeb => true,
......@@ -1365,10 +1379,7 @@ pub const Target = struct {
13651379 }
13661380
13671381 pub fn isAndroid(self: Target) bool {
1368 return switch (self.abi) {
1369 .android => true,
1370 else => false,
1371 };
1382 return self.abi == .android;
13721383 }
13731384
13741385 pub fn isWasm(self: Target) bool {
......@@ -1379,6 +1390,10 @@ pub const Target = struct {
13791390 return self.os.tag.isDarwin();
13801391 }
13811392
1393 pub fn isBSD(self: Target) bool {
1394 return self.os.tag.isBSD();
1395 }
1396
13821397 pub fn isGnuLibC_os_tag_abi(os_tag: Os.Tag, abi: Abi) bool {
13831398 return os_tag == .linux and abi.isGnu();
13841399 }
src/ThreadPool.zig+4-4
......@@ -21,7 +21,7 @@ const Runnable = struct {
2121
2222const Worker = struct {
2323 pool: *ThreadPool,
24 thread: *std.Thread,
24 thread: std.Thread,
2525 /// The node is for this worker only and must have an already initialized event
2626 /// when the thread is spawned.
2727 idle_node: IdleQueue.Node,
......@@ -60,7 +60,7 @@ pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void {
6060 if (std.builtin.single_threaded)
6161 return;
6262
63 const worker_count = std.math.max(1, std.Thread.cpuCount() catch 1);
63 const worker_count = std.math.max(1, std.Thread.getCpuCount() catch 1);
6464 self.workers = try allocator.alloc(Worker, worker_count);
6565 errdefer allocator.free(self.workers);
6666
......@@ -74,13 +74,13 @@ pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void {
7474 try worker.idle_node.data.init();
7575 errdefer worker.idle_node.data.deinit();
7676
77 worker.thread = try std.Thread.spawn(Worker.run, worker);
77 worker.thread = try std.Thread.spawn(.{}, Worker.run, .{worker});
7878 }
7979}
8080
8181fn destroyWorkers(self: *ThreadPool, spawned: usize) void {
8282 for (self.workers[0..spawned]) |*worker| {
83 worker.thread.wait();
83 worker.thread.join();
8484 worker.idle_node.data.deinit();
8585 }
8686}
tools/update_cpu_features.zig+10-8
......@@ -816,18 +816,20 @@ pub fn main() anyerror!void {
816816 });
817817 }
818818 } else {
819 var threads = try arena.alloc(*std.Thread, llvm_targets.len);
819 var threads = try arena.alloc(std.Thread, llvm_targets.len);
820820 for (llvm_targets) |llvm_target, i| {
821 threads[i] = try std.Thread.spawn(processOneTarget, .{
822 .llvm_tblgen_exe = llvm_tblgen_exe,
823 .llvm_src_root = llvm_src_root,
824 .zig_src_dir = zig_src_dir,
825 .root_progress = root_progress,
826 .llvm_target = llvm_target,
821 threads[i] = try std.Thread.spawn(.{}, processOneTarget, .{
822 Job{
823 .llvm_tblgen_exe = llvm_tblgen_exe,
824 .llvm_src_root = llvm_src_root,
825 .zig_src_dir = zig_src_dir,
826 .root_progress = root_progress,
827 .llvm_target = llvm_target,
828 },
827829 });
828830 }
829831 for (threads) |thread| {
830 thread.wait();
832 thread.join();
831833 }
832834 }
833835}