authorgravatar for kbutcher6200@gmail.comkprotty <kbutcher6200@gmail.com> 2021-06-19 17:08:56-05:00
committergravatar for kbutcher6200@gmail.comkprotty <kbutcher6200@gmail.com> 2021-06-30 21:48:59-05:00
loge16d3d162a85a822e16ae181ecc6ddc507278126
tree8ed7d4df8e1adca52cb9cf2f1650b7213c1f1f41
parentacf2e8fe6484a48cef76c20368ff06fe9d7b264e

std.Thread: rewrite + extensions


1 files changed, 513 insertions(+), 446 deletions(-)

lib/std/Thread.zig+513-446
...@@ -8,7 +8,10 @@...@@ -8,7 +8,10 @@
8//! primitives that operate on kernel threads. For concurrency primitives that support8//! primitives that operate on kernel threads. For concurrency primitives that support
9//! both evented I/O and async I/O, see the respective names in the top level std namespace.9//! 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 target = std.Target.current;
14const Atomic = std.atomic.Atomic;
1215
13pub const AutoResetEvent = @import("Thread/AutoResetEvent.zig");16pub const AutoResetEvent = @import("Thread/AutoResetEvent.zig");
14pub const Futex = @import("Thread/Futex.zig");17pub const Futex = @import("Thread/Futex.zig");
...@@ -18,118 +21,59 @@ pub const Mutex = @import("Thread/Mutex.zig");...@@ -18,118 +21,59 @@ pub const Mutex = @import("Thread/Mutex.zig");
18pub const Semaphore = @import("Thread/Semaphore.zig");21pub const Semaphore = @import("Thread/Semaphore.zig");
19pub const Condition = @import("Thread/Condition.zig");22pub const Condition = @import("Thread/Condition.zig");
2023
21pub const use_pthreads = std.Target.current.os.tag != .windows and builtin.link_libc;24pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint");
2225
23const Thread = @This();26pub const use_pthreads = target.os.tag != .windows and std.builtin.link_libc;
24const std = @import("std.zig");27
25const builtin = std.builtin;28const Impl = if (target.os.tag == .windows)
26const os = std.os;29 WindowsThreadImpl
27const mem = std.mem;30else if (use_pthreads)
28const windows = std.os.windows;31 PosixThreadImpl
29const c = std.c;32else if (target.os.tag == .linux)
30const assert = std.debug.assert;33 LinuxThreadImpl
34else
35 @compileLog("Unsupported operating system", target.os.tag);
36
37impl: Impl,
3138
32const bad_startfn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";
3339
34/// Represents a kernel thread handle.40/// Represents a kernel thread handle.
35/// May be an integer or a pointer depending on the platform.41/// May be an integer or a pointer depending on the platform.
36/// On Linux and POSIX, this is the same as Id.42/// On Linux and POSIX, this is the same as Id.
37pub const Handle = if (use_pthreads)43pub const Handle = Impl.ThreadHandle;
38 c.pthread_t
39else switch (std.Target.current.os.tag) {
40 .linux => i32,
41 .windows => windows.HANDLE,
42 else => void,
43};
4444
45/// Represents a unique ID per thread.45/// Represents a unique ID per thread.
46/// May be an integer or pointer depending on the platform.46/// May be an integer or pointer depending on the platform.
47/// On Linux and POSIX, this is the same as Handle.47/// On Linux and POSIX, this is the same as Handle.
48pub const Id = switch (std.Target.current.os.tag) {48pub const Id = Impl.ThreadId;
49 .windows => windows.DWORD,
50 else => Handle,
51};
52
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};
7049
71pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint");50/// Returns the platform ID of the callers thread.
7251/// Attempts to use thread locals and avoid syscalls when possible.
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.
76pub fn getCurrentId() Id {52pub fn getCurrentId() Id {
77 if (use_pthreads) {53 return Impl.getCurrentId();
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 };
84}54}
8555
86/// Returns the handle of this thread.56pub const CpuCountError = error{
87/// On Linux and POSIX, this is the same as Id.57 PermissionDenied,
88/// On Linux, it is possible that the thread spawned with `spawn`58 SystemResources,
89/// finishes executing entirely before the clone syscall completes. In this59 Unexpected,
90/// case, this function will return 0 rather than the no-longer-existing thread's60};
91/// pid.
92pub fn handle(self: Thread) Handle {
93 return self.data.handle;
94}
9561
96pub fn wait(self: *Thread) void {62/// Returns the platforms view on the number of logical CPU cores available.
97 if (use_pthreads) {63pub fn getCpuCount() CpuCountError!usize {
98 const err = c.pthread_join(self.data.handle, null);64 return Impl.getCpuCount();
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 }
130}65}
13166
132pub const SpawnError = error{67/// Configuration options for hints on how to spawn threads.
68pub const SpawnConfig = struct {
69 // TODO compile-time call graph analysis to determine stack upper bound
70 // https://github.com/ziglang/zig/issues/157
71
72 /// Size in bytes of the Thread's stack
73 stack_size: usize = 16 * 1024 * 1024,
74};
75
76pub const SpawnError = error {
133 /// A system-imposed limit on the number of threads was encountered.77 /// A system-imposed limit on the number of threads was encountered.
134 /// There are a number of limits that may trigger this error:78 /// There are a number of limits that may trigger this error:
135 /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),79 /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),
...@@ -159,248 +103,376 @@ pub const SpawnError = error{...@@ -159,248 +103,376 @@ pub const SpawnError = error{
159 Unexpected,103 Unexpected,
160};104};
161105
162// Given `T`, the type of the thread startFn, extract the expected type for the106/// Spawns a new thread which executes `function` using `args` and returns a handle the spawned thread.
163// context parameter.107/// `config` can be used as hints to the platform for now to spawn and execute the `function`.
164fn SpawnContextType(comptime T: type) type {108/// The caller must eventually either call `join()` to wait for the thread to finish and free its resources
165 const TI = @typeInfo(T);109/// or call `detach()` to excuse the caller from calling `join()` and have the thread clean up its resources on completion`.
166 if (TI != .Fn)110pub fn spawn(
167 @compileError("expected function type, found " ++ @typeName(T));111 config: SpawnConfig,
168112 comptime function: anytype,
169 if (TI.Fn.args.len != 1)113 args: std.meta.ArgsTuple(function),
170 @compileError("expected function with single argument, found " ++ @typeName(T));114) SpawnError!Thread {
115 if (std.builtin.single_threaded) {
116 @compileError("cannot spawn thread when building in single-threaded mode");
117 }
171118
172 return TI.Fn.args[0].arg_type orelse119 const impl = try Thread.spawn(config, function, args);
173 @compileError("cannot use a generic function as thread startFn");120 return .{ .impl = impl };
174}121}
175122
176/// Spawns a new thread executing startFn, returning an handle for it.123/// Used by the Thread implementations to call the spawned function with the arguments.
177/// Caller must call wait on the returned thread.124fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
178/// The `startFn` function must take a single argument of type T and return a125 WindowsThreadImpl => windows.DWORD,
179/// value of type u8, noreturn, void or !void.126 LinuxThreadImpl => u8,
180/// The `context` parameter is of type T and is passed to the spawned thread.127 PosixThreadImpl => ?*c_void,
181pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startFn))) SpawnError!*Thread {128 else => unreachable,
182 if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode");129} {
183 // TODO compile-time call graph analysis to determine stack upper bound130 const default_value = if (Impl == PosixThreadImpl) null else 0;
184 // https://github.com/ziglang/zig/issues/157131 const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";
185 const default_stack_size = 16 * 1024 * 1024;132
133 switch (@typeInfo(@typeInfo(@TypeOf(f)).Fn.return_type.?)) {
134 .NoReturn => {
135 @call(.{}, f, args);
136 },
137 .Void => {
138 @call(.{}, f, args);
139 return default_value;
140 },
141 .Int => |info| {
142 if (info.bits != 8) {
143 @compileError(bad_fn_ret);
144 }
186145
187 const Context = @TypeOf(context);146 const status = @call(.{}, f, args);
147 if (Impl != PosixThreadImpl) {
148 return status;
149 }
188150
189 if (std.Target.current.os.tag == .windows) {151 // pthreads don't support exit status, ignore value
190 const WinThread = struct {152 _ = status;
191 const OuterContext = struct {153 return default_value;
192 thread: Thread,154 },
193 inner: Context,155 .ErrorUnion => |info| {
194 };156 if (info.payload != void) {
195 fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD {157 @compileError(bad_fn_ret);
196 const arg = if (@sizeOf(Context) == 0) undefined //158 }
197 else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*;
198159
199 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {160 @call(.{}, f, args) catch |err| {
200 .NoReturn => {161 std.debug.warn("error: {s}\n", .{@errorName(err)});
201 startFn(arg);162 if (@errorReturnTrace()) |trace| {
202 },163 std.debug.dumpStackTrace(trace.*);
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),
226 }164 }
165 };
166
167 return default_value;
168 },
169 else => {
170 @compileError(bad_fn_ret);
171 },
172 }
173}
174
175/// Retrns the handle of this thread
176/// On Linux and POSIX, this is the same as Id.
177pub fn getHandle(self: Thread) Handle {
178 return self.impl.getHandle();
179}
180
181/// Release the obligation of the caller to call `join()` and have the thread clean up its own resources on completion.
182pub fn detach(self: Thread) void {
183 return self.impl.detach();
184}
185
186/// Waits for the thread to complete, then deallocates any resources created on `spawn()`.
187pub fn join(self: Thread) void {
188 return self.impl.join();
189}
190
191/// State to synchronize detachment of spawner thread to spawned thread
192const Completion = Atomic(enum {
193 running,
194 detached,
195 completed,
196});
197
198const WindowsThreadImpl = struct {
199 const windows = os.windows;
200
201 pub const ThreadHandle = windows.HANDLE;
202 pub const ThreadId = windows.DWORD;
203
204 fn getCurrentId() ThreadId {
205 return windows.kernel.GetCurrentThreadId();
206 }
207
208 fn getCpuCount() !usize {
209 return windows.peb().NumberOfProcessors;
210 }
211
212 thread: *ThreadCompletion,
213
214 const ThreadCompletion = struct {
215 completion: Completion,
216 heap_ptr: windows.PVOID,
217 heap_handle: windows.HANDLE,
218 thread_handle: windows.HANDLE = undefined,
219
220 fn free(self: ThreadCompletion) void {
221 const status = windows.kernel32.HeapFree(self.heap_handle, 0, self.heap_ptr);
222 assert(status == 0);
223 }
224 };
225
226 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
227 const Args = @TypeOf(args);
228 const Instance = struct {
229 fn_args: Args,
230 thread: ThreadCompletion,
231
232 fn entryFn(raw_ptr: *windows.PVOID) callconv(.C) windows.DWORD {
233 const self = @ptrCast(*@This(), @alignCast(@alignOf(@This()), raw_ptr));
234 defer switch (self.thread.completion.swap(.completed, .Acquire)) {
235 .running => {},
236 .completed => unreachable,
237 .detached => self.thread.free(),
238 };
239 return callFn(f, self.fn_args);
227 }240 }
228 };241 };
229242
230 const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory;243 const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory;
231 const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext);244 const alloc_bytes = @alignOf(Instance) + @sizeOf(Instance);
232 const bytes_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory;245 const alloc_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, alloc_bytes) orelse return error.OutOfMemory;
233 errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, bytes_ptr) != 0);246 errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, alloc_ptr) != 0);
234 const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count];247
235 const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable;248 const instance_bytes = @ptrCast([*]u8, alloc_ptr)[0..alloc_bytes];
236 outer_context.* = WinThread.OuterContext{249 const instance = std.heap.FixedBufferAllocator.init(instance_bytes).allocator.create(Instance) catch unreachable;
237 .thread = Thread{250 instance.* = .{
238 .data = Thread.Data{251 .fn_args = args,
239 .heap_handle = heap_handle,252 .thread = .{
240 .alloc_start = bytes_ptr,253 .completion = Completion.init(.running),
241 .handle = undefined,254 .heap_ptr = alloc_ptr,
242 },255 .heap_handle = heap_handle,
243 },256 },
244 .inner = context,
245 };257 };
246258
247 const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner);259 const stack_size = std.math.min(64 * 1024, std.math.cast(u32, config.stack_size) catch std.math.maxInt(u32));
248 outer_context.thread.data.handle = windows.kernel32.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse {260
249 switch (windows.kernel32.GetLastError()) {261 const parameter = @ptrCast(*c_void, impl);
250 else => |err| return windows.unexpectedError(err),262
251 }263 instance.thread.thread_handle = windows.CreateThread(null, stack_size, Impl.entry, parameter, 0, null) orelse {
264 return windows.unexpectedError(windows.kernel32.GetLastError());
252 };265 };
253 return &outer_context.thread;266
267 return .{ .thread = &instance.thread };
254 }268 }
255269
256 const MainFuncs = struct {270 fn getHandle(self: Impl) ThreadHandle {
257 fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 {271 return self.thread.thread_handle;
258 const arg = if (@sizeOf(Context) == 0) undefined //272 }
259 else @intToPtr(*Context, ctx_addr).*;273
260274 fn detach(self: Impl) void {
261 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {275 windows.CloseHandle(self.thread.thread_handle);
262 .NoReturn => {276 switch (self.thread.completion.swap(.detached, .AcqRel)) {
263 startFn(arg);277 .running => {},
264 },278 .completed => self.thread.free(),
265 .Void => {279 .detached => unreachable,
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 }
289 }280 }
290 fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void {281 }
291 const arg = if (@sizeOf(Context) == 0) undefined //282
292 else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*;283 fn join(self: Impl) void {
293284 windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable;
294 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {285 windows.CloseHandle(self.thread.thread_handle);
295 .NoReturn => {286 self.thread.free();
296 startFn(arg);287 }
297 },288};
298 .Void => {289
299 startFn(arg);290const PosixThreadImpl = struct {
300 return null;291 const c = std.c;
301 },292
302 .Int => |info| {293 pub const ThreadHandle = c.pthread_t;
303 if (info.bits != 8) {294 pub const ThreadId = ThreadHandle;
304 @compileError(bad_startfn_ret);295
305 }296 fn getCurrentId() ThreadId {
306 // pthreads don't support exit status, ignore value297 return c.pthread_self();
307 _ = startFn(arg);298 }
308 return null;299
309 },300 fn getCpuCount() !usize {
310 .ErrorUnion => |info| {301 switch (target.os.tag) {
311 if (info.payload != void) {302 .linux => return LinuxThreadImpl.getCpuCount(),
312 @compileError(bad_startfn_ret);303 .openbsd => {
313 }304 var count: c_int = undefined;
314 startFn(arg) catch |err| {305 var count_size: usize = @sizeOf(c_int);
315 std.debug.warn("error: {s}\n", .{@errorName(err)});306 const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE };
316 if (@errorReturnTrace()) |trace| {307 os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) {
317 std.debug.dumpStackTrace(trace.*);308 error.NameTooLong, error.UnknownName => unreachable,
318 }309 else => |e| return e,
319 };310 };
320 return null;311 return @intCast(usize, count);
321 },312 },
322 else => @compileError(bad_startfn_ret),313 .haiku => {
323 }314 var count: u32 = undefined;
315 var system_info: os.system_info = undefined;
316 _ = os.system.get_system_info(&system_info); // always returns B_OK
317 count = system_info.cpu_count;
318 return @intCast(usize, count);
319 },
320 else => {
321 var count: c_int = undefined;
322 var count_len: usize = @sizeOf(c_int);
323 const name = if (comptime target.isDarwin()) "hw.logicalcpu" else "hw.ncpu";
324 os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) {
325 error.NameTooLong, error.UnknownName => unreachable,
326 else => |e| return e,
327 };
328 return @intCast(usize, count);
329 },
324 }330 }
325 };331 }
332
333 handle: ThreadHandle,
334
335 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
336 const Args = @TypeOf(args);
337 const allocator = std.heap.c_allocator;
338 const Instance = struct {
339 fn entryFn(raw_arg: ?*c_void) callconv(.C) ?*c_void {
340 const args_ptr = @ptrCast(*Args, @alignCast(@alignOf(Args), raw_arg orelse unreachable));
341 defer allocator.destroy(args_ptr);
342 return callFn(f, args_ptr.*);
343 }
344 };
345
346 const args_ptr = try allocator.create(Args);
347 errdefer allocator.destroy(args_ptr);
326348
327 if (Thread.use_pthreads) {
328 var attr: c.pthread_attr_t = undefined;349 var attr: c.pthread_attr_t = undefined;
329 if (c.pthread_attr_init(&attr) != 0) return error.SystemResources;350 if (c.pthread_attr_init(&attr) != 0) return error.SystemResources;
330 defer assert(c.pthread_attr_destroy(&attr) == 0);351 defer assert(c.pthread_attr_destroy(&attr) == 0);
331352
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
347 // Use the same set of parameters used by the libc-less impl.353 // Use the same set of parameters used by the libc-less impl.
348 assert(c.pthread_attr_setstacksize(&attr, default_stack_size) == 0);354 const stack_size = std.math.max(config.stack_size, 16 * 1024);
349 assert(c.pthread_attr_setguardsize(&attr, mem.page_size) == 0);355 assert(c.pthread_attr_setstacksize(&attr, stack_size) == 0);
356 assert(c.pthread_attr_setguardsize(&attr, std.mem.page_size) == 0);
350357
351 const err = c.pthread_create(358 var handle: c.pthread_t = undefined;
352 &thread_obj.data.handle,359 return switch (c.pthread_create(
360 &handle,
353 &attr,361 &attr,
354 MainFuncs.posixThreadMain,362 Instance.entryFn,
355 thread_obj.data.memory.ptr,363 @ptrCast(*c_void, args_ptr),
356 );364 )) {
357 switch (err) {365 0 => .{ .handle = handle },
358 0 => return thread_obj,366 os.EAGAIN => error.SystemResources,
359 os.EAGAIN => return error.SystemResources,
360 os.EPERM => unreachable,367 os.EPERM => unreachable,
361 os.EINVAL => unreachable,368 os.EINVAL => unreachable,
362 else => return os.unexpectedErrno(err),369 else => os.unexpectedErrno(err),
363 }370 };
371 }
372
373 fn getHandle(self: Impl) ThreadHandle {
374 return self.handle;
375 }
364376
365 return thread_obj;377 fn detach(self: Impl) void {
378 switch (c.pthread_detach(self.handle)) {
379 os.EINVAL => unreachable,
380 os.ESRCH => unreachable,
381 else => unreachable,
382 }
366 }383 }
367384
368 var guard_end_offset: usize = undefined;385 fn join(self: Impl) void {
369 var stack_end_offset: usize = undefined;386 switch (c.pthread_join(self.handle, null)) {
370 var thread_start_offset: usize = undefined;387 0 => {},
371 var context_start_offset: usize = undefined;388 os.EINVAL => unreachable,
372 var tls_start_offset: usize = undefined;389 os.ESRCH => unreachable,
373 const mmap_len = blk: {390 os.EDEADLK => unreachable,
374 var l: usize = mem.page_size;391 else => unreachable,
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);
389 }392 }
390 // Finally, the Thread Local Storage, if any.393 }
391 l = mem.alignForward(l, os.linux.tls.tls_image.alloc_align);394};
392 tls_start_offset = l;395
393 l += os.linux.tls.tls_image.alloc_size;396const LinuxThreadImpl = struct {
394 // Round the size to the page size.397 const linux = os.linux;
395 break :blk mem.alignForward(l, mem.page_size);398
399 pub const ThreadHandle = i32;
400 pub const ThreadId = ThreadHandle;
401
402 threadlocal var tls_thread_id: ?ThreadId = null;
403
404 fn getCurrentId() ThreadId {
405 return tls_thread_id orelse {
406 const tid = linux.gettid();
407 tls_thread_id = tid;
408 return tid;
409 };
410 }
411
412 fn getCpuCount() !usize {
413 const cpu_set = try os.sched_getaffinity(0);
414 return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast
415 }
416
417 thread: *ThreadCompletion,
418
419 const ThreadCompletion = struct {
420 completion: Completion = Completion.init(.running),
421 child_tid: Atomic(i32) = Atomic(i32).init(0),
422 parent_tid: i32 = undefined,
423 mapped: []align(std.mem.page_size) u8,
396 };424 };
397425
398 const mmap_slice = mem: {426 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
399 // Map the whole stack with no rw permissions to avoid427 const Args = @TypeOf(args);
400 // committing the whole region right away428 const Instance = struct {
401 const mmap_slice = os.mmap(429 fn_args: Args,
430 thread: ThreadCompletion,
431
432 fn entryFn(raw_arg: usize) callconv(.C) u8 {
433 const self = @intToPtr(*@This(), raw_arg);
434 defer switch (self.thread.completion.swap(.completed, .Acquire)) {
435 .running => {},
436 .completed => unreachable,
437 .detached => {
438 const memory = self.thread.mapped;
439 __unmap_and_exit(@ptrToInt(memory.ptr), memory.len);
440 },
441 };
442 return callFn(f, self.fn_args);
443 }
444 };
445
446 var guard_offset: usize = undefined;
447 var stack_offset: usize = undefined;
448 var tls_offset: usize = undefined;
449 var instance_offset: usize = undefined;
450
451 const map_bytes = blk: {
452 var bytes: usize = std.mem.page_size;
453 guard_offset = bytes;
454
455 bytes += std.math.max(std.mem.page_size, config.stack_size);
456 bytes = std.mem.alignForward(bytes, std.mem.page_size);
457 stack_offset = bytes;
458
459 bytes = std.mem.alignForward(bytes, linux.tls.tls_image.alloc_align);
460 tls_offset = bytes;
461 bytes += linux.tls.tls_image.alloc_size;
462
463 bytes = std.mem.alignForward(bytes, @alignOf(Instance));
464 instance_offset = bytes;
465 bytes += @sizeOf(Instance);
466
467 bytes = std.mem.alignForward(bytes, std.mem.page_size);
468 break :blk bytes;
469 };
470
471 // map all memory needed without read/write permissions
472 // to avoid committing the whole region right away
473 const mapped = os.mmap(
402 null,474 null,
403 mmap_len,475 map_bytes,
404 os.PROT_NONE,476 os.PROT_NONE,
405 os.MAP_PRIVATE | os.MAP_ANONYMOUS,477 os.MAP_PRIVATE | os.MAP_ANONYMOUS,
406 -1,478 -1,
...@@ -411,175 +483,170 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF...@@ -411,175 +483,170 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF
411 error.PermissionDenied => unreachable,483 error.PermissionDenied => unreachable,
412 else => |e| return e,484 else => |e| return e,
413 };485 };
414 errdefer os.munmap(mmap_slice);486 errdefer os.munmap(mapped);
415487
416 // Map everything but the guard page as rw488 // map everything but the guard page as read/write
417 os.mprotect(489 os.mprotect(
418 mmap_slice[guard_end_offset..],490 mapped[guard_offset..],
419 os.PROT_READ | os.PROT_WRITE,491 os.PROT_READ | os.PROT_WRITE,
420 ) catch |err| switch (err) {492 ) catch |err| switch (err) {
421 error.AccessDenied => unreachable,493 error.AccessDenied => unreachable,
422 else => |e| return e,494 else => |e| return e,
423 };495 };
424496
425 break :mem mmap_slice;497 // Prepare the TLS segment and prepare a user_desc struct when needed on i386
426 };498 var tls_ptr = os.linux.tls.prepareTLS(mapped[tls_offset..]);
427499 var user_desc: if (target.cpu.arch == .i386) os.linux.user_desc else void = undefined;
428 const mmap_addr = @ptrToInt(mmap_slice.ptr);500 if (target.cpu.arch == .i386) {
429501 defer tls_ptr = @ptrToInt(&user_desc);
430 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset));502 user_desc = .{
431 thread_ptr.data.memory = mmap_slice;503 .entry_number = os.linux.tls.tls_image.gdt_entry_number,
504 .base_addr = tks_ptr,
505 .limit = 0xfffff,
506 .seg_32bit = 1,
507 .contents = 0, // Data
508 .read_exec_only = 0,
509 .limit_in_pages = 1,
510 .seg_not_present = 0,
511 .useable = 1,
512 };
513 }
432514
433 var arg: usize = undefined;515 const instance = @ptrCast(*Instance, @alignCast(@alignOf(Instance), &mapped[instance_offset]));
434 if (@sizeOf(Context) != 0) {516 instance.* = .{
435 arg = mmap_addr + context_start_offset;517 .fn_args = args,
436 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg));518 .thread = .{ .mapped = .mapped },
437 context_ptr.* = context;519 };
438 }
439520
440 if (std.Target.current.os.tag == .linux) {
441 const flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES |521 const flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES |
442 os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM |522 os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM |
443 os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID |523 os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID |
444 os.CLONE_DETACHED | os.CLONE_SETTLS;524 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 };
469525
470 const rc = os.linux.clone(526 return switch (linux.getErrno(linux.clone(
471 MainFuncs.linuxThreadMain,527 Instance.entryFn,
472 mmap_addr + stack_end_offset,528 @ptrToInt(&mapped[stack_offset]),
473 flags,529 flags,
474 arg,530 @ptrToInt(instance),
475 &thread_ptr.data.handle,531 &instance.thread.parent_tid,
476 newtls,532 tls_ptr,
477 &thread_ptr.data.handle,533 &instance.thread.child_tid.value,
478 );534 ))) {
479 switch (os.errno(rc)) {535 0 => .{ .thread = &instance.thread },
480 0 => return thread_ptr,536 os.EAGAIN => error.ThreadQuotaExceeded,
481 os.EAGAIN => return error.ThreadQuotaExceeded,
482 os.EINVAL => unreachable,537 os.EINVAL => unreachable,
483 os.ENOMEM => return error.SystemResources,538 os.ENOMEM => error.SystemResources,
484 os.ENOSPC => unreachable,539 os.ENOSPC => unreachable,
485 os.EPERM => unreachable,540 os.EPERM => unreachable,
486 os.EUSERS => unreachable,541 os.EUSERS => unreachable,
487 else => |err| return os.unexpectedErrno(err),542 else => |err| os.unexpectedErrno(err),
488 }543 };
489 } else {
490 @compileError("Unsupported OS");
491 }544 }
492}
493545
494pub const CpuCountError = error{546 fn getHandle(self: Impl) ThreadHandle {
495 PermissionDenied,547 return self.thread.parent_tid;
496 SystemResources,548 }
497 Unexpected,
498};
499549
500pub fn cpuCount() CpuCountError!usize {550 fn detach(self: Impl) void {
501 switch (std.Target.current.os.tag) {551 switch (self.thread.completion.swap(.detached, .AcqRel)) {
502 .linux => {552 .running => {},
503 const cpu_set = try os.sched_getaffinity(0);553 .completed => self.join(),
504 return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast554 .detached => unreachable,
505 },555 }
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 },
536 }556 }
537}
538557
539pub fn getCurrentThreadId() u64 {558 fn join(self: Impl) void {
540 switch (std.Target.current.os.tag) {559 defer self.thread.free();
541 .linux => {560
542 // Use the syscall directly as musl doesn't provide a wrapper.561 var spin: u8 = 10;
543 return @bitCast(u32, os.linux.gettid());562 while (true) {
544 },563 const tid = self.thread.child_tid.load(.Acquire);
545 .windows => {564 if (tid == 0) {
546 return os.windows.kernel32.GetCurrentThreadId();565 break;
547 },566 }
548 .macos, .ios, .watchos, .tvos => {567
549 var thread_id: u64 = undefined;568 if (spin > 0) {
550 // Pass thread=null to get the current thread ID.569 spin -= 1;
551 assert(c.pthread_threadid_np(null, &thread_id) == 0);570 std.atomic.spinLoopHint();
552 return thread_id;571 continue;
553 },572 }
554 .dragonfly => {573
555 return @bitCast(u32, c.lwp_gettid());574 switch (linux.getErrno(linux.futex_wait(
556 },575 &self.thread.child_tid.value,
557 .netbsd => {576 linux.FUTEX_WAIT,
558 return @bitCast(u32, c._lwp_self());577 tid,
559 },578 null,
560 .freebsd => {579 ))) {
561 return @bitCast(u32, c.pthread_getthreadid_np());580 0 => continue,
562 },581 os.EINTR => continue,
563 .openbsd => {582 os.EAGAIN => continue,
564 return @bitCast(u32, c.getthrid());583 else => unreachable,
565 },584 }
566 .haiku => {585 }
567 return @bitCast(u32, c.find_thread(null));
568 },
569 else => {
570 @compileError("getCurrentThreadId not implemented for this platform");
571 },
572 }586 }
573}
574587
575test "std.Thread" {588 // Calls `munmap(ptr, len)` then `exit(1)` without touching the stack (which lives in `ptr`).
576 if (!builtin.single_threaded) {589 // Ported over from musl libc's pthread detached implementation.
577 _ = AutoResetEvent;590 extern fn __unmap_and_exit(ptr: usize, len: usize) callconv(.C) noreturn;
578 _ = Futex;591 comptime {
579 _ = ResetEvent;592 asm(switch (target.cpu.arch) {
580 _ = StaticResetEvent;593 .i386 => (
581 _ = Mutex;594 \\.text
582 _ = Semaphore;595 \\.global __unmap_and_exit
583 _ = Condition;596 \\.type __unmap_and_exit, @function
597 \\__unmap_and_exit:
598 \\ movl $91, %eax
599 \\ movl 4(%esp), %ebx
600 \\ movl 8(%esp), %ecx
601 \\ int $128
602 \\ xorl %ebx, %ebx
603 \\ movl $1, %eax
604 \\ int $128
605 ),
606 .x86_64 => (
607 \\.text
608 \\.global __unmap_and_exit
609 \\.type __unmap_and_exit, @function
610 \\__unmap_and_exit:
611 \\ movl $11, %eax
612 \\ syscall
613 \\ xor %rdi, %rdi
614 \\ movl $60, %eax
615 \\ syscall
616 ),
617 .arm, .armeb, .aarch64, .aarch64_be, .aarch64_32 => (
618 \\.text
619 \\.global __unmap_and_exit
620 \\.type __unmap_and_exit, @function
621 \\__unmap_and_exit:
622 \\ mov r7, #91
623 \\ svc 0
624 \\ mov r7, #1
625 \\ svc 0
626 ),
627 .mips, .mipsel, .mips64, .mips64el => (
628 \\.set noreorder
629 \\.global __unmap_and_exit
630 \\.type __unmap_and_exit, @function
631 \\__unmap_and_exit:
632 \\ li $2, 4091
633 \\ syscall
634 \\ li $4, 0
635 \\ li $2, 4001
636 \\ syscall
637 ),
638 .powerpc, .powerpc64, .powerpc64le => (
639 \\.text
640 \\.global __unmap_and_exit
641 \\.type __unmap_and_exit, @function
642 \\__unmap_and_exit:
643 \\ li 0, 91
644 \\ sc
645 \\ li 0, 1
646 \\ sc
647 \\ blr
648 ),
649 else => @compileError("Platform not supported"),
650 });
584 }651 }
585}652};
\ No newline at end of file