| author | |
| committer | |
| log | b7af9edb8a8802c35f1a460f8dafff8643b34639 |
| tree | 7a50495c844c951b344806acab6b452986f03108 |
| parent | fa05cab01a755827209b6ede299402f515681a81 |
this adds kernel thread support to the standard library for
linux.
See #1747 files changed, 250 insertions(+), 14 deletions(-)
src/ir.cpp+1-1| ... | @@ -18407,6 +18407,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { | ... | @@ -18407,6 +18407,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { |
| 18407 | case IrInstructionIdAddImplicitReturnType: | 18407 | case IrInstructionIdAddImplicitReturnType: |
| 18408 | case IrInstructionIdMergeErrRetTraces: | 18408 | case IrInstructionIdMergeErrRetTraces: |
| 18409 | case IrInstructionIdMarkErrRetTracePtr: | 18409 | case IrInstructionIdMarkErrRetTracePtr: |
| 18410 | case IrInstructionIdAtomicRmw: | ||
| 18410 | return true; | 18411 | return true; |
| 18411 | 18412 | ||
| 18412 | case IrInstructionIdPhi: | 18413 | case IrInstructionIdPhi: |
| ... | @@ -18487,7 +18488,6 @@ bool ir_has_side_effects(IrInstruction *instruction) { | ... | @@ -18487,7 +18488,6 @@ bool ir_has_side_effects(IrInstruction *instruction) { |
| 18487 | case IrInstructionIdCoroSize: | 18488 | case IrInstructionIdCoroSize: |
| 18488 | case IrInstructionIdCoroSuspend: | 18489 | case IrInstructionIdCoroSuspend: |
| 18489 | case IrInstructionIdCoroFree: | 18490 | case IrInstructionIdCoroFree: |
| 18490 | case IrInstructionIdAtomicRmw: | ||
| 18491 | case IrInstructionIdCoroPromise: | 18491 | case IrInstructionIdCoroPromise: |
| 18492 | case IrInstructionIdPromiseResultType: | 18492 | case IrInstructionIdPromiseResultType: |
| 18493 | return false; | 18493 | return false; |
std/fmt/index.zig+13-13| ... | @@ -8,25 +8,25 @@ const errol3 = @import("errol/index.zig").errol3; | ... | @@ -8,25 +8,25 @@ const errol3 = @import("errol/index.zig").errol3; |
| 8 | 8 | ||
| 9 | const max_int_digits = 65; | 9 | const max_int_digits = 65; |
| 10 | 10 | ||
| 11 | const 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 | |||
| 24 | /// Renders fmt string with args, calling output with slices of bytes. | 11 | /// Renders fmt string with args, calling output with slices of bytes. |
| 25 | /// If `output` returns an error, the error is returned from `format` and | 12 | /// If `output` returns an error, the error is returned from `format` and |
| 26 | /// `output` is not called again. | 13 | /// `output` is not called again. |
| 27 | pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8) Errors!void, | 14 | pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8) Errors!void, |
| 28 | comptime fmt: []const u8, args: ...) Errors!void | 15 | comptime fmt: []const u8, args: ...) Errors!void |
| 29 | { | 16 | { |
| 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 | |||
| 30 | comptime var start_index = 0; | 30 | comptime var start_index = 0; |
| 31 | comptime var state = State.Start; | 31 | comptime var state = State.Start; |
| 32 | comptime var next_arg = 0; | 32 | comptime var next_arg = 0; |
std/os/index.zig+129| ... | @@ -2384,3 +2384,132 @@ pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void { | ... | @@ -2384,3 +2384,132 @@ pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void { |
| 2384 | posix.ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. | 2384 | posix.ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. |
| 2385 | } | 2385 | } |
| 2386 | } | 2386 | } |
| 2387 | |||
| 2388 | pub 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 | |||
| 2411 | pub 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 | |||
| 2434 | pub 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 | ||
| 2439 | pub 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 | ||
| 2453 | pub 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 | |||
| 2503 | pub 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; | ... | @@ -14,6 +14,22 @@ pub const STDIN_FILENO = 0; |
| 14 | pub const STDOUT_FILENO = 1; | 14 | pub const STDOUT_FILENO = 1; |
| 15 | pub const STDERR_FILENO = 2; | 15 | pub const STDERR_FILENO = 2; |
| 16 | 16 | ||
| 17 | pub const FUTEX_WAIT = 0; | ||
| 18 | pub const FUTEX_WAKE = 1; | ||
| 19 | pub const FUTEX_FD = 2; | ||
| 20 | pub const FUTEX_REQUEUE = 3; | ||
| 21 | pub const FUTEX_CMP_REQUEUE = 4; | ||
| 22 | pub const FUTEX_WAKE_OP = 5; | ||
| 23 | pub const FUTEX_LOCK_PI = 6; | ||
| 24 | pub const FUTEX_UNLOCK_PI = 7; | ||
| 25 | pub const FUTEX_TRYLOCK_PI = 8; | ||
| 26 | pub const FUTEX_WAIT_BITSET = 9; | ||
| 27 | |||
| 28 | pub const FUTEX_PRIVATE_FLAG = 128; | ||
| 29 | |||
| 30 | pub const FUTEX_CLOCK_REALTIME = 256; | ||
| 31 | |||
| 32 | |||
| 17 | pub const PROT_NONE = 0; | 33 | pub const PROT_NONE = 0; |
| 18 | pub const PROT_READ = 1; | 34 | pub const PROT_READ = 1; |
| 19 | pub const PROT_WRITE = 2; | 35 | pub const PROT_WRITE = 2; |
| ... | @@ -652,6 +668,10 @@ pub fn fork() usize { | ... | @@ -652,6 +668,10 @@ pub fn fork() usize { |
| 652 | return syscall0(SYS_fork); | 668 | return syscall0(SYS_fork); |
| 653 | } | 669 | } |
| 654 | 670 | ||
| 671 | pub 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 | |||
| 655 | pub fn getcwd(buf: &u8, size: usize) usize { | 675 | pub fn getcwd(buf: &u8, size: usize) usize { |
| 656 | return syscall2(SYS_getcwd, @ptrToInt(buf), size); | 676 | return syscall2(SYS_getcwd, @ptrToInt(buf), size); |
| 657 | } | 677 | } |
| ... | @@ -746,6 +766,16 @@ pub fn openat(dirfd: i32, path: &const u8, flags: usize, mode: usize) usize { | ... | @@ -746,6 +766,16 @@ pub fn openat(dirfd: i32, path: &const u8, flags: usize, mode: usize) usize { |
| 746 | return syscall4(SYS_openat, usize(dirfd), @ptrToInt(path), flags, mode); | 766 | return syscall4(SYS_openat, usize(dirfd), @ptrToInt(path), flags, mode); |
| 747 | } | 767 | } |
| 748 | 768 | ||
| 769 | /// See also `clone` (from the arch-specific include) | ||
| 770 | pub 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) | ||
| 775 | pub fn clone2(flags: usize, child_stack_ptr: usize) usize { | ||
| 776 | return syscall2(SYS_clone, flags, child_stack_ptr); | ||
| 777 | } | ||
| 778 | |||
| 749 | pub fn close(fd: i32) usize { | 779 | pub fn close(fd: i32) usize { |
| 750 | return syscall1(SYS_close, usize(fd)); | 780 | return syscall1(SYS_close, usize(fd)); |
| 751 | } | 781 | } |
std/os/linux/x86_64.zig+3| ... | @@ -443,6 +443,9 @@ pub fn syscall6(number: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usiz | ... | @@ -443,6 +443,9 @@ pub fn syscall6(number: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usiz |
| 443 | : "rcx", "r11"); | 443 | : "rcx", "r11"); |
| 444 | } | 444 | } |
| 445 | 445 | ||
| 446 | /// This matches the libc clone function. | ||
| 447 | pub extern fn clone(func: extern fn(arg: usize) u8, stack: usize, flags: usize, arg: usize, ptid: &i32, tls: usize, ctid: &i32) usize; | ||
| 448 | |||
| 446 | pub nakedcc fn restore_rt() void { | 449 | pub nakedcc fn restore_rt() void { |
| 447 | return asm volatile ("syscall" | 450 | return asm volatile ("syscall" |
| 448 | : | 451 | : |
std/os/test.zig+39| ... | @@ -6,6 +6,8 @@ const io = std.io; | ... | @@ -6,6 +6,8 @@ const io = std.io; |
| 6 | const a = std.debug.global_allocator; | 6 | const a = std.debug.global_allocator; |
| 7 | 7 | ||
| 8 | const builtin = @import("builtin"); | 8 | const builtin = @import("builtin"); |
| 9 | const AtomicRmwOp = builtin.AtomicRmwOp; | ||
| 10 | const AtomicOrder = builtin.AtomicOrder; | ||
| 9 | 11 | ||
| 10 | test "makePath, put some files in it, deleteTree" { | 12 | test "makePath, put some files in it, deleteTree" { |
| 11 | if (builtin.os == builtin.Os.windows) { | 13 | if (builtin.os == builtin.Os.windows) { |
| ... | @@ -40,3 +42,40 @@ test "access file" { | ... | @@ -40,3 +42,40 @@ test "access file" { |
| 40 | assert((try os.File.access(a, "os_test_tmp/file.txt", os.default_file_mode)) == true); | 42 | assert((try os.File.access(a, "os_test_tmp/file.txt", os.default_file_mode)) == true); |
| 41 | try os.deleteTree(a, "os_test_tmp"); | 43 | try os.deleteTree(a, "os_test_tmp"); |
| 42 | } | 44 | } |
| 45 | |||
| 46 | test "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 | |||
| 74 | fn start1(ctx: void) u8 { | ||
| 75 | return 0; | ||
| 76 | } | ||
| 77 | |||
| 78 | fn 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 { | ... | @@ -57,11 +57,46 @@ comptime { |
| 57 | if (builtin.mode != builtin.Mode.ReleaseFast and builtin.os != builtin.Os.windows) { | 57 | if (builtin.mode != builtin.Mode.ReleaseFast and builtin.os != builtin.Os.windows) { |
| 58 | @export("__stack_chk_fail", __stack_chk_fail, builtin.GlobalLinkage.Strong); | 58 | @export("__stack_chk_fail", __stack_chk_fail, builtin.GlobalLinkage.Strong); |
| 59 | } | 59 | } |
| 60 | if (builtin.os == builtin.Os.linux and builtin.arch == builtin.Arch.x86_64) { | ||
| 61 | @export("clone", clone, builtin.GlobalLinkage.Strong); | ||
| 62 | } | ||
| 60 | } | 63 | } |
| 61 | extern fn __stack_chk_fail() noreturn { | 64 | extern fn __stack_chk_fail() noreturn { |
| 62 | @panic("stack smashing detected"); | 65 | @panic("stack smashing detected"); |
| 63 | } | 66 | } |
| 64 | 67 | ||
| 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. | ||
| 71 | nakedcc 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 | |||
| 65 | const math = @import("../math/index.zig"); | 100 | const math = @import("../math/index.zig"); |
| 66 | 101 | ||
| 67 | export fn fmodf(x: f32, y: f32) f32 { return generic_fmod(f32, x, y); } | 102 | export fn fmodf(x: f32, y: f32) f32 { return generic_fmod(f32, x, y); } |