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;...@@ -53,10 +53,6 @@ const puts_per_thread = 10000;
53const put_thread_count = 3;53const put_thread_count = 3;
5454
55test "std.atomic.queue" {55test "std.atomic.queue" {
56 if (builtin.os == builtin.Os.windows) {
57 // TODO implement kernel threads for windows
58 return;
59 }
60 var direct_allocator = std.heap.DirectAllocator.init();56 var direct_allocator = std.heap.DirectAllocator.init();
61 defer direct_allocator.deinit();57 defer direct_allocator.deinit();
6258
...@@ -79,11 +75,11 @@ test "std.atomic.queue" {...@@ -79,11 +75,11 @@ test "std.atomic.queue" {
7975
80 var putters: [put_thread_count]&std.os.Thread = undefined;76 var putters: [put_thread_count]&std.os.Thread = undefined;
81 for (putters) |*t| {77 for (putters) |*t| {
82 *t = try std.os.spawnThreadAllocator(a, &context, startPuts);78 *t = try std.os.spawnThread(&context, startPuts);
83 }79 }
84 var getters: [put_thread_count]&std.os.Thread = undefined;80 var getters: [put_thread_count]&std.os.Thread = undefined;
85 for (getters) |*t| {81 for (getters) |*t| {
86 *t = try std.os.spawnThreadAllocator(a, &context, startGets);82 *t = try std.os.spawnThread(&context, startGets);
87 }83 }
8884
89 for (putters) |t| t.wait();85 for (putters) |t| t.wait();
std/atomic/stack.zig+2-6
...@@ -60,10 +60,6 @@ const puts_per_thread = 1000;...@@ -60,10 +60,6 @@ const puts_per_thread = 1000;
60const put_thread_count = 3;60const put_thread_count = 3;
6161
62test "std.atomic.stack" {62test "std.atomic.stack" {
63 if (builtin.os == builtin.Os.windows) {
64 // TODO implement kernel threads for windows
65 return;
66 }
67 var direct_allocator = std.heap.DirectAllocator.init();63 var direct_allocator = std.heap.DirectAllocator.init();
68 defer direct_allocator.deinit();64 defer direct_allocator.deinit();
6965
...@@ -85,11 +81,11 @@ test "std.atomic.stack" {...@@ -85,11 +81,11 @@ test "std.atomic.stack" {
8581
86 var putters: [put_thread_count]&std.os.Thread = undefined;82 var putters: [put_thread_count]&std.os.Thread = undefined;
87 for (putters) |*t| {83 for (putters) |*t| {
88 *t = try std.os.spawnThreadAllocator(a, &context, startPuts);84 *t = try std.os.spawnThread(&context, startPuts);
89 }85 }
90 var getters: [put_thread_count]&std.os.Thread = undefined;86 var getters: [put_thread_count]&std.os.Thread = undefined;
91 for (getters) |*t| {87 for (getters) |*t| {
92 *t = try std.os.spawnThreadAllocator(a, &context, startGets);88 *t = try std.os.spawnThread(&context, startGets);
93 }89 }
9490
95 for (putters) |t| t.wait();91 for (putters) |t| t.wait();
std/mem.zig+1
...@@ -32,6 +32,7 @@ pub const Allocator = struct {...@@ -32,6 +32,7 @@ pub const Allocator = struct {
32 freeFn: fn (self: &Allocator, old_mem: []u8) void,32 freeFn: fn (self: &Allocator, old_mem: []u8) void,
3333
34 fn create(self: &Allocator, comptime T: type) !&T {34 fn create(self: &Allocator, comptime T: type) !&T {
35 if (@sizeOf(T) == 0) return &{};
35 const slice = try self.alloc(T, 1);36 const slice = try self.alloc(T, 1);
36 return &slice[0];37 return &slice[0];
37 }38 }
std/os/index.zig+105-60
...@@ -2347,18 +2347,30 @@ pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void {...@@ -2347,18 +2347,30 @@ pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void {
2347}2347}
23482348
2349pub const Thread = struct {2349pub const Thread = struct {
2350 pid: pid_t,2350 data: Data,
2351 allocator: ?&mem.Allocator,
2352 stack: []u8,
2353 pthread_handle: pthread_t,
23542351
2355 pub const use_pthreads = is_posix and builtin.link_libc;2352 pub const use_pthreads = is_posix and builtin.link_libc;
2356 const pthread_t = if (use_pthreads) c.pthread_t else void;2353 const Data = if (use_pthreads) struct {
2357 const pid_t = if (!use_pthreads) i32 else void;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
2359 pub fn wait(self: &const Thread) void {2371 pub fn wait(self: &const Thread) void {
2360 if (use_pthreads) {2372 if (use_pthreads) {
2361 const err = c.pthread_join(self.pthread_handle, null);2373 const err = c.pthread_join(self.data.handle, null);
2362 switch (err) {2374 switch (err) {
2363 0 => {},2375 0 => {},
2364 posix.EINVAL => unreachable,2376 posix.EINVAL => unreachable,
...@@ -2366,23 +2378,27 @@ pub const Thread = struct {...@@ -2366,23 +2378,27 @@ pub const Thread = struct {
2366 posix.EDEADLK => unreachable,2378 posix.EDEADLK => unreachable,
2367 else => unreachable,2379 else => unreachable,
2368 }2380 }
2369 } else if (builtin.os == builtin.Os.linux) {2381 assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0);
2370 while (true) {2382 } else switch (builtin.os) {
2371 const pid_value = @atomicLoad(i32, &self.pid, builtin.AtomicOrder.SeqCst);2383 builtin.Os.linux => {
2372 if (pid_value == 0) break;2384 while (true) {
2373 const rc = linux.futex_wait(@ptrToInt(&self.pid), linux.FUTEX_WAIT, pid_value, null);2385 const pid_value = @atomicLoad(i32, &self.data.pid, builtin.AtomicOrder.SeqCst);
2374 switch (linux.getErrno(rc)) {2386 if (pid_value == 0) break;
2375 0 => continue,2387 const rc = linux.futex_wait(@ptrToInt(&self.data.pid), linux.FUTEX_WAIT, pid_value, null);
2376 posix.EINTR => continue,2388 switch (linux.getErrno(rc)) {
2377 posix.EAGAIN => continue,2389 0 => continue,
2378 else => unreachable,2390 posix.EINTR => continue,
2391 posix.EAGAIN => continue,
2392 else => unreachable,
2393 }
2379 }2394 }
2380 }2395 assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0);
2381 } else {2396 },
2382 @compileError("Unsupported OS");2397 builtin.Os.windows => {
2383 }2398 assert(windows.WaitForSingleObject(self.data.handle, windows.INFINITE) == windows.WAIT_OBJECT_0);
2384 if (self.allocator) |a| {2399 assert(windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start) != 0);
2385 a.free(self.stack);2400 },
2401 else => @compileError("Unsupported OS"),
2386 }2402 }
2387 }2403 }
2388};2404};
...@@ -2407,52 +2423,60 @@ pub const SpawnThreadError = error {...@@ -2407,52 +2423,60 @@ pub const SpawnThreadError = error {
2407 /// be copied.2423 /// be copied.
2408 SystemResources,2424 SystemResources,
24092425
2410 /// pthreads requires at least 16384 bytes of stack space2426 /// Not enough userland memory to spawn the thread.
2411 StackTooSmall,2427 OutOfMemory,
24122428
2413 Unexpected,2429 Unexpected,
2414};2430};
24152431
2416pub const SpawnThreadAllocatorError = SpawnThreadError || error{OutOfMemory};
2417
2418/// caller must call wait on the returned thread2432/// caller must call wait on the returned thread
2419/// fn startFn(@typeOf(context)) T2433/// fn startFn(@typeOf(context)) T
2420/// where T is u8, noreturn, void, or !void2434/// 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 {
2422 // TODO compile-time call graph analysis to determine stack upper bound2437 // TODO compile-time call graph analysis to determine stack upper bound
2423 // https://github.com/zig-lang/zig/issues/1572438 // https://github.com/zig-lang/zig/issues/157
2424 const default_stack_size = 8 * 1024 * 1024;2439 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 {
2436 const Context = @typeOf(context);2441 const Context = @typeOf(context);
2437 comptime assert(@ArgType(@typeOf(startFn), 0) == Context);2442 comptime assert(@ArgType(@typeOf(startFn), 0) == Context);
24382443
2439 var stack_end: usize = @ptrToInt(stack.ptr) + stack.len;2444 if (builtin.os == builtin.Os.windows) {
2440 var arg: usize = undefined;2445 const WinThread = struct {
2441 if (@sizeOf(Context) != 0) {2446 const OuterContext = struct {
2442 stack_end -= @sizeOf(Context);2447 thread: Thread,
2443 stack_end -= stack_end % @alignOf(Context);2448 inner: Context,
2444 assert(stack_end >= @ptrToInt(stack.ptr));2449 };
2445 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(&Context, stack_end));2450 extern fn threadMain(arg: windows.LPVOID) windows.DWORD {
2446 *context_ptr = context;2451 if (@sizeOf(Context) == 0) {
2447 arg = stack_end;2452 return startFn({});
2448 }2453 } else {
2454 return startFn(*@ptrCast(&Context, @alignCast(@alignOf(Context), arg)));
2455 }
2456 }
2457 };
24492458
2450 stack_end -= @sizeOf(Thread);2459 const heap_handle = windows.GetProcessHeap() ?? return SpawnThreadError.OutOfMemory;
2451 stack_end -= stack_end % @alignOf(Thread);2460 const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext);
2452 assert(stack_end >= @ptrToInt(stack.ptr));2461 const bytes_ptr = windows.HeapAlloc(heap_handle, 0, byte_count) ?? return SpawnThreadError.OutOfMemory;
2453 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(&Thread, stack_end));2462 errdefer assert(windows.HeapFree(heap_handle, 0, bytes_ptr) != 0);
2454 thread_ptr.stack = stack;2463 const bytes = @ptrCast(&u8, bytes_ptr)[0..byte_count];
2455 thread_ptr.allocator = null;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
2457 const MainFuncs = struct {2481 const MainFuncs = struct {
2458 extern fn linuxThreadMain(ctx_addr: usize) u8 {2482 extern fn linuxThreadMain(ctx_addr: usize) u8 {
...@@ -2473,6 +2497,29 @@ pub fn spawnThread(stack: []align(os.page_size) u8, context: var, comptime start...@@ -2473,6 +2497,29 @@ pub fn spawnThread(stack: []align(os.page_size) u8, context: var, comptime start
2473 }2497 }
2474 };2498 };
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
2476 if (builtin.os == builtin.Os.windows) {2523 if (builtin.os == builtin.Os.windows) {
2477 // use windows API directly2524 // use windows API directly
2478 @compileError("TODO support spawnThread for Windows");2525 @compileError("TODO support spawnThread for Windows");
...@@ -2484,14 +2531,12 @@ pub fn spawnThread(stack: []align(os.page_size) u8, context: var, comptime start...@@ -2484,14 +2531,12 @@ pub fn spawnThread(stack: []align(os.page_size) u8, context: var, comptime start
24842531
2485 // align to page2532 // align to page
2486 stack_end -= stack_end % os.page_size;2533 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);2536 thread_ptr.data.stack_addr = stack_addr;
2489 const setstack_err = c.pthread_attr_setstack(&attr, @ptrCast(&c_void, stack.ptr), stack_size);2537 thread_ptr.data.stack_len = stack_len;
2490 if (setstack_err != 0) {
2491 return SpawnThreadError.StackTooSmall; // pthreads requires at least 16384 bytes
2492 }
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));
2495 switch (err) {2540 switch (err) {
2496 0 => return thread_ptr,2541 0 => return thread_ptr,
2497 posix.EAGAIN => return SpawnThreadError.SystemResources,2542 posix.EAGAIN => return SpawnThreadError.SystemResources,
std/os/test.zig+4-16
...@@ -44,24 +44,12 @@ test "access file" {...@@ -44,24 +44,12 @@ test "access file" {
44}44}
4545
46test "spawn threads" {46test "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
55 var shared_ctx: i32 = 1;47 var shared_ctx: i32 = 1;
5648
57 const thread1 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, {}, start1);49 const thread1 = try std.os.spawnThread({}, start1);
58 const thread4 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, &shared_ctx, start2);50 const thread2 = try std.os.spawnThread(&shared_ctx, start2);
5951 const thread3 = try std.os.spawnThread(&shared_ctx, start2);
60 var stack1: [20 * 1024]u8 align(os.page_size) = undefined;52 const thread4 = try std.os.spawnThread(&shared_ctx, start2);
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);
6553
66 thread1.wait();54 thread1.wait();
67 thread2.wait();55 thread2.wait();
std/os/windows/index.zig+6
...@@ -28,6 +28,9 @@ pub extern "kernel32" stdcallcc fn CreateProcessA(lpApplicationName: ?LPCSTR, lp...@@ -28,6 +28,9 @@ pub extern "kernel32" stdcallcc fn CreateProcessA(lpApplicationName: ?LPCSTR, lp
28pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA(lpSymlinkFileName: LPCSTR, lpTargetFileName: LPCSTR,28pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA(lpSymlinkFileName: LPCSTR, lpTargetFileName: LPCSTR,
29 dwFlags: DWORD) BOOLEAN;29 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
31pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL;34pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL;
3235
33pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;36pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
...@@ -318,6 +321,9 @@ pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000;...@@ -318,6 +321,9 @@ pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000;
318pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004;321pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004;
319pub const HEAP_NO_SERIALIZE = 0x00000001;322pub const HEAP_NO_SERIALIZE = 0x00000001;
320323
324pub const PTHREAD_START_ROUTINE = extern fn(LPVOID) DWORD;
325pub const LPTHREAD_START_ROUTINE = PTHREAD_START_ROUTINE;
326
321test "import" {327test "import" {
322 _ = @import("util.zig");328 _ = @import("util.zig");
323}329}