| ... | ... | @@ -24,17 +24,6 @@ pub const Condition = @import("Thread/Condition.zig"); |
| 24 | 24 | |
| 25 | 25 | pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint"); |
| 26 | 26 | |
| 27 | | test "std.Thread" { |
| 28 | | // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint. |
| 29 | | _ = AutoResetEvent; |
| 30 | | _ = Futex; |
| 31 | | _ = ResetEvent; |
| 32 | | _ = StaticResetEvent; |
| 33 | | _ = Mutex; |
| 34 | | _ = Semaphore; |
| 35 | | _ = Condition; |
| 36 | | } |
| 37 | | |
| 38 | 27 | pub const use_pthreads = target.os.tag != .windows and std.builtin.link_libc; |
| 39 | 28 | |
| 40 | 29 | const Thread = @This(); |
| ... | ... | @@ -50,7 +39,6 @@ else |
| 50 | 39 | impl: Impl, |
| 51 | 40 | |
| 52 | 41 | /// Represents a unique ID per thread. |
| 53 | | /// May be an integer or pointer depending on the platform. |
| 54 | 42 | pub const Id = u64; |
| 55 | 43 | |
| 56 | 44 | /// Returns the platform ID of the callers thread. |
| ... | ... | @@ -79,7 +67,7 @@ pub const SpawnConfig = struct { |
| 79 | 67 | stack_size: usize = 16 * 1024 * 1024, |
| 80 | 68 | }; |
| 81 | 69 | |
| 82 | | pub const SpawnError = error { |
| 70 | pub const SpawnError = error{ |
| 83 | 71 | /// A system-imposed limit on the number of threads was encountered. |
| 84 | 72 | /// There are a number of limits that may trigger this error: |
| 85 | 73 | /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)), |
| ... | ... | @@ -115,7 +103,7 @@ pub const SpawnError = error { |
| 115 | 103 | /// or call `detach()` to excuse the caller from calling `join()` and have the thread clean up its resources on completion`. |
| 116 | 104 | pub fn spawn(config: SpawnConfig, comptime function: anytype, args: anytype) SpawnError!Thread { |
| 117 | 105 | if (std.builtin.single_threaded) { |
| 118 | | @compileError("cannot spawn thread when building in single-threaded mode"); |
| 106 | @compileError("Cannot spawn thread when building in single-threaded mode"); |
| 119 | 107 | } |
| 120 | 108 | |
| 121 | 109 | const impl = try Impl.spawn(config, function, args); |
| ... | ... | @@ -132,11 +120,13 @@ pub fn getHandle(self: Thread) Handle { |
| 132 | 120 | } |
| 133 | 121 | |
| 134 | 122 | /// Release the obligation of the caller to call `join()` and have the thread clean up its own resources on completion. |
| 123 | /// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior. |
| 135 | 124 | pub fn detach(self: Thread) void { |
| 136 | 125 | return self.impl.detach(); |
| 137 | 126 | } |
| 138 | 127 | |
| 139 | 128 | /// Waits for the thread to complete, then deallocates any resources created on `spawn()`. |
| 129 | /// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior. |
| 140 | 130 | pub fn join(self: Thread) void { |
| 141 | 131 | return self.impl.join(); |
| 142 | 132 | } |
| ... | ... | @@ -200,6 +190,8 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) { |
| 200 | 190 | } |
| 201 | 191 | } |
| 202 | 192 | |
| 193 | /// We can't compile error in the `Impl` switch statement as its eagerly evaluated. |
| 194 | /// So instead, we compile-error on the methods themselves for platforms which don't support threads. |
| 203 | 195 | const UnsupportedImpl = struct { |
| 204 | 196 | pub const ThreadHandle = void; |
| 205 | 197 | |
| ... | ... | @@ -212,7 +204,7 @@ const UnsupportedImpl = struct { |
| 212 | 204 | } |
| 213 | 205 | |
| 214 | 206 | fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl { |
| 215 | | return unsupported(.{config, f, args}); |
| 207 | return unsupported(.{ config, f, args }); |
| 216 | 208 | } |
| 217 | 209 | |
| 218 | 210 | fn getHandle(self: Impl) ThreadHandle { |
| ... | ... | @@ -225,7 +217,7 @@ const UnsupportedImpl = struct { |
| 225 | 217 | |
| 226 | 218 | fn join(self: Impl) void { |
| 227 | 219 | return unsupported(self); |
| 228 | | } |
| 220 | } |
| 229 | 221 | |
| 230 | 222 | fn unsupported(unusued: anytype) noreturn { |
| 231 | 223 | @compileLog("Unsupported operating system", target.os.tag); |
| ... | ... | @@ -244,6 +236,7 @@ const WindowsThreadImpl = struct { |
| 244 | 236 | } |
| 245 | 237 | |
| 246 | 238 | fn getCpuCount() !usize { |
| 239 | // Faster than calling into GetSystemInfo(), even if amortized. |
| 247 | 240 | return windows.peb().NumberOfProcessors; |
| 248 | 241 | } |
| 249 | 242 | |
| ... | ... | @@ -299,16 +292,17 @@ const WindowsThreadImpl = struct { |
| 299 | 292 | // Its also fine if the limit here is incorrect as stack size is only a hint. |
| 300 | 293 | var stack_size = std.math.cast(u32, config.stack_size) catch std.math.maxInt(u32); |
| 301 | 294 | stack_size = std.math.max(64 * 1024, stack_size); |
| 302 | | |
| 295 | |
| 303 | 296 | instance.thread.thread_handle = windows.kernel32.CreateThread( |
| 304 | | null, |
| 305 | | stack_size, |
| 306 | | Instance.entryFn, |
| 307 | | @ptrCast(*c_void, instance), |
| 308 | | 0, |
| 297 | null, |
| 298 | stack_size, |
| 299 | Instance.entryFn, |
| 300 | @ptrCast(*c_void, instance), |
| 301 | 0, |
| 309 | 302 | null, |
| 310 | 303 | ) orelse { |
| 311 | | return windows.unexpectedError(windows.kernel32.GetLastError()); |
| 304 | const errno = windows.kernel32.GetLastError(); |
| 305 | return windows.unexpectedError(errno); |
| 312 | 306 | }; |
| 313 | 307 | |
| 314 | 308 | return Impl{ .thread = &instance.thread }; |
| ... | ... | @@ -332,7 +326,7 @@ const WindowsThreadImpl = struct { |
| 332 | 326 | windows.CloseHandle(self.thread.thread_handle); |
| 333 | 327 | assert(self.thread.completion.load(.SeqCst) == .completed); |
| 334 | 328 | self.thread.free(); |
| 335 | | } |
| 329 | } |
| 336 | 330 | }; |
| 337 | 331 | |
| 338 | 332 | const PosixThreadImpl = struct { |
| ... | ... | @@ -374,7 +368,9 @@ const PosixThreadImpl = struct { |
| 374 | 368 | |
| 375 | 369 | fn getCpuCount() !usize { |
| 376 | 370 | switch (target.os.tag) { |
| 377 | | .linux => return LinuxThreadImpl.getCpuCount(), |
| 371 | .linux => { |
| 372 | return LinuxThreadImpl.getCpuCount(); |
| 373 | }, |
| 378 | 374 | .openbsd => { |
| 379 | 375 | var count: c_int = undefined; |
| 380 | 376 | var count_size: usize = @sizeOf(c_int); |
| ... | ... | @@ -413,6 +409,7 @@ const PosixThreadImpl = struct { |
| 413 | 409 | |
| 414 | 410 | const Instance = struct { |
| 415 | 411 | fn entryFn(raw_arg: ?*c_void) callconv(.C) ?*c_void { |
| 412 | // @alignCast() below doesn't support zero-sized-types (ZST) |
| 416 | 413 | if (@sizeOf(Args) < 1) { |
| 417 | 414 | return callFn(f, @as(Args, undefined)); |
| 418 | 415 | } |
| ... | ... | @@ -457,8 +454,9 @@ const PosixThreadImpl = struct { |
| 457 | 454 | |
| 458 | 455 | fn detach(self: Impl) void { |
| 459 | 456 | switch (c.pthread_detach(self.handle)) { |
| 460 | | os.EINVAL => unreachable, |
| 461 | | os.ESRCH => unreachable, |
| 457 | 0 => {}, |
| 458 | os.EINVAL => unreachable, // thread handle is not joinable |
| 459 | os.ESRCH => unreachable, // thread handle is invalid |
| 462 | 460 | else => unreachable, |
| 463 | 461 | } |
| 464 | 462 | } |
| ... | ... | @@ -466,9 +464,9 @@ const PosixThreadImpl = struct { |
| 466 | 464 | fn join(self: Impl) void { |
| 467 | 465 | switch (c.pthread_join(self.handle, null)) { |
| 468 | 466 | 0 => {}, |
| 469 | | os.EINVAL => unreachable, |
| 470 | | os.ESRCH => unreachable, |
| 471 | | os.EDEADLK => unreachable, |
| 467 | os.EINVAL => unreachable, // thread handle is not joinable (or another thread is already joining in) |
| 468 | os.ESRCH => unreachable, // thread handle is invalid |
| 469 | os.EDEADLK => unreachable, // two threads tried to join each other |
| 472 | 470 | else => unreachable, |
| 473 | 471 | } |
| 474 | 472 | } |
| ... | ... | @@ -476,7 +474,7 @@ const PosixThreadImpl = struct { |
| 476 | 474 | |
| 477 | 475 | const LinuxThreadImpl = struct { |
| 478 | 476 | const linux = os.linux; |
| 479 | | |
| 477 | |
| 480 | 478 | pub const ThreadHandle = i32; |
| 481 | 479 | |
| 482 | 480 | threadlocal var tls_thread_id: ?Id = null; |
| ... | ... | @@ -491,7 +489,8 @@ const LinuxThreadImpl = struct { |
| 491 | 489 | |
| 492 | 490 | fn getCpuCount() !usize { |
| 493 | 491 | const cpu_set = try os.sched_getaffinity(0); |
| 494 | | return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast |
| 492 | // TODO: should not need this usize cast |
| 493 | return @as(usize, os.CPU_COUNT(cpu_set)); |
| 495 | 494 | } |
| 496 | 495 | |
| 497 | 496 | thread: *ThreadCompletion, |
| ... | ... | @@ -547,7 +546,7 @@ const LinuxThreadImpl = struct { |
| 547 | 546 | bytes = std.mem.alignForward(bytes, std.mem.page_size); |
| 548 | 547 | break :blk bytes; |
| 549 | 548 | }; |
| 550 | | |
| 549 | |
| 551 | 550 | // map all memory needed without read/write permissions |
| 552 | 551 | // to avoid committing the whole region right away |
| 553 | 552 | const mapped = os.mmap( |
| ... | ... | @@ -654,7 +653,7 @@ const LinuxThreadImpl = struct { |
| 654 | 653 | |
| 655 | 654 | switch (linux.getErrno(linux.futex_wait( |
| 656 | 655 | &self.thread.child_tid.value, |
| 657 | | linux.FUTEX_WAIT, |
| 656 | linux.FUTEX_WAIT, |
| 658 | 657 | tid, |
| 659 | 658 | null, |
| 660 | 659 | ))) { |
| ... | ... | @@ -671,98 +670,145 @@ const LinuxThreadImpl = struct { |
| 671 | 670 | extern fn __unmap_and_exit(ptr: usize, len: usize) callconv(.C) noreturn; |
| 672 | 671 | comptime { |
| 673 | 672 | if (target.os.tag == .linux) { |
| 674 | | asm(switch (target.cpu.arch) { |
| 675 | | .i386 => ( |
| 676 | | \\.text |
| 677 | | \\.global __unmap_and_exit |
| 678 | | \\.type __unmap_and_exit, @function |
| 679 | | \\__unmap_and_exit: |
| 680 | | \\ movl $91, %eax |
| 681 | | \\ movl 4(%esp), %ebx |
| 682 | | \\ movl 8(%esp), %ecx |
| 683 | | \\ int $128 |
| 684 | | \\ xorl %ebx, %ebx |
| 685 | | \\ movl $1, %eax |
| 686 | | \\ int $128 |
| 687 | | ), |
| 688 | | .x86_64 => ( |
| 689 | | \\.text |
| 690 | | \\.global __unmap_and_exit |
| 691 | | \\.type __unmap_and_exit, @function |
| 692 | | \\__unmap_and_exit: |
| 693 | | \\ movl $11, %eax |
| 694 | | \\ syscall |
| 695 | | \\ xor %rdi, %rdi |
| 696 | | \\ movl $60, %eax |
| 697 | | \\ syscall |
| 698 | | ), |
| 699 | | .arm, .armeb, .thumb, .thumbeb => ( |
| 700 | | \\.syntax unified |
| 701 | | \\.text |
| 702 | | \\.global __unmap_and_exit |
| 703 | | \\.type __unmap_and_exit, %function |
| 704 | | \\__unmap_and_exit: |
| 705 | | \\ mov r7, #91 |
| 706 | | \\ svc 0 |
| 707 | | \\ mov r7, #1 |
| 708 | | \\ svc 0 |
| 709 | | ), |
| 710 | | .aarch64, .aarch64_be, .aarch64_32 => ( |
| 711 | | \\.global __unmap_and_exit |
| 712 | | \\.type __unmap_and_exit, %function |
| 713 | | \\__unmap_and_exit: |
| 714 | | \\ mov x8, #215 |
| 715 | | \\ svc 0 |
| 716 | | \\ mov x8, #93 |
| 717 | | \\ svc 0 |
| 718 | | ), |
| 719 | | .mips, .mipsel, => ( |
| 720 | | \\.set noreorder |
| 721 | | \\.global __unmap_and_exit |
| 722 | | \\.type __unmap_and_exit,@function |
| 723 | | \\__unmap_and_exit: |
| 724 | | \\ move $sp, $25 |
| 725 | | \\ li $2, 4091 |
| 726 | | \\ syscall |
| 727 | | \\ li $4, 0 |
| 728 | | \\ li $2, 4001 |
| 729 | | \\ syscall |
| 730 | | ), |
| 731 | | .mips64, .mips64el => ( |
| 732 | | \\.set noreorder |
| 733 | | \\.global __unmap_and_exit |
| 734 | | \\.type __unmap_and_exit, @function |
| 735 | | \\__unmap_and_exit: |
| 736 | | \\ li $2, 4091 |
| 737 | | \\ syscall |
| 738 | | \\ li $4, 0 |
| 739 | | \\ li $2, 4001 |
| 740 | | \\ syscall |
| 741 | | ), |
| 742 | | .powerpc, .powerpc64, .powerpc64le => ( |
| 743 | | \\.text |
| 744 | | \\.global __unmap_and_exit |
| 745 | | \\.type __unmap_and_exit, %function |
| 746 | | \\__unmap_and_exit: |
| 747 | | \\ li 0, 91 |
| 748 | | \\ sc |
| 749 | | \\ li 0, 1 |
| 750 | | \\ sc |
| 751 | | \\ blr |
| 752 | | ), |
| 753 | | .riscv64 => ( |
| 754 | | \\.global __unmap_and_exit |
| 755 | | \\.type __unmap_and_exit, %function |
| 756 | | \\__unmap_and_exit: |
| 757 | | \\ li a7, 215 |
| 758 | | \\ ecall |
| 759 | | \\ li a7, 93 |
| 760 | | \\ ecall |
| 761 | | ), |
| 762 | | else => |cpu_arch| { |
| 763 | | @compileLog("linux arch", cpu_arch, "is not supported"); |
| 764 | | }, |
| 765 | | }); |
| 673 | asm (switch (target.cpu.arch) { |
| 674 | .i386 => ( |
| 675 | \\.text |
| 676 | \\.global __unmap_and_exit |
| 677 | \\.type __unmap_and_exit, @function |
| 678 | \\__unmap_and_exit: |
| 679 | \\ movl $91, %eax |
| 680 | \\ movl 4(%esp), %ebx |
| 681 | \\ movl 8(%esp), %ecx |
| 682 | \\ int $128 |
| 683 | \\ xorl %ebx, %ebx |
| 684 | \\ movl $1, %eax |
| 685 | \\ int $128 |
| 686 | ), |
| 687 | .x86_64 => ( |
| 688 | \\.text |
| 689 | \\.global __unmap_and_exit |
| 690 | \\.type __unmap_and_exit, @function |
| 691 | \\__unmap_and_exit: |
| 692 | \\ movl $11, %eax |
| 693 | \\ syscall |
| 694 | \\ xor %rdi, %rdi |
| 695 | \\ movl $60, %eax |
| 696 | \\ syscall |
| 697 | ), |
| 698 | .arm, .armeb, .thumb, .thumbeb => ( |
| 699 | \\.syntax unified |
| 700 | \\.text |
| 701 | \\.global __unmap_and_exit |
| 702 | \\.type __unmap_and_exit, %function |
| 703 | \\__unmap_and_exit: |
| 704 | \\ mov r7, #91 |
| 705 | \\ svc 0 |
| 706 | \\ mov r7, #1 |
| 707 | \\ svc 0 |
| 708 | ), |
| 709 | .aarch64, .aarch64_be, .aarch64_32 => ( |
| 710 | \\.global __unmap_and_exit |
| 711 | \\.type __unmap_and_exit, %function |
| 712 | \\__unmap_and_exit: |
| 713 | \\ mov x8, #215 |
| 714 | \\ svc 0 |
| 715 | \\ mov x8, #93 |
| 716 | \\ svc 0 |
| 717 | ), |
| 718 | .mips, |
| 719 | .mipsel, |
| 720 | => ( |
| 721 | \\.set noreorder |
| 722 | \\.global __unmap_and_exit |
| 723 | \\.type __unmap_and_exit,@function |
| 724 | \\__unmap_and_exit: |
| 725 | \\ move $sp, $25 |
| 726 | \\ li $2, 4091 |
| 727 | \\ syscall |
| 728 | \\ li $4, 0 |
| 729 | \\ li $2, 4001 |
| 730 | \\ syscall |
| 731 | ), |
| 732 | .mips64, .mips64el => ( |
| 733 | \\.set noreorder |
| 734 | \\.global __unmap_and_exit |
| 735 | \\.type __unmap_and_exit, @function |
| 736 | \\__unmap_and_exit: |
| 737 | \\ li $2, 4091 |
| 738 | \\ syscall |
| 739 | \\ li $4, 0 |
| 740 | \\ li $2, 4001 |
| 741 | \\ syscall |
| 742 | ), |
| 743 | .powerpc, .powerpc64, .powerpc64le => ( |
| 744 | \\.text |
| 745 | \\.global __unmap_and_exit |
| 746 | \\.type __unmap_and_exit, %function |
| 747 | \\__unmap_and_exit: |
| 748 | \\ li 0, 91 |
| 749 | \\ sc |
| 750 | \\ li 0, 1 |
| 751 | \\ sc |
| 752 | \\ blr |
| 753 | ), |
| 754 | .riscv64 => ( |
| 755 | \\.global __unmap_and_exit |
| 756 | \\.type __unmap_and_exit, %function |
| 757 | \\__unmap_and_exit: |
| 758 | \\ li a7, 215 |
| 759 | \\ ecall |
| 760 | \\ li a7, 93 |
| 761 | \\ ecall |
| 762 | ), |
| 763 | else => |cpu_arch| { |
| 764 | @compileLog("linux arch", cpu_arch, "is not supported"); |
| 765 | }, |
| 766 | }); |
| 766 | 767 | } |
| 767 | 768 | } |
| 768 | | }; |
| | \ No newline at end of file |
| 769 | }; |
| 770 | |
| 771 | test "std.Thread" { |
| 772 | // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint. |
| 773 | _ = AutoResetEvent; |
| 774 | _ = Futex; |
| 775 | _ = ResetEvent; |
| 776 | _ = StaticResetEvent; |
| 777 | _ = Mutex; |
| 778 | _ = Semaphore; |
| 779 | _ = Condition; |
| 780 | } |
| 781 | |
| 782 | fn testIncrementNotify(value: *usize, event: *ResetEvent) void { |
| 783 | value.* += 1; |
| 784 | event.set(); |
| 785 | } |
| 786 | |
| 787 | test "Thread.join" { |
| 788 | if (std.builtin.single_threaded) return error.SkipZigTest; |
| 789 | |
| 790 | var value: usize = 0; |
| 791 | var event: ResetEvent = undefined; |
| 792 | try event.init(); |
| 793 | defer event.deinit(); |
| 794 | |
| 795 | const thread = try Thread.spawn(.{}, testIncrementNotify, .{&value, &event}); |
| 796 | thread.join(); |
| 797 | |
| 798 | try std.testing.expectEqual(value, 1); |
| 799 | } |
| 800 | |
| 801 | test "Thread.detach" { |
| 802 | if (std.builtin.single_threaded) return error.SkipZigTest; |
| 803 | |
| 804 | var value: usize = 0; |
| 805 | var event: ResetEvent = undefined; |
| 806 | try event.init(); |
| 807 | defer event.deinit(); |
| 808 | |
| 809 | const thread = try Thread.spawn(.{}, testIncrementNotify, .{&value, &event}); |
| 810 | thread.detach(); |
| 811 | |
| 812 | event.wait(); |
| 813 | try std.testing.expectEqual(value, 1); |
| 814 | } |
| | \ No newline at end of file |