authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-04-14 02:12:19-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-04-14 02:24:05-04:00
logb7af9edb8a8802c35f1a460f8dafff8643b34639
tree7a50495c844c951b344806acab6b452986f03108
parentfa05cab01a755827209b6ede299402f515681a81

add std.os.createThread

this adds kernel thread support to the standard library for linux. See #174

7 files changed, 250 insertions(+), 14 deletions(-)

src/ir.cpp+1-1
......@@ -18407,6 +18407,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
1840718407 case IrInstructionIdAddImplicitReturnType:
1840818408 case IrInstructionIdMergeErrRetTraces:
1840918409 case IrInstructionIdMarkErrRetTracePtr:
18410 case IrInstructionIdAtomicRmw:
1841018411 return true;
1841118412
1841218413 case IrInstructionIdPhi:
......@@ -18487,7 +18488,6 @@ bool ir_has_side_effects(IrInstruction *instruction) {
1848718488 case IrInstructionIdCoroSize:
1848818489 case IrInstructionIdCoroSuspend:
1848918490 case IrInstructionIdCoroFree:
18490 case IrInstructionIdAtomicRmw:
1849118491 case IrInstructionIdCoroPromise:
1849218492 case IrInstructionIdPromiseResultType:
1849318493 return false;
std/fmt/index.zig+13-13
......@@ -8,25 +8,25 @@ const errol3 = @import("errol/index.zig").errol3;
88
99const max_int_digits = 65;
1010
11const State = enum { // TODO put inside format function and make sure the name and debug info is correct
12 Start,
13 OpenBrace,
14 CloseBrace,
15 Integer,
16 IntegerWidth,
17 Float,
18 FloatWidth,
19 Character,
20 Buf,
21 BufWidth,
22};
23
2411/// Renders fmt string with args, calling output with slices of bytes.
2512/// If `output` returns an error, the error is returned from `format` and
2613/// `output` is not called again.
2714pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8) Errors!void,
2815 comptime fmt: []const u8, args: ...) Errors!void
2916{
17 const State = enum {
18 Start,
19 OpenBrace,
20 CloseBrace,
21 Integer,
22 IntegerWidth,
23 Float,
24 FloatWidth,
25 Character,
26 Buf,
27 BufWidth,
28 };
29
3030 comptime var start_index = 0;
3131 comptime var state = State.Start;
3232 comptime var next_arg = 0;
std/os/index.zig+129
......@@ -2384,3 +2384,132 @@ pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void {
23842384 posix.ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
23852385 }
23862386}
2387
2388pub const Thread = struct {
2389 pid: i32,
2390 allocator: ?&mem.Allocator,
2391 stack: []u8,
2392
2393 pub fn wait(self: &const Thread) void {
2394 while (true) {
2395 const pid_value = self.pid; // TODO atomic load
2396 if (pid_value == 0) break;
2397 const rc = linux.futex_wait(@ptrToInt(&self.pid), linux.FUTEX_WAIT, pid_value, null);
2398 switch (linux.getErrno(rc)) {
2399 0 => continue,
2400 posix.EINTR => continue,
2401 posix.EAGAIN => continue,
2402 else => unreachable,
2403 }
2404 }
2405 if (self.allocator) |a| {
2406 a.free(self.stack);
2407 }
2408 }
2409};
2410
2411pub const SpawnThreadError = error {
2412 /// A system-imposed limit on the number of threads was encountered.
2413 /// There are a number of limits that may trigger this error:
2414 /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),
2415 /// which limits the number of processes and threads for a real
2416 /// user ID, was reached;
2417 /// * the kernel's system-wide limit on the number of processes and
2418 /// threads, /proc/sys/kernel/threads-max, was reached (see
2419 /// proc(5));
2420 /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was
2421 /// reached (see proc(5)); or
2422 /// * the PID limit (pids.max) imposed by the cgroup "process num‐
2423 /// ber" (PIDs) controller was reached.
2424 ThreadQuotaExceeded,
2425
2426 /// The kernel cannot allocate sufficient memory to allocate a task structure
2427 /// for the child, or to copy those parts of the caller's context that need to
2428 /// be copied.
2429 SystemResources,
2430
2431 Unexpected,
2432};
2433
2434pub const SpawnThreadAllocatorError = SpawnThreadError || error{OutOfMemory};
2435
2436/// caller must call wait on the returned thread
2437/// fn startFn(@typeOf(context)) T
2438/// where T is u8, noreturn, void, or !void
2439pub fn spawnThreadAllocator(allocator: &mem.Allocator, context: var, comptime startFn: var) SpawnThreadAllocatorError!&Thread {
2440 // TODO compile-time call graph analysis to determine stack upper bound
2441 // https://github.com/zig-lang/zig/issues/157
2442 const default_stack_size = 8 * 1024 * 1024;
2443 const stack_bytes = try allocator.alloc(u8, default_stack_size);
2444 const thread = try spawnThread(stack_bytes, context, startFn);
2445 thread.allocator = allocator;
2446 return thread;
2447}
2448
2449/// stack must be big enough to store one Thread and one @typeOf(context), each with default alignment, at the end
2450/// fn startFn(@typeOf(context)) T
2451/// where T is u8, noreturn, void, or !void
2452/// caller must call wait on the returned thread
2453pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThreadError!&Thread {
2454 const Context = @typeOf(context);
2455 comptime assert(@ArgType(@typeOf(startFn), 0) == Context);
2456
2457 var stack_end: usize = @ptrToInt(stack.ptr) + stack.len;
2458 var arg: usize = undefined;
2459 if (@sizeOf(Context) != 0) {
2460 stack_end -= @sizeOf(Context);
2461 stack_end -= stack_end % @alignOf(Context);
2462 assert(stack_end >= @ptrToInt(stack.ptr));
2463 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(&Context, stack_end));
2464 *context_ptr = context;
2465 arg = stack_end;
2466 }
2467
2468 stack_end -= @sizeOf(Thread);
2469 stack_end -= stack_end % @alignOf(Thread);
2470 assert(stack_end >= @ptrToInt(stack.ptr));
2471 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(&Thread, stack_end));
2472 thread_ptr.stack = stack;
2473 thread_ptr.allocator = null;
2474
2475 const threadMain = struct {
2476 extern fn threadMain(ctx_addr: usize) u8 {
2477 if (@sizeOf(Context) == 0) {
2478 return startFn({});
2479 } else {
2480 return startFn(*@intToPtr(&const Context, ctx_addr));
2481 }
2482 }
2483 }.threadMain;
2484
2485 const flags = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND
2486 | posix.CLONE_THREAD | posix.CLONE_SYSVSEM // | posix.CLONE_SETTLS
2487 | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED;
2488 const newtls: usize = 0;
2489 const rc = posix.clone(threadMain, stack_end, flags, arg, &thread_ptr.pid, newtls, &thread_ptr.pid);
2490 const err = posix.getErrno(rc);
2491 switch (err) {
2492 0 => return thread_ptr,
2493 posix.EAGAIN => return SpawnThreadError.ThreadQuotaExceeded,
2494 posix.EINVAL => unreachable,
2495 posix.ENOMEM => return SpawnThreadError.SystemResources,
2496 posix.ENOSPC => unreachable,
2497 posix.EPERM => unreachable,
2498 posix.EUSERS => unreachable,
2499 else => return unexpectedErrorPosix(err),
2500 }
2501}
2502
2503pub fn posixWait(pid: i32) i32 {
2504 var status: i32 = undefined;
2505 while (true) {
2506 const err = posix.getErrno(posix.waitpid(pid, &status, 0));
2507 switch (err) {
2508 0 => return status,
2509 posix.EINTR => continue,
2510 posix.ECHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
2511 posix.EINVAL => unreachable, // The options argument was invalid
2512 else => unreachable,
2513 }
2514 }
2515}
std/os/linux/index.zig+30
......@@ -14,6 +14,22 @@ pub const STDIN_FILENO = 0;
1414pub const STDOUT_FILENO = 1;
1515pub const STDERR_FILENO = 2;
1616
17pub const FUTEX_WAIT = 0;
18pub const FUTEX_WAKE = 1;
19pub const FUTEX_FD = 2;
20pub const FUTEX_REQUEUE = 3;
21pub const FUTEX_CMP_REQUEUE = 4;
22pub const FUTEX_WAKE_OP = 5;
23pub const FUTEX_LOCK_PI = 6;
24pub const FUTEX_UNLOCK_PI = 7;
25pub const FUTEX_TRYLOCK_PI = 8;
26pub const FUTEX_WAIT_BITSET = 9;
27
28pub const FUTEX_PRIVATE_FLAG = 128;
29
30pub const FUTEX_CLOCK_REALTIME = 256;
31
32
1733pub const PROT_NONE = 0;
1834pub const PROT_READ = 1;
1935pub const PROT_WRITE = 2;
......@@ -652,6 +668,10 @@ pub fn fork() usize {
652668 return syscall0(SYS_fork);
653669}
654670
671pub fn futex_wait(uaddr: usize, futex_op: u32, val: i32, timeout: ?&timespec) usize {
672 return syscall4(SYS_futex, uaddr, futex_op, @bitCast(u32, val), @ptrToInt(timeout));
673}
674
655675pub fn getcwd(buf: &u8, size: usize) usize {
656676 return syscall2(SYS_getcwd, @ptrToInt(buf), size);
657677}
......@@ -746,6 +766,16 @@ pub fn openat(dirfd: i32, path: &const u8, flags: usize, mode: usize) usize {
746766 return syscall4(SYS_openat, usize(dirfd), @ptrToInt(path), flags, mode);
747767}
748768
769/// See also `clone` (from the arch-specific include)
770pub fn clone5(flags: usize, child_stack_ptr: usize, parent_tid: &i32, child_tid: &i32, newtls: usize) usize {
771 return syscall5(SYS_clone, flags, child_stack_ptr, @ptrToInt(parent_tid), @ptrToInt(child_tid), newtls);
772}
773
774/// See also `clone` (from the arch-specific include)
775pub fn clone2(flags: usize, child_stack_ptr: usize) usize {
776 return syscall2(SYS_clone, flags, child_stack_ptr);
777}
778
749779pub fn close(fd: i32) usize {
750780 return syscall1(SYS_close, usize(fd));
751781}
std/os/linux/x86_64.zig+3
......@@ -443,6 +443,9 @@ pub fn syscall6(number: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usiz
443443 : "rcx", "r11");
444444}
445445
446/// This matches the libc clone function.
447pub extern fn clone(func: extern fn(arg: usize) u8, stack: usize, flags: usize, arg: usize, ptid: &i32, tls: usize, ctid: &i32) usize;
448
446449pub nakedcc fn restore_rt() void {
447450 return asm volatile ("syscall"
448451 :
std/os/test.zig+39
......@@ -6,6 +6,8 @@ const io = std.io;
66const a = std.debug.global_allocator;
77
88const builtin = @import("builtin");
9const AtomicRmwOp = builtin.AtomicRmwOp;
10const AtomicOrder = builtin.AtomicOrder;
911
1012test "makePath, put some files in it, deleteTree" {
1113 if (builtin.os == builtin.Os.windows) {
......@@ -40,3 +42,40 @@ test "access file" {
4042 assert((try os.File.access(a, "os_test_tmp/file.txt", os.default_file_mode)) == true);
4143 try os.deleteTree(a, "os_test_tmp");
4244}
45
46test "spawn threads" {
47 if (builtin.os != builtin.Os.linux) {
48 // TODO implement threads on macos and 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;
56
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: [1024]u8 = undefined;
61 var stack2: [1024]u8 = 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);
65
66 thread1.wait();
67 thread2.wait();
68 thread3.wait();
69 thread4.wait();
70
71 assert(shared_ctx == 4);
72}
73
74fn start1(ctx: void) u8 {
75 return 0;
76}
77
78fn start2(ctx: &i32) u8 {
79 _ = @atomicRmw(i32, ctx, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
80 return 0;
81}
std/special/builtin.zig+35
......@@ -57,11 +57,46 @@ comptime {
5757 if (builtin.mode != builtin.Mode.ReleaseFast and builtin.os != builtin.Os.windows) {
5858 @export("__stack_chk_fail", __stack_chk_fail, builtin.GlobalLinkage.Strong);
5959 }
60 if (builtin.os == builtin.Os.linux and builtin.arch == builtin.Arch.x86_64) {
61 @export("clone", clone, builtin.GlobalLinkage.Strong);
62 }
6063}
6164extern fn __stack_chk_fail() noreturn {
6265 @panic("stack smashing detected");
6366}
6467
68// TODO we should be able to put this directly in std/linux/x86_64.zig but
69// it causes a segfault in release mode. this is a workaround of calling it
70// across .o file boundaries. fix comptime @ptrCast of nakedcc functions.
71nakedcc fn clone() void {
72 asm volatile (
73 \\ xor %%eax,%%eax
74 \\ mov $56,%%al
75 \\ mov %%rdi,%%r11
76 \\ mov %%rdx,%%rdi
77 \\ mov %%r8,%%rdx
78 \\ mov %%r9,%%r8
79 \\ mov 8(%%rsp),%%r10
80 \\ mov %%r11,%%r9
81 \\ and $-16,%%rsi
82 \\ sub $8,%%rsi
83 \\ mov %%rcx,(%%rsi)
84 \\ syscall
85 \\ test %%eax,%%eax
86 \\ jnz 1f
87 \\ xor %%ebp,%%ebp
88 \\ pop %%rdi
89 \\ call *%%r9
90 \\ mov %%eax,%%edi
91 \\ xor %%eax,%%eax
92 \\ mov $60,%%al
93 \\ syscall
94 \\ hlt
95 \\1: ret
96 \\
97 );
98}
99
65100const math = @import("../math/index.zig");
66101
67102export fn fmodf(x: f32, y: f32) f32 { return generic_fmod(f32, x, y); }