authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-04-29 02:40:22-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-04-29 02:40:22-04:00
log6376d96824c5205ecc02b2c621bcef5dc78f1a81
treeff00e84ddd0b007e0110faf3f28f276df4458cf2
parentbf8e419d2b7853f5cb5aba4dcba45ae28a3840aa

support kernel threads for windows

* remove std.os.spawnThreadAllocator - windows does not support an explicit stack, so using an allocator for a thread stack space does not work. * std.os.spawnThread - instead of accepting a stack argument, the implementation will directly allocate using OS-specific APIs.

6 files changed, 120 insertions(+), 88 deletions(-)

std/atomic/queue.zig+2-6
......@@ -53,10 +53,6 @@ const puts_per_thread = 10000;
5353const put_thread_count = 3;
5454
5555test "std.atomic.queue" {
56 if (builtin.os == builtin.Os.windows) {
57 // TODO implement kernel threads for windows
58 return;
59 }
6056 var direct_allocator = std.heap.DirectAllocator.init();
6157 defer direct_allocator.deinit();
6258
......@@ -79,11 +75,11 @@ test "std.atomic.queue" {
7975
8076 var putters: [put_thread_count]&std.os.Thread = undefined;
8177 for (putters) |*t| {
82 *t = try std.os.spawnThreadAllocator(a, &context, startPuts);
78 *t = try std.os.spawnThread(&context, startPuts);
8379 }
8480 var getters: [put_thread_count]&std.os.Thread = undefined;
8581 for (getters) |*t| {
86 *t = try std.os.spawnThreadAllocator(a, &context, startGets);
82 *t = try std.os.spawnThread(&context, startGets);
8783 }
8884
8985 for (putters) |t| t.wait();
std/atomic/stack.zig+2-6
......@@ -60,10 +60,6 @@ const puts_per_thread = 1000;
6060const put_thread_count = 3;
6161
6262test "std.atomic.stack" {
63 if (builtin.os == builtin.Os.windows) {
64 // TODO implement kernel threads for windows
65 return;
66 }
6763 var direct_allocator = std.heap.DirectAllocator.init();
6864 defer direct_allocator.deinit();
6965
......@@ -85,11 +81,11 @@ test "std.atomic.stack" {
8581
8682 var putters: [put_thread_count]&std.os.Thread = undefined;
8783 for (putters) |*t| {
88 *t = try std.os.spawnThreadAllocator(a, &context, startPuts);
84 *t = try std.os.spawnThread(&context, startPuts);
8985 }
9086 var getters: [put_thread_count]&std.os.Thread = undefined;
9187 for (getters) |*t| {
92 *t = try std.os.spawnThreadAllocator(a, &context, startGets);
88 *t = try std.os.spawnThread(&context, startGets);
9389 }
9490
9591 for (putters) |t| t.wait();
std/mem.zig+1
......@@ -32,6 +32,7 @@ pub const Allocator = struct {
3232 freeFn: fn (self: &Allocator, old_mem: []u8) void,
3333
3434 fn create(self: &Allocator, comptime T: type) !&T {
35 if (@sizeOf(T) == 0) return &{};
3536 const slice = try self.alloc(T, 1);
3637 return &slice[0];
3738 }
std/os/index.zig+105-60
......@@ -2347,18 +2347,30 @@ pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void {
23472347}
23482348
23492349pub const Thread = struct {
2350 pid: pid_t,
2351 allocator: ?&mem.Allocator,
2352 stack: []u8,
2353 pthread_handle: pthread_t,
2350 data: Data,
23542351
23552352 pub const use_pthreads = is_posix and builtin.link_libc;
2356 const pthread_t = if (use_pthreads) c.pthread_t else void;
2357 const pid_t = if (!use_pthreads) i32 else void;
2353 const Data = if (use_pthreads) struct {
2354 handle: c.pthread_t,
2355 stack_addr: usize,
2356 stack_len: usize,
2357 } else switch (builtin.os) {
2358 builtin.Os.linux => struct {
2359 pid: i32,
2360 stack_addr: usize,
2361 stack_len: usize,
2362 },
2363 builtin.Os.windows => struct {
2364 handle: windows.HANDLE,
2365 alloc_start: &c_void,
2366 heap_handle: windows.HANDLE,
2367 },
2368 else => @compileError("Unsupported OS"),
2369 };
23582370
23592371 pub fn wait(self: &const Thread) void {
23602372 if (use_pthreads) {
2361 const err = c.pthread_join(self.pthread_handle, null);
2373 const err = c.pthread_join(self.data.handle, null);
23622374 switch (err) {
23632375 0 => {},
23642376 posix.EINVAL => unreachable,
......@@ -2366,23 +2378,27 @@ pub const Thread = struct {
23662378 posix.EDEADLK => unreachable,
23672379 else => unreachable,
23682380 }
2369 } else if (builtin.os == builtin.Os.linux) {
2370 while (true) {
2371 const pid_value = @atomicLoad(i32, &self.pid, builtin.AtomicOrder.SeqCst);
2372 if (pid_value == 0) break;
2373 const rc = linux.futex_wait(@ptrToInt(&self.pid), linux.FUTEX_WAIT, pid_value, null);
2374 switch (linux.getErrno(rc)) {
2375 0 => continue,
2376 posix.EINTR => continue,
2377 posix.EAGAIN => continue,
2378 else => unreachable,
2381 assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0);
2382 } else switch (builtin.os) {
2383 builtin.Os.linux => {
2384 while (true) {
2385 const pid_value = @atomicLoad(i32, &self.data.pid, builtin.AtomicOrder.SeqCst);
2386 if (pid_value == 0) break;
2387 const rc = linux.futex_wait(@ptrToInt(&self.data.pid), linux.FUTEX_WAIT, pid_value, null);
2388 switch (linux.getErrno(rc)) {
2389 0 => continue,
2390 posix.EINTR => continue,
2391 posix.EAGAIN => continue,
2392 else => unreachable,
2393 }
23792394 }
2380 }
2381 } else {
2382 @compileError("Unsupported OS");
2383 }
2384 if (self.allocator) |a| {
2385 a.free(self.stack);
2395 assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0);
2396 },
2397 builtin.Os.windows => {
2398 assert(windows.WaitForSingleObject(self.data.handle, windows.INFINITE) == windows.WAIT_OBJECT_0);
2399 assert(windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start) != 0);
2400 },
2401 else => @compileError("Unsupported OS"),
23862402 }
23872403 }
23882404};
......@@ -2407,52 +2423,60 @@ pub const SpawnThreadError = error {
24072423 /// be copied.
24082424 SystemResources,
24092425
2410 /// pthreads requires at least 16384 bytes of stack space
2411 StackTooSmall,
2426 /// Not enough userland memory to spawn the thread.
2427 OutOfMemory,
24122428
24132429 Unexpected,
24142430};
24152431
2416pub const SpawnThreadAllocatorError = SpawnThreadError || error{OutOfMemory};
2417
24182432/// caller must call wait on the returned thread
24192433/// fn startFn(@typeOf(context)) T
24202434/// where T is u8, noreturn, void, or !void
2421pub fn spawnThreadAllocator(allocator: &mem.Allocator, context: var, comptime startFn: var) SpawnThreadAllocatorError!&Thread {
2435/// caller must call wait on the returned thread
2436pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread {
24222437 // TODO compile-time call graph analysis to determine stack upper bound
24232438 // https://github.com/zig-lang/zig/issues/157
24242439 const default_stack_size = 8 * 1024 * 1024;
2425 const stack_bytes = try allocator.alignedAlloc(u8, os.page_size, default_stack_size);
2426 const thread = try spawnThread(stack_bytes, context, startFn);
2427 thread.allocator = allocator;
2428 return thread;
2429}
24302440
2431/// stack must be big enough to store one Thread and one @typeOf(context), each with default alignment, at the end
2432/// fn startFn(@typeOf(context)) T
2433/// where T is u8, noreturn, void, or !void
2434/// caller must call wait on the returned thread
2435pub fn spawnThread(stack: []align(os.page_size) u8, context: var, comptime startFn: var) SpawnThreadError!&Thread {
24362441 const Context = @typeOf(context);
24372442 comptime assert(@ArgType(@typeOf(startFn), 0) == Context);
24382443
2439 var stack_end: usize = @ptrToInt(stack.ptr) + stack.len;
2440 var arg: usize = undefined;
2441 if (@sizeOf(Context) != 0) {
2442 stack_end -= @sizeOf(Context);
2443 stack_end -= stack_end % @alignOf(Context);
2444 assert(stack_end >= @ptrToInt(stack.ptr));
2445 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(&Context, stack_end));
2446 *context_ptr = context;
2447 arg = stack_end;
2448 }
2444 if (builtin.os == builtin.Os.windows) {
2445 const WinThread = struct {
2446 const OuterContext = struct {
2447 thread: Thread,
2448 inner: Context,
2449 };
2450 extern fn threadMain(arg: windows.LPVOID) windows.DWORD {
2451 if (@sizeOf(Context) == 0) {
2452 return startFn({});
2453 } else {
2454 return startFn(*@ptrCast(&Context, @alignCast(@alignOf(Context), arg)));
2455 }
2456 }
2457 };
24492458
2450 stack_end -= @sizeOf(Thread);
2451 stack_end -= stack_end % @alignOf(Thread);
2452 assert(stack_end >= @ptrToInt(stack.ptr));
2453 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(&Thread, stack_end));
2454 thread_ptr.stack = stack;
2455 thread_ptr.allocator = null;
2459 const heap_handle = windows.GetProcessHeap() ?? return SpawnThreadError.OutOfMemory;
2460 const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext);
2461 const bytes_ptr = windows.HeapAlloc(heap_handle, 0, byte_count) ?? return SpawnThreadError.OutOfMemory;
2462 errdefer assert(windows.HeapFree(heap_handle, 0, bytes_ptr) != 0);
2463 const bytes = @ptrCast(&u8, bytes_ptr)[0..byte_count];
2464 const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable;
2465 outer_context.inner = context;
2466 outer_context.thread.data.heap_handle = heap_handle;
2467 outer_context.thread.data.alloc_start = bytes_ptr;
2468
2469 const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(&c_void, &outer_context.inner);
2470 outer_context.thread.data.handle = windows.CreateThread(null, default_stack_size, WinThread.threadMain,
2471 parameter, 0, null) ??
2472 {
2473 const err = windows.GetLastError();
2474 return switch (err) {
2475 else => os.unexpectedErrorWindows(err),
2476 };
2477 };
2478 return &outer_context.thread;
2479 }
24562480
24572481 const MainFuncs = struct {
24582482 extern fn linuxThreadMain(ctx_addr: usize) u8 {
......@@ -2473,6 +2497,29 @@ pub fn spawnThread(stack: []align(os.page_size) u8, context: var, comptime start
24732497 }
24742498 };
24752499
2500 const stack_len = default_stack_size;
2501 const stack_addr = posix.mmap(null, stack_len, posix.PROT_READ|posix.PROT_WRITE,
2502 posix.MAP_PRIVATE|posix.MAP_ANONYMOUS|posix.MAP_GROWSDOWN, -1, 0);
2503 if (stack_addr == posix.MAP_FAILED) return error.OutOfMemory;
2504 errdefer _ = posix.munmap(stack_addr, stack_len);
2505
2506 var stack_end: usize = stack_addr + stack_len;
2507 var arg: usize = undefined;
2508 if (@sizeOf(Context) != 0) {
2509 stack_end -= @sizeOf(Context);
2510 stack_end -= stack_end % @alignOf(Context);
2511 assert(stack_end >= stack_addr);
2512 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(&Context, stack_end));
2513 *context_ptr = context;
2514 arg = stack_end;
2515 }
2516
2517 stack_end -= @sizeOf(Thread);
2518 stack_end -= stack_end % @alignOf(Thread);
2519 assert(stack_end >= stack_addr);
2520 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(&Thread, stack_end));
2521
2522
24762523 if (builtin.os == builtin.Os.windows) {
24772524 // use windows API directly
24782525 @compileError("TODO support spawnThread for Windows");
......@@ -2484,14 +2531,12 @@ pub fn spawnThread(stack: []align(os.page_size) u8, context: var, comptime start
24842531
24852532 // align to page
24862533 stack_end -= stack_end % os.page_size;
2534 assert(c.pthread_attr_setstack(&attr, @intToPtr(&c_void, stack_addr), stack_len) == 0);
24872535
2488 const stack_size = stack_end - @ptrToInt(stack.ptr);
2489 const setstack_err = c.pthread_attr_setstack(&attr, @ptrCast(&c_void, stack.ptr), stack_size);
2490 if (setstack_err != 0) {
2491 return SpawnThreadError.StackTooSmall; // pthreads requires at least 16384 bytes
2492 }
2536 thread_ptr.data.stack_addr = stack_addr;
2537 thread_ptr.data.stack_len = stack_len;
24932538
2494 const err = c.pthread_create(&thread_ptr.pthread_handle, &attr, MainFuncs.posixThreadMain, @intToPtr(&c_void, arg));
2539 const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(&c_void, arg));
24952540 switch (err) {
24962541 0 => return thread_ptr,
24972542 posix.EAGAIN => return SpawnThreadError.SystemResources,
std/os/test.zig+4-16
......@@ -44,24 +44,12 @@ test "access file" {
4444}
4545
4646test "spawn threads" {
47 if (builtin.os == builtin.Os.windows) {
48 // TODO implement threads on windows
49 return;
50 }
51
52 var direct_allocator = std.heap.DirectAllocator.init();
53 defer direct_allocator.deinit();
54
5547 var shared_ctx: i32 = 1;
5648
57 const thread1 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, {}, start1);
58 const thread4 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, &shared_ctx, start2);
59
60 var stack1: [20 * 1024]u8 align(os.page_size) = undefined;
61 var stack2: [20 * 1024]u8 align(os.page_size) = undefined;
62
63 const thread2 = try std.os.spawnThread(stack1[0..], &shared_ctx, start2);
64 const thread3 = try std.os.spawnThread(stack2[0..], &shared_ctx, start2);
49 const thread1 = try std.os.spawnThread({}, start1);
50 const thread2 = try std.os.spawnThread(&shared_ctx, start2);
51 const thread3 = try std.os.spawnThread(&shared_ctx, start2);
52 const thread4 = try std.os.spawnThread(&shared_ctx, start2);
6553
6654 thread1.wait();
6755 thread2.wait();
std/os/windows/index.zig+6
......@@ -28,6 +28,9 @@ pub extern "kernel32" stdcallcc fn CreateProcessA(lpApplicationName: ?LPCSTR, lp
2828pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA(lpSymlinkFileName: LPCSTR, lpTargetFileName: LPCSTR,
2929 dwFlags: DWORD) BOOLEAN;
3030
31
32pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE;
33
3134pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL;
3235
3336pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
......@@ -318,6 +321,9 @@ pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000;
318321pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004;
319322pub const HEAP_NO_SERIALIZE = 0x00000001;
320323
324pub const PTHREAD_START_ROUTINE = extern fn(LPVOID) DWORD;
325pub const LPTHREAD_START_ROUTINE = PTHREAD_START_ROUTINE;
326
321327test "import" {
322328 _ = @import("util.zig");
323329}