blob caa22ea4 (66273B) - Raw
1 //! This struct represents a kernel thread. 2 const Thread = @This(); 3 4 const builtin = @import("builtin"); 5 const target = builtin.target; 6 const native_os = builtin.os.tag; 7 8 const std = @import("std.zig"); 9 const Io = std.Io; 10 const math = std.math; 11 const assert = std.debug.assert; 12 const posix = std.posix; 13 const windows = std.os.windows; 14 const testing = std.testing; 15 16 pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc; 17 18 const Impl = if (native_os == .windows) 19 WindowsThreadImpl 20 else if (use_pthreads) 21 PosixThreadImpl 22 else if (native_os == .linux) 23 LinuxThreadImpl 24 else if (native_os == .wasi) 25 WasiThreadImpl 26 else 27 UnsupportedImpl; 28 29 impl: Impl, 30 31 pub const max_name_len = switch (native_os) { 32 .linux => 15, 33 .windows => 31, 34 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => 63, 35 .netbsd => 31, 36 .freebsd => 15, 37 .openbsd => 23, 38 .dragonfly => 1023, 39 .illumos => 31, 40 // https://github.com/SerenityOS/serenity/blob/6b4c300353da49d3508b5442cf61da70bd04d757/Kernel/Tasks/Thread.h#L102 41 .serenity => 63, 42 else => 0, 43 }; 44 45 pub const SetNameError = error{ 46 NameTooLong, 47 Unsupported, 48 Unexpected, 49 InvalidWtf8, 50 } || posix.PrctlError || Io.File.Writer.Error || Io.File.OpenError || std.fmt.BufPrintError; 51 52 pub fn setName(self: Thread, io: Io, name: []const u8) SetNameError!void { 53 if (name.len > max_name_len) return error.NameTooLong; 54 55 const name_with_terminator = blk: { 56 var name_buf: [max_name_len:0]u8 = undefined; 57 @memcpy(name_buf[0..name.len], name); 58 name_buf[name.len] = 0; 59 break :blk name_buf[0..name.len :0]; 60 }; 61 62 switch (native_os) { 63 .linux => if (use_pthreads) { 64 if (self.getHandle() == std.c.pthread_self()) { 65 // Set the name of the calling thread (no thread id required). 66 assert(try posix.prctl(.SET_NAME, .{@intFromPtr(name_with_terminator.ptr)}) == 0); 67 return; 68 } else { 69 const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr); 70 switch (@as(posix.E, @enumFromInt(err))) { 71 .SUCCESS => return, 72 .RANGE => unreachable, 73 else => |e| return posix.unexpectedErrno(e), 74 } 75 } 76 } else { 77 var buf: [32]u8 = undefined; 78 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); 79 80 const file = try Io.Dir.cwd().openFile(io, path, .{ .mode = .write_only }); 81 defer file.close(io); 82 83 try file.writeStreamingAll(io, name); 84 return; 85 }, 86 .windows => { 87 var buf: [max_name_len]u16 = undefined; 88 switch (windows.ntdll.NtSetInformationThread( 89 self.getHandle(), 90 .NameInformation, 91 &windows.UNICODE_STRING.init(buf[0..try std.unicode.wtf8ToWtf16Le(&buf, name)]), 92 @sizeOf(windows.UNICODE_STRING), 93 )) { 94 .SUCCESS => return, 95 .NOT_IMPLEMENTED => return error.Unsupported, 96 else => |err| return windows.unexpectedStatus(err), 97 } 98 }, 99 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => if (use_pthreads) { 100 // There doesn't seem to be a way to set the name for an arbitrary thread, only the current one. 101 if (self.getHandle() != std.c.pthread_self()) return error.Unsupported; 102 103 const err = std.c.pthread_setname_np(name_with_terminator.ptr); 104 switch (@as(posix.E, @enumFromInt(err))) { 105 .SUCCESS => return, 106 else => |e| return posix.unexpectedErrno(e), 107 } 108 }, 109 .serenity => if (use_pthreads) { 110 const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr); 111 switch (@as(posix.E, @enumFromInt(err))) { 112 .SUCCESS => return, 113 .NAMETOOLONG => unreachable, 114 .SRCH => unreachable, 115 else => |e| return posix.unexpectedErrno(e), 116 } 117 }, 118 .netbsd, .illumos => if (use_pthreads) { 119 const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr, null); 120 switch (@as(posix.E, @enumFromInt(err))) { 121 .SUCCESS => return, 122 .INVAL => unreachable, 123 .SRCH => unreachable, 124 .NOMEM => unreachable, 125 else => |e| return posix.unexpectedErrno(e), 126 } 127 }, 128 .freebsd, .openbsd => if (use_pthreads) { 129 // Use pthread_set_name_np for FreeBSD because pthread_setname_np is FreeBSD 12.2+ only. 130 // TODO maybe revisit this if depending on FreeBSD 12.2+ is acceptable because 131 // pthread_setname_np can return an error. 132 133 std.c.pthread_set_name_np(self.getHandle(), name_with_terminator.ptr); 134 return; 135 }, 136 .dragonfly => if (use_pthreads) { 137 const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr); 138 switch (@as(posix.E, @enumFromInt(err))) { 139 .SUCCESS => return, 140 .INVAL => unreachable, 141 .FAULT => unreachable, 142 .NAMETOOLONG => unreachable, // already checked 143 .SRCH => unreachable, 144 else => |e| return posix.unexpectedErrno(e), 145 } 146 }, 147 else => {}, 148 } 149 return error.Unsupported; 150 } 151 152 pub const GetNameError = error{ 153 Unsupported, 154 Unexpected, 155 } || posix.PrctlError || posix.ReadError || Io.File.OpenError || std.fmt.BufPrintError; 156 157 /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). 158 /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. 159 pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 { 160 buffer_ptr[max_name_len] = 0; 161 var buffer: [:0]u8 = buffer_ptr; 162 163 switch (native_os) { 164 .linux => if (use_pthreads) { 165 if (self.getHandle() == std.c.pthread_self()) { 166 // Get the name of the calling thread (no thread id required). 167 assert(try posix.prctl(.GET_NAME, .{@intFromPtr(buffer.ptr)}) == 0); 168 return std.mem.sliceTo(buffer, 0); 169 } else { 170 const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1); 171 switch (@as(posix.E, @enumFromInt(err))) { 172 .SUCCESS => return std.mem.sliceTo(buffer, 0), 173 .RANGE => unreachable, 174 else => |e| return posix.unexpectedErrno(e), 175 } 176 } 177 } else { 178 var buf: [32]u8 = undefined; 179 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); 180 181 const io = std.Options.debug_io; 182 183 const file = try Io.Dir.cwd().openFile(io, path, .{}); 184 defer file.close(io); 185 186 var file_reader = file.readerStreaming(io, &.{}); 187 const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) { 188 error.ReadFailed => return file_reader.err.?, 189 }; 190 return if (data_len >= 1) buffer[0 .. data_len - 1] else null; 191 }, 192 .windows => { 193 const buf_capacity = @sizeOf(windows.UNICODE_STRING) + (@sizeOf(u16) * max_name_len); 194 var buf: [buf_capacity]u8 align(@alignOf(windows.UNICODE_STRING)) = undefined; 195 196 switch (windows.ntdll.NtQueryInformationThread( 197 self.getHandle(), 198 .NameInformation, 199 &buf, 200 buf_capacity, 201 null, 202 )) { 203 .SUCCESS => { 204 const string: *const windows.UNICODE_STRING = @ptrCast(&buf); 205 const len = std.unicode.wtf16LeToWtf8(buffer, string.slice()); 206 return if (len > 0) buffer[0..len] else null; 207 }, 208 .NOT_IMPLEMENTED => return error.Unsupported, 209 else => |err| return windows.unexpectedStatus(err), 210 } 211 }, 212 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => if (use_pthreads) { 213 const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1); 214 switch (@as(posix.E, @enumFromInt(err))) { 215 .SUCCESS => return std.mem.sliceTo(buffer, 0), 216 .SRCH => unreachable, 217 else => |e| return posix.unexpectedErrno(e), 218 } 219 }, 220 .serenity => if (use_pthreads) { 221 const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1); 222 switch (@as(posix.E, @enumFromInt(err))) { 223 .SUCCESS => return, 224 .NAMETOOLONG => unreachable, 225 .SRCH => unreachable, 226 .FAULT => unreachable, 227 else => |e| return posix.unexpectedErrno(e), 228 } 229 }, 230 .netbsd, .illumos => if (use_pthreads) { 231 const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1); 232 switch (@as(posix.E, @enumFromInt(err))) { 233 .SUCCESS => return std.mem.sliceTo(buffer, 0), 234 .INVAL => unreachable, 235 .SRCH => unreachable, 236 else => |e| return posix.unexpectedErrno(e), 237 } 238 }, 239 .freebsd, .openbsd => if (use_pthreads) { 240 // Use pthread_get_name_np for FreeBSD because pthread_getname_np is FreeBSD 12.2+ only. 241 // TODO maybe revisit this if depending on FreeBSD 12.2+ is acceptable because pthread_getname_np can return an error. 242 243 std.c.pthread_get_name_np(self.getHandle(), buffer.ptr, max_name_len + 1); 244 return std.mem.sliceTo(buffer, 0); 245 }, 246 .dragonfly => if (use_pthreads) { 247 const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1); 248 switch (@as(posix.E, @enumFromInt(err))) { 249 .SUCCESS => return std.mem.sliceTo(buffer, 0), 250 .INVAL => unreachable, 251 .FAULT => unreachable, 252 .SRCH => unreachable, 253 else => |e| return posix.unexpectedErrno(e), 254 } 255 }, 256 else => {}, 257 } 258 return error.Unsupported; 259 } 260 261 /// Represents an ID per thread guaranteed to be unique only within a process. 262 pub const Id = switch (native_os) { 263 .linux, 264 .dragonfly, 265 .netbsd, 266 .freebsd, 267 .openbsd, 268 .haiku, 269 .wasi, 270 .serenity, 271 => u32, 272 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => u64, 273 .windows => windows.DWORD, 274 else => usize, 275 }; 276 277 /// Returns the platform ID of the callers thread. 278 /// Attempts to use thread locals and avoid syscalls when possible. 279 pub fn getCurrentId() Id { 280 return Impl.getCurrentId(); 281 } 282 283 pub const CpuCountError = error{ 284 PermissionDenied, 285 SystemResources, 286 Unsupported, 287 Unexpected, 288 }; 289 290 /// Returns the platforms view on the number of logical CPU cores available. 291 /// 292 /// Returned value guaranteed to be >= 1. 293 pub fn getCpuCount() CpuCountError!usize { 294 return try Impl.getCpuCount(); 295 } 296 297 /// Configuration options for hints on how to spawn threads. 298 pub const SpawnConfig = struct { 299 // TODO compile-time call graph analysis to determine stack upper bound 300 // https://github.com/ziglang/zig/issues/157 301 302 /// Size in bytes of the Thread's stack 303 stack_size: usize = default_stack_size, 304 /// The allocator to be used to allocate memory for the to-be-spawned thread 305 allocator: ?std.mem.Allocator = null, 306 307 pub const default_stack_size = 16 * 1024 * 1024; 308 }; 309 310 pub const SpawnError = error{ 311 /// A system-imposed limit on the number of threads was encountered. 312 /// There are a number of limits that may trigger this error: 313 /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)), 314 /// which limits the number of processes and threads for a real 315 /// user ID, was reached; 316 /// * the kernel's system-wide limit on the number of processes and 317 /// threads, /proc/sys/kernel/threads-max, was reached (see 318 /// proc(5)); 319 /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was 320 /// reached (see proc(5)); or 321 /// * the PID limit (pids.max) imposed by the cgroup "process num‐ 322 /// ber" (PIDs) controller was reached. 323 ThreadQuotaExceeded, 324 325 /// The kernel cannot allocate sufficient memory to allocate a task structure 326 /// for the child, or to copy those parts of the caller's context that need to 327 /// be copied. 328 SystemResources, 329 330 /// Not enough userland memory to spawn the thread. 331 OutOfMemory, 332 333 /// `mlockall` is enabled, and the memory needed to spawn the thread 334 /// would exceed the limit. 335 LockedMemoryLimitExceeded, 336 337 Unexpected, 338 }; 339 340 /// Spawns a new thread which executes `function` using `args` and returns a handle to the spawned thread. 341 /// `config` can be used as hints to the platform for how to spawn and execute the `function`. 342 /// The caller must eventually either call `join()` to wait for the thread to finish and free its resources 343 /// or call `detach()` to excuse the caller from calling `join()` and have the thread clean up its resources on completion. 344 pub fn spawn(config: SpawnConfig, comptime function: anytype, args: anytype) SpawnError!Thread { 345 if (builtin.single_threaded) { 346 @compileError("Cannot spawn thread when building in single-threaded mode"); 347 } 348 349 const impl = try Impl.spawn(config, function, args); 350 return Thread{ .impl = impl }; 351 } 352 353 /// Represents a kernel thread handle. 354 /// May be an integer or a pointer depending on the platform. 355 pub const Handle = Impl.ThreadHandle; 356 357 /// Returns the handle of this thread 358 pub fn getHandle(self: Thread) Handle { 359 return self.impl.getHandle(); 360 } 361 362 /// Release the obligation of the caller to call `join()` and have the thread clean up its own resources on completion. 363 /// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior. 364 pub fn detach(self: Thread) void { 365 return self.impl.detach(); 366 } 367 368 /// Waits for the thread to complete, then deallocates any resources created on `spawn()`. 369 /// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior. 370 pub fn join(self: Thread) void { 371 return self.impl.join(); 372 } 373 374 pub const YieldError = error{ 375 /// The system is not configured to allow yielding 376 SystemCannotYield, 377 }; 378 379 /// Yields the current thread potentially allowing other threads to run. 380 pub fn yield() YieldError!void { 381 if (native_os == .windows) switch (windows.ntdll.NtYieldExecution()) { 382 .SUCCESS, .NO_YIELD_PERFORMED => return, 383 else => return error.SystemCannotYield, 384 }; 385 switch (posix.errno(posix.system.sched_yield())) { 386 .SUCCESS => return, 387 .NOSYS => return error.SystemCannotYield, 388 else => return error.SystemCannotYield, 389 } 390 } 391 392 /// State to synchronize detachment of spawner thread to spawned thread 393 const Completion = std.atomic.Value(enum(if (builtin.zig_backend == .stage2_riscv64) u32 else u8) { 394 running, 395 detached, 396 completed, 397 }); 398 399 /// Performs implementation-agnostic thread setup (`maybeAttachSignalStack`), then calls the given 400 /// thread entry point `f` with `args` and handles the result. 401 fn callFn(comptime f: anytype, args: anytype) switch (Impl) { 402 WindowsThreadImpl => windows.NTSTATUS, 403 LinuxThreadImpl => u8, 404 PosixThreadImpl => ?*anyopaque, 405 else => unreachable, 406 } { 407 maybeAttachSignalStack(); 408 409 const default_value = switch (Impl) { 410 WindowsThreadImpl => .SUCCESS, 411 LinuxThreadImpl => 0, 412 PosixThreadImpl => null, 413 else => unreachable, 414 }; 415 const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', '!noreturn', 'void', or '!void'"; 416 417 switch (@typeInfo(@typeInfo(@TypeOf(f)).@"fn".return_type.?)) { 418 .noreturn => { 419 @call(.auto, f, args); 420 }, 421 .void => { 422 @call(.auto, f, args); 423 return default_value; 424 }, 425 .int => |info| { 426 if (info.bits != 8) { 427 @compileError(bad_fn_ret); 428 } 429 430 const status = @call(.auto, f, args); 431 switch (Impl) { 432 WindowsThreadImpl => return @enumFromInt(status), 433 LinuxThreadImpl => return status, 434 // pthreads don't support exit status, ignore value 435 PosixThreadImpl => return default_value, 436 else => unreachable, 437 } 438 }, 439 .error_union => |info| { 440 switch (info.payload) { 441 void, noreturn => { 442 @call(.auto, f, args) catch |err| { 443 std.debug.print("error: {s}\n", .{@errorName(err)}); 444 if (@errorReturnTrace()) |trace| { 445 std.debug.dumpErrorReturnTrace(trace); 446 } 447 }; 448 449 return default_value; 450 }, 451 else => { 452 @compileError(bad_fn_ret); 453 }, 454 } 455 }, 456 else => { 457 @compileError(bad_fn_ret); 458 }, 459 } 460 } 461 462 /// We can't compile error in the `Impl` switch statement as its eagerly evaluated. 463 /// So instead, we compile-error on the methods themselves for platforms which don't support threads. 464 const UnsupportedImpl = struct { 465 pub const ThreadHandle = void; 466 467 fn getCurrentId() usize { 468 return unsupported({}); 469 } 470 471 fn getCpuCount() !usize { 472 return unsupported({}); 473 } 474 475 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl { 476 return unsupported(.{ config, f, args }); 477 } 478 479 fn getHandle(self: Impl) ThreadHandle { 480 return unsupported(self); 481 } 482 483 fn detach(self: Impl) void { 484 return unsupported(self); 485 } 486 487 fn join(self: Impl) void { 488 return unsupported(self); 489 } 490 491 fn unsupported(unused: anytype) noreturn { 492 _ = unused; 493 @compileError("Unsupported operating system " ++ @tagName(native_os)); 494 } 495 }; 496 497 const WindowsThreadImpl = struct { 498 pub const ThreadHandle = windows.HANDLE; 499 500 fn getCurrentId() windows.DWORD { 501 return windows.GetCurrentThreadId(); 502 } 503 504 fn getCpuCount() !usize { 505 // Faster than calling into GetSystemInfo(), even if amortized. 506 return windows.peb().NumberOfProcessors; 507 } 508 509 thread: *ThreadCompletion, 510 511 const ThreadCompletion = struct { 512 completion: Completion, 513 heap_ptr: windows.PVOID, 514 heap_handle: *windows.HEAP, 515 thread_handle: windows.HANDLE = undefined, 516 517 fn free(self: ThreadCompletion) void { 518 const status = windows.ntdll.RtlFreeHeap(self.heap_handle, .{}, self.heap_ptr); 519 assert(status != 0); 520 } 521 }; 522 523 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl { 524 const Args = @TypeOf(args); 525 const Instance = struct { 526 fn_args: Args, 527 thread: ThreadCompletion, 528 529 fn entryFn(raw_ptr: windows.PVOID) callconv(.winapi) windows.NTSTATUS { 530 const self: *@This() = @ptrCast(@alignCast(raw_ptr)); 531 defer switch (self.thread.completion.swap(.completed, .seq_cst)) { 532 .running => {}, 533 .completed => unreachable, 534 .detached => self.thread.free(), 535 }; 536 return callFn(f, self.fn_args); 537 } 538 }; 539 540 const heap_handle = windows.GetProcessHeap() orelse return error.OutOfMemory; 541 const alloc_bytes = @alignOf(Instance) + @sizeOf(Instance); 542 const alloc_ptr = windows.ntdll.RtlAllocateHeap(heap_handle, .{}, alloc_bytes) orelse return error.OutOfMemory; 543 errdefer assert(windows.ntdll.RtlFreeHeap(heap_handle, .{}, alloc_ptr) != 0); 544 545 const instance_bytes = @as([*]u8, @ptrCast(alloc_ptr))[0..alloc_bytes]; 546 var fba = std.heap.FixedBufferAllocator.init(instance_bytes); 547 const instance = fba.allocator().create(Instance) catch unreachable; 548 instance.* = .{ 549 .fn_args = args, 550 .thread = .{ 551 .completion = Completion.init(.running), 552 .heap_ptr = alloc_ptr, 553 .heap_handle = heap_handle, 554 }, 555 }; 556 557 // Windows appears to only support SYSTEM.BASIC_INFORMATION.AllocationGranularity 558 // minimum stack size. Going lower makes it default to that specified in the executable 559 // (~1mb). Its also fine if the limit here is incorrect as stack size is only a hint. 560 const stack_size = @max(64 * 1024, std.math.lossyCast(u32, config.stack_size)); 561 562 // Intended to be equivalent to a kernel32.CreateThread call with no flags set. 563 // However, CreateThread is just a wrapper around CreateRemoteThreadEx, 564 // so that's the more relevant function in this context. 565 // 566 // https://github.com/wine-mirror/wine/blob/3d128be6400b3869119d293d0c8fa9e7702978f8/dlls/kernelbase/thread.c#L85 567 instance.thread.thread_handle = blk: { 568 var active_ctx: ?windows.HANDLE = undefined; 569 // Note: Can return null on SUCCESS 570 switch (windows.ntdll.RtlGetActiveActivationContext(&active_ctx)) { 571 .SUCCESS => {}, 572 else => |status| return windows.unexpectedStatus(status), 573 } 574 defer if (active_ctx) |ctx| windows.ntdll.RtlReleaseActivationContext(ctx); 575 576 var teb: *windows.TEB = undefined; 577 var attr_list = windows.PS.ATTRIBUTE.LIST{ 578 .TotalLength = @sizeOf(windows.PS.ATTRIBUTE.LIST), 579 .Attributes = .{ 580 .{ 581 .Attribute = .TEB_ADDRESS, 582 .Size = @sizeOf(*windows.TEB), 583 .u = .{ 584 .ValuePtr = @ptrCast(&teb), 585 }, 586 .ReturnLength = null, 587 }, 588 }, 589 }; 590 591 var thread_handle: windows.HANDLE = undefined; 592 switch (windows.ntdll.NtCreateThreadEx( 593 &thread_handle, 594 .{ .MAXIMUM_ALLOWED = true }, 595 &.{}, 596 windows.GetCurrentProcess(), 597 Instance.entryFn, 598 instance, 599 .{ .CREATE_SUSPENDED = true }, 600 0, 601 @enumFromInt(stack_size), 602 .default, 603 &attr_list, 604 )) { 605 .SUCCESS => {}, 606 else => |status| return windows.unexpectedStatus(status), 607 } 608 609 if (active_ctx) |ctx| { 610 var cookie: windows.ULONG = 0; 611 switch (windows.ntdll.RtlActivateActivationContextEx(0, teb, ctx, &cookie)) { 612 .SUCCESS => {}, 613 else => |status| return windows.unexpectedStatus(status), 614 } 615 } 616 617 switch (windows.ntdll.NtResumeThread(thread_handle, null)) { 618 .SUCCESS => {}, 619 else => |status| return windows.unexpectedStatus(status), 620 } 621 622 break :blk thread_handle; 623 }; 624 625 return Impl{ .thread = &instance.thread }; 626 } 627 628 fn getHandle(self: Impl) ThreadHandle { 629 return self.thread.thread_handle; 630 } 631 632 fn detach(self: Impl) void { 633 windows.CloseHandle(self.thread.thread_handle); 634 switch (self.thread.completion.swap(.detached, .seq_cst)) { 635 .running => {}, 636 .completed => self.thread.free(), 637 .detached => unreachable, 638 } 639 } 640 641 fn join(self: Impl) void { 642 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); 643 switch (windows.ntdll.NtWaitForSingleObject(self.thread.thread_handle, .FALSE, &infinite_timeout)) { 644 windows.NTSTATUS.WAIT_0 => {}, 645 else => |status| windows.unexpectedStatus(status) catch unreachable, 646 } 647 windows.CloseHandle(self.thread.thread_handle); 648 assert(self.thread.completion.load(.seq_cst) == .completed); 649 self.thread.free(); 650 } 651 }; 652 653 const PosixThreadImpl = struct { 654 const c = std.c; 655 656 pub const ThreadHandle = c.pthread_t; 657 658 fn getCurrentId() Id { 659 switch (native_os) { 660 .linux => { 661 return LinuxThreadImpl.getCurrentId(); 662 }, 663 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { 664 var thread_id: u64 = undefined; 665 // Pass thread=null to get the current thread ID. 666 assert(c.pthread_threadid_np(null, &thread_id) == 0); 667 return thread_id; 668 }, 669 .dragonfly => { 670 return @as(u32, @bitCast(c.lwp_gettid())); 671 }, 672 .netbsd => { 673 return @as(u32, @bitCast(c._lwp_self())); 674 }, 675 .freebsd => { 676 return @as(u32, @bitCast(c.pthread_getthreadid_np())); 677 }, 678 .openbsd => { 679 return @as(u32, @bitCast(c.getthrid())); 680 }, 681 .haiku => { 682 return @as(u32, @bitCast(c.find_thread(null))); 683 }, 684 .serenity => { 685 return @as(u32, @bitCast(c.pthread_self())); 686 }, 687 else => { 688 return @intFromPtr(c.pthread_self()); 689 }, 690 } 691 } 692 693 fn getCpuCount() !usize { 694 switch (native_os) { 695 .linux => { 696 return LinuxThreadImpl.getCpuCount(); 697 }, 698 .emscripten => { 699 return @as(usize, @intCast(std.os.emscripten.emscripten_num_logical_cores())); 700 }, 701 .openbsd => { 702 var count: c_int = undefined; 703 var count_size: usize = @sizeOf(c_int); 704 const mib = [_]c_int{ std.c.CTL.HW, std.c.HW.NCPUONLINE }; 705 posix.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) { 706 error.NameTooLong, error.UnknownName => unreachable, 707 else => |e| return e, 708 }; 709 return @as(usize, @intCast(count)); 710 }, 711 .illumos, .serenity => { 712 // The "proper" way to get the cpu count would be to query 713 // /dev/kstat via ioctls, and traverse a linked list for each 714 // cpu. (illumos) 715 const rc = c.sysconf(@intFromEnum(std.c._SC.NPROCESSORS_ONLN)); 716 return switch (posix.errno(rc)) { 717 .SUCCESS => @as(usize, @intCast(rc)), 718 else => |err| posix.unexpectedErrno(err), 719 }; 720 }, 721 .haiku => { 722 var system_info: std.c.system_info = undefined; 723 const rc = std.c.get_system_info(&system_info); // always returns B_OK 724 return switch (posix.errno(rc)) { 725 .SUCCESS => @as(usize, @intCast(system_info.cpu_count)), 726 else => |err| posix.unexpectedErrno(err), 727 }; 728 }, 729 else => { 730 var count: c_int = undefined; 731 var count_len: usize = @sizeOf(c_int); 732 const name = comptime if (target.os.tag.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; 733 switch (posix.errno(posix.system.sysctlbyname(name, &count, &count_len, null, 0))) { 734 .SUCCESS => return @intCast(count), 735 .FAULT => unreachable, 736 .PERM => return error.PermissionDenied, 737 .NOMEM => return error.SystemResources, 738 .NOENT => unreachable, 739 else => |err| return posix.unexpectedErrno(err), 740 } 741 }, 742 } 743 } 744 745 handle: ThreadHandle, 746 747 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl { 748 const Args = @TypeOf(args); 749 const allocator = std.heap.c_allocator; 750 751 const Instance = struct { 752 fn entryFn(raw_arg: ?*anyopaque) callconv(.c) ?*anyopaque { 753 const args_ptr: *Args = @ptrCast(@alignCast(raw_arg)); 754 defer allocator.destroy(args_ptr); 755 return callFn(f, args_ptr.*); 756 } 757 }; 758 759 const args_ptr = try allocator.create(Args); 760 args_ptr.* = args; 761 errdefer allocator.destroy(args_ptr); 762 763 var attr: c.pthread_attr_t = undefined; 764 if (c.pthread_attr_init(&attr) != .SUCCESS) return error.SystemResources; 765 defer assert(c.pthread_attr_destroy(&attr) == .SUCCESS); 766 767 // Use the same set of parameters used by the libc-less impl. 768 const stack_size = @max(config.stack_size, 16 * 1024); 769 assert(c.pthread_attr_setstacksize(&attr, stack_size) == .SUCCESS); 770 assert(c.pthread_attr_setguardsize(&attr, std.heap.pageSize()) == .SUCCESS); 771 772 var handle: c.pthread_t = undefined; 773 switch (c.pthread_create( 774 &handle, 775 &attr, 776 Instance.entryFn, 777 @ptrCast(args_ptr), 778 )) { 779 .SUCCESS => return Impl{ .handle = handle }, 780 .AGAIN => return error.SystemResources, 781 .PERM => unreachable, 782 .INVAL => unreachable, 783 else => |err| return posix.unexpectedErrno(err), 784 } 785 } 786 787 fn getHandle(self: Impl) ThreadHandle { 788 return self.handle; 789 } 790 791 fn detach(self: Impl) void { 792 switch (c.pthread_detach(self.handle)) { 793 .SUCCESS => {}, 794 .INVAL => unreachable, // thread handle is not joinable 795 .SRCH => unreachable, // thread handle is invalid 796 else => unreachable, 797 } 798 } 799 800 fn join(self: Impl) void { 801 switch (c.pthread_join(self.handle, null)) { 802 .SUCCESS => {}, 803 .INVAL => unreachable, // thread handle is not joinable (or another thread is already joining in) 804 .SRCH => unreachable, // thread handle is invalid 805 .DEADLK => unreachable, // two threads tried to join each other 806 else => unreachable, 807 } 808 } 809 }; 810 811 const WasiThreadImpl = struct { 812 thread: *WasiThread, 813 814 pub const ThreadHandle = i32; 815 threadlocal var tls_thread_id: Id = 0; 816 817 const WasiThread = struct { 818 /// Thread ID 819 tid: std.atomic.Value(i32) = std.atomic.Value(i32).init(0), 820 /// Contains all memory which was allocated to bootstrap this thread, including: 821 /// - Guard page 822 /// - Stack 823 /// - TLS segment 824 /// - `Instance` 825 /// All memory is freed upon call to `join` 826 memory: []u8, 827 /// The allocator used to allocate the thread's memory, 828 /// which is also used during `join` to ensure clean-up. 829 allocator: std.mem.Allocator, 830 /// The current state of the thread. 831 state: State = State.init(.running), 832 }; 833 834 /// A meta-data structure used to bootstrap a thread 835 const Instance = struct { 836 thread: WasiThread, 837 /// Contains the offset to the new __tls_base. 838 /// The offset starting from the memory's base. 839 tls_offset: usize, 840 /// Contains the offset to the stack for the newly spawned thread. 841 /// The offset is calculated starting from the memory's base. 842 stack_offset: usize, 843 /// Contains the raw pointer value to the wrapper which holds all arguments 844 /// for the callback. 845 raw_ptr: usize, 846 /// Function pointer to a wrapping function which will call the user's 847 /// function upon thread spawn. The above mentioned pointer will be passed 848 /// to this function pointer as its argument. 849 call_back: *const fn (usize) void, 850 /// When a thread is in `detached` state, we must free all of its memory 851 /// upon thread completion. However, as this is done while still within 852 /// the thread, we must first jump back to the main thread's stack or else 853 /// we end up freeing the stack that we're currently using. 854 original_stack_pointer: [*]u8, 855 }; 856 857 const State = std.atomic.Value(enum(u8) { running, completed, detached }); 858 859 fn getCurrentId() Id { 860 return tls_thread_id; 861 } 862 863 fn getCpuCount() error{Unsupported}!noreturn { 864 return error.Unsupported; 865 } 866 867 fn getHandle(self: Impl) ThreadHandle { 868 return self.thread.tid.load(.seq_cst); 869 } 870 871 fn detach(self: Impl) void { 872 switch (self.thread.state.swap(.detached, .seq_cst)) { 873 .running => {}, 874 .completed => self.join(), 875 .detached => unreachable, 876 } 877 } 878 879 fn join(self: Impl) void { 880 defer { 881 // Create a copy of the allocator so we do not free the reference to the 882 // original allocator while freeing the memory. 883 var allocator = self.thread.allocator; 884 allocator.free(self.thread.memory); 885 } 886 887 while (true) { 888 const tid = self.thread.tid.load(.seq_cst); 889 if (tid == 0) break; 890 891 const result = asm ( 892 \\ local.get %[ptr] 893 \\ local.get %[expected] 894 \\ i64.const -1 # infinite 895 \\ memory.atomic.wait32 0 896 \\ local.set %[ret] 897 : [ret] "=r" (-> u32), 898 : [ptr] "r" (&self.thread.tid.raw), 899 [expected] "r" (tid), 900 ); 901 switch (result) { 902 0 => continue, // ok 903 1 => continue, // expected =! loaded 904 2 => unreachable, // timeout (infinite) 905 else => unreachable, 906 } 907 } 908 } 909 910 fn spawn(config: std.Thread.SpawnConfig, comptime f: anytype, args: anytype) SpawnError!WasiThreadImpl { 911 if (config.allocator == null) { 912 @panic("an allocator is required to spawn a WASI thread"); 913 } 914 915 // Wrapping struct required to hold the user-provided function arguments. 916 const Wrapper = struct { 917 args: @TypeOf(args), 918 fn entry(ptr: usize) void { 919 const w: *@This() = @ptrFromInt(ptr); 920 const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"; 921 switch (@typeInfo(@typeInfo(@TypeOf(f)).@"fn".return_type.?)) { 922 .noreturn, .void => { 923 @call(.auto, f, w.args); 924 }, 925 .int => |info| { 926 if (info.bits != 8) { 927 @compileError(bad_fn_ret); 928 } 929 _ = @call(.auto, f, w.args); // WASI threads don't support exit status, ignore value 930 }, 931 .error_union => |info| { 932 if (info.payload != void) { 933 @compileError(bad_fn_ret); 934 } 935 @call(.auto, f, w.args) catch |err| { 936 std.debug.print("error: {s}\n", .{@errorName(err)}); 937 if (@errorReturnTrace()) |trace| { 938 std.debug.dumpErrorReturnTrace(trace); 939 } 940 }; 941 }, 942 else => { 943 @compileError(bad_fn_ret); 944 }, 945 } 946 } 947 }; 948 949 var stack_offset: usize = undefined; 950 var tls_offset: usize = undefined; 951 var wrapper_offset: usize = undefined; 952 var instance_offset: usize = undefined; 953 954 // Calculate the bytes we have to allocate to store all thread information, including: 955 // - The actual stack for the thread 956 // - The TLS segment 957 // - `Instance` - containing information about how to call the user's function. 958 const map_bytes = blk: { 959 // start with atleast a single page, which is used as a guard to prevent 960 // other threads clobbering our new thread. 961 // Unfortunately, WebAssembly has no notion of read-only segments, so this 962 // is only a best effort. 963 var bytes: usize = std.wasm.page_size; 964 965 bytes = std.mem.alignForward(usize, bytes, 16); // align stack to 16 bytes 966 stack_offset = bytes; 967 bytes += @max(std.wasm.page_size, config.stack_size); 968 969 bytes = std.mem.alignForward(usize, bytes, __tls_align()); 970 tls_offset = bytes; 971 bytes += __tls_size(); 972 973 bytes = std.mem.alignForward(usize, bytes, @alignOf(Wrapper)); 974 wrapper_offset = bytes; 975 bytes += @sizeOf(Wrapper); 976 977 bytes = std.mem.alignForward(usize, bytes, @alignOf(Instance)); 978 instance_offset = bytes; 979 bytes += @sizeOf(Instance); 980 981 bytes = std.mem.alignForward(usize, bytes, std.wasm.page_size); 982 break :blk bytes; 983 }; 984 985 // Allocate the amount of memory required for all meta data. 986 const allocated_memory = try config.allocator.?.alloc(u8, map_bytes); 987 988 const wrapper: *Wrapper = @ptrCast(@alignCast(&allocated_memory[wrapper_offset])); 989 wrapper.* = .{ .args = args }; 990 991 const instance: *Instance = @ptrCast(@alignCast(&allocated_memory[instance_offset])); 992 instance.* = .{ 993 .thread = .{ .memory = allocated_memory, .allocator = config.allocator.? }, 994 .tls_offset = tls_offset, 995 .stack_offset = stack_offset, 996 .raw_ptr = @intFromPtr(wrapper), 997 .call_back = &Wrapper.entry, 998 .original_stack_pointer = __get_stack_pointer(), 999 }; 1000 1001 const tid = spawnWasiThread(instance); 1002 // The specification says any value lower than 0 indicates an error. 1003 // The values of such error are unspecified. WASI-Libc treats it as EAGAIN. 1004 if (tid < 0) { 1005 return error.SystemResources; 1006 } 1007 instance.thread.tid.store(tid, .seq_cst); 1008 1009 return .{ .thread = &instance.thread }; 1010 } 1011 1012 comptime { 1013 if (!builtin.single_threaded) { 1014 @export(&wasi_thread_start, .{ .name = "wasi_thread_start" }); 1015 } 1016 } 1017 1018 /// Called by the host environment after thread creation. 1019 fn wasi_thread_start(tid: i32, arg: *Instance) callconv(.c) void { 1020 comptime assert(!builtin.single_threaded); 1021 __set_stack_pointer(arg.thread.memory.ptr + arg.stack_offset); 1022 __wasm_init_tls(arg.thread.memory.ptr + arg.tls_offset); 1023 @atomicStore(u32, &WasiThreadImpl.tls_thread_id, @intCast(tid), .seq_cst); 1024 1025 // Finished bootstrapping, call user's procedure. 1026 arg.call_back(arg.raw_ptr); 1027 1028 switch (arg.thread.state.swap(.completed, .seq_cst)) { 1029 .running => { 1030 // reset the Thread ID 1031 asm volatile ( 1032 \\ local.get %[ptr] 1033 \\ i32.const 0 1034 \\ i32.atomic.store 0 1035 : 1036 : [ptr] "r" (&arg.thread.tid.raw), 1037 ); 1038 1039 // Wake the main thread listening to this thread 1040 asm volatile ( 1041 \\ local.get %[ptr] 1042 \\ i32.const 1 # waiters 1043 \\ memory.atomic.notify 0 1044 \\ drop # no need to know the waiters 1045 : 1046 : [ptr] "r" (&arg.thread.tid.raw), 1047 ); 1048 }, 1049 .completed => unreachable, 1050 .detached => { 1051 // restore the original stack pointer so we can free the memory 1052 // without having to worry about freeing the stack 1053 __set_stack_pointer(arg.original_stack_pointer); 1054 // Ensure a copy so we don't free the allocator reference itself 1055 var allocator = arg.thread.allocator; 1056 allocator.free(arg.thread.memory); 1057 }, 1058 } 1059 } 1060 1061 /// Asks the host to create a new thread for us. 1062 /// Newly created thread will call `wasi_tread_start` with the thread ID as well 1063 /// as the input `arg` that was provided to `spawnWasiThread` 1064 const spawnWasiThread = @"thread-spawn"; 1065 extern "wasi" fn @"thread-spawn"(arg: *Instance) i32; 1066 1067 /// Initializes the TLS data segment starting at `memory`. 1068 /// This is a synthetic function, generated by the linker. 1069 extern fn __wasm_init_tls(memory: [*]u8) void; 1070 1071 /// Returns a pointer to the base of the TLS data segment for the current thread 1072 inline fn __tls_base() [*]u8 { 1073 return asm ( 1074 \\ .globaltype __tls_base, i32 1075 \\ global.get __tls_base 1076 \\ local.set %[ret] 1077 : [ret] "=r" (-> [*]u8), 1078 ); 1079 } 1080 1081 /// Returns the size of the TLS segment 1082 inline fn __tls_size() u32 { 1083 return asm volatile ( 1084 \\ .globaltype __tls_size, i32, immutable 1085 \\ global.get __tls_size 1086 \\ local.set %[ret] 1087 : [ret] "=r" (-> u32), 1088 ); 1089 } 1090 1091 /// Returns the alignment of the TLS segment 1092 inline fn __tls_align() u32 { 1093 return asm ( 1094 \\ .globaltype __tls_align, i32, immutable 1095 \\ global.get __tls_align 1096 \\ local.set %[ret] 1097 : [ret] "=r" (-> u32), 1098 ); 1099 } 1100 1101 /// Allows for setting the stack pointer in the WebAssembly module. 1102 inline fn __set_stack_pointer(addr: [*]u8) void { 1103 asm volatile ( 1104 \\ local.get %[ptr] 1105 \\ global.set __stack_pointer 1106 : 1107 : [ptr] "r" (addr), 1108 ); 1109 } 1110 1111 /// Returns the current value of the stack pointer 1112 inline fn __get_stack_pointer() [*]u8 { 1113 return asm ( 1114 \\ global.get __stack_pointer 1115 \\ local.set %[stack_ptr] 1116 : [stack_ptr] "=r" (-> [*]u8), 1117 ); 1118 } 1119 }; 1120 1121 const LinuxThreadImpl = struct { 1122 const linux = std.os.linux; 1123 1124 pub const ThreadHandle = i32; 1125 1126 threadlocal var tls_thread_id: ?Id = null; 1127 1128 fn getCurrentId() Id { 1129 return tls_thread_id orelse { 1130 const tid: u32 = @bitCast(linux.gettid()); 1131 tls_thread_id = tid; 1132 return tid; 1133 }; 1134 } 1135 1136 fn getCpuCount() !usize { 1137 const cpu_set = try posix.sched_getaffinity(0); 1138 return posix.CPU_COUNT(cpu_set); 1139 } 1140 1141 thread: *ThreadCompletion, 1142 1143 const ThreadCompletion = struct { 1144 completion: Completion = Completion.init(.running), 1145 child_tid: std.atomic.Value(i32) = std.atomic.Value(i32).init(1), 1146 parent_tid: i32 = undefined, 1147 mapped: []align(std.heap.page_size_min) u8, 1148 1149 /// Calls `munmap(mapped.ptr, mapped.len)` then `exit(1)` without touching the stack (which lives in `mapped.ptr`). 1150 /// Ported over from musl libc's pthread detached implementation: 1151 /// https://github.com/ifduyue/musl/search?q=__unmapself 1152 fn freeAndExit(self: *ThreadCompletion) noreturn { 1153 // If we do not reset the child_tidptr to null here, the kernel would later write the 1154 // value zero to that address, which is inside the block we're unmapping below, after 1155 // our thread exits. This can sometimes corrupt memory in other mmap blocks from 1156 // unrelated concurrent threads. 1157 _ = linux.set_tid_address(null); 1158 // If a signal were delivered between SYS_munmap and SYS_exit, any installed signal 1159 // handler would immediately segfault due to the stack being unmapped. To avoid this, 1160 // we need to mask all signals before entering the inline asm. 1161 posix.sigprocmask(std.posix.SIG.BLOCK, &std.os.linux.sigfillset(), null); 1162 switch (target.cpu.arch) { 1163 .x86 => asm volatile ( 1164 \\ movl $91, %%eax # SYS_munmap 1165 \\ int $128 1166 \\ movl $1, %%eax # SYS_exit 1167 \\ movl $0, %%ebx 1168 \\ int $128 1169 : 1170 : [ptr] "{ebx}" (@intFromPtr(self.mapped.ptr)), 1171 [len] "{ecx}" (self.mapped.len), 1172 ), 1173 .x86_64 => asm volatile (switch (target.abi) { 1174 .gnux32, .muslx32 => 1175 \\ movl $0x4000000b, %%eax # SYS_munmap 1176 \\ syscall 1177 \\ movl $0x4000003c, %%eax # SYS_exit 1178 \\ xor %%rdi, %%rdi 1179 \\ syscall 1180 , 1181 else => 1182 \\ movl $11, %%eax # SYS_munmap 1183 \\ syscall 1184 \\ movl $60, %%eax # SYS_exit 1185 \\ xor %%rdi, %%rdi 1186 \\ syscall 1187 , 1188 } 1189 : 1190 : [ptr] "{rdi}" (@intFromPtr(self.mapped.ptr)), 1191 [len] "{rsi}" (self.mapped.len), 1192 ), 1193 .arm, .armeb, .thumb, .thumbeb => asm volatile ( 1194 \\ mov r7, #91 // SYS_munmap 1195 \\ svc 0 1196 \\ mov r7, #1 // SYS_exit 1197 \\ mov r0, #0 1198 \\ svc 0 1199 : 1200 : [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)), 1201 [len] "{r1}" (self.mapped.len), 1202 ), 1203 .aarch64, .aarch64_be => asm volatile ( 1204 \\ mov x8, #215 // SYS_munmap 1205 \\ svc 0 1206 \\ mov x8, #93 // SYS_exit 1207 \\ mov x0, #0 1208 \\ svc 0 1209 : 1210 : [ptr] "{x0}" (@intFromPtr(self.mapped.ptr)), 1211 [len] "{x1}" (self.mapped.len), 1212 ), 1213 .alpha => asm volatile ( 1214 \\ ldi $0, 73 # SYS_munmap 1215 \\ callsys 1216 \\ ldi $0, 1 # SYS_exit 1217 \\ ldi $16, 0 1218 \\ callsys 1219 : 1220 : [ptr] "{$16}" (@intFromPtr(self.mapped.ptr)), 1221 [len] "{$17}" (self.mapped.len), 1222 ), 1223 .hexagon => asm volatile ( 1224 \\ r6 = #215 // SYS_munmap 1225 \\ trap0(#1) 1226 \\ r6 = #93 // SYS_exit 1227 \\ r0 = #0 1228 \\ trap0(#1) 1229 : 1230 : [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)), 1231 [len] "{r1}" (self.mapped.len), 1232 ), 1233 .hppa => asm volatile ( 1234 \\ ldi 91, %%r20 /* SYS_munmap */ 1235 \\ ble 0x100(%%sr2, %%r0) 1236 \\ ldi 1, %%r20 /* SYS_exit */ 1237 \\ ldi 0, %%r26 1238 \\ ble 0x100(%%sr2, %%r0) 1239 : 1240 : [ptr] "{r26}" (@intFromPtr(self.mapped.ptr)), 1241 [len] "{r25}" (self.mapped.len), 1242 ), 1243 .m68k => asm volatile ( 1244 \\ move.l #91, %%d0 // SYS_munmap 1245 \\ trap #0 1246 \\ move.l #1, %%d0 // SYS_exit 1247 \\ move.l #0, %%d1 1248 \\ trap #0 1249 : 1250 : [ptr] "{d1}" (@intFromPtr(self.mapped.ptr)), 1251 [len] "{d2}" (self.mapped.len), 1252 ), 1253 .microblaze, .microblazeel => asm volatile ( 1254 \\ ori r12, r0, 91 # SYS_munmap 1255 \\ brki r14, 0x8 1256 \\ ori r12, r0, 1 # SYS_exit 1257 \\ or r5, r0, r0 1258 \\ brki r14, 0x8 1259 : 1260 : [ptr] "{r5}" (@intFromPtr(self.mapped.ptr)), 1261 [len] "{r6}" (self.mapped.len), 1262 ), 1263 // We set `sp` to the address of the current function as a workaround for a Linux 1264 // kernel bug that caused syscalls to return EFAULT if the stack pointer is invalid. 1265 // The bug was introduced in 46e12c07b3b9603c60fc1d421ff18618241cb081 and fixed in 1266 // 7928eb0370d1133d0d8cd2f5ddfca19c309079d5. 1267 .mips, .mipsel => asm volatile ( 1268 \\ move $sp, $t9 1269 \\ li $v0, 4091 # SYS_munmap 1270 \\ syscall 1271 \\ li $v0, 4001 # SYS_exit 1272 \\ li $a0, 0 1273 \\ syscall 1274 : 1275 : [ptr] "{$4}" (@intFromPtr(self.mapped.ptr)), 1276 [len] "{$5}" (self.mapped.len), 1277 ), 1278 .mips64, .mips64el => asm volatile (switch (target.abi) { 1279 .gnuabin32, .muslabin32 => 1280 \\ li $v0, 6011 # SYS_munmap 1281 \\ syscall 1282 \\ li $v0, 6058 # SYS_exit 1283 \\ li $a0, 0 1284 \\ syscall 1285 , 1286 else => 1287 \\ li $v0, 5011 # SYS_munmap 1288 \\ syscall 1289 \\ li $v0, 5058 # SYS_exit 1290 \\ li $a0, 0 1291 \\ syscall 1292 , 1293 } 1294 : 1295 : [ptr] "{$4}" (@intFromPtr(self.mapped.ptr)), 1296 [len] "{$5}" (self.mapped.len), 1297 ), 1298 .or1k => asm volatile ( 1299 \\ l.ori r11, r0, 215 # SYS_munmap 1300 \\ l.sys 1 1301 \\ l.ori r11, r0, 93 # SYS_exit 1302 \\ l.ori r3, r0, r0 1303 \\ l.sys 1 1304 : 1305 : [ptr] "{r3}" (@intFromPtr(self.mapped.ptr)), 1306 [len] "{r4}" (self.mapped.len), 1307 ), 1308 .powerpc, .powerpcle, .powerpc64, .powerpc64le => asm volatile ( 1309 \\ li 0, 91 # SYS_munmap 1310 \\ sc 1311 \\ li 0, 1 # SYS_exit 1312 \\ li 3, 0 1313 \\ sc 1314 \\ blr 1315 : 1316 : [ptr] "{r3}" (@intFromPtr(self.mapped.ptr)), 1317 [len] "{r4}" (self.mapped.len), 1318 ), 1319 .riscv32, .riscv64 => asm volatile ( 1320 \\ li a7, 215 # SYS_munmap 1321 \\ ecall 1322 \\ li a7, 93 # SYS_exit 1323 \\ mv a0, zero 1324 \\ ecall 1325 : 1326 : [ptr] "{a0}" (@intFromPtr(self.mapped.ptr)), 1327 [len] "{a1}" (self.mapped.len), 1328 ), 1329 .s390x => asm volatile ( 1330 \\ svc 91 # SYS_munmap 1331 \\ lghi %%r2, 0 1332 \\ svc 1 # SYS_exit 1333 : 1334 : [ptr] "{r2}" (@intFromPtr(self.mapped.ptr)), 1335 [len] "{r3}" (self.mapped.len), 1336 ), 1337 .sh, .sheb => asm volatile ( 1338 \\ mov #91, r3 ! SYS_munmap 1339 \\ trapa #31 1340 \\ or r0, r0 1341 \\ or r0, r0 1342 \\ or r0, r0 1343 \\ or r0, r0 1344 \\ or r0, r0 1345 \\ mov #1, r3 ! SYS_exit 1346 \\ mov #0, r4 1347 \\ trapa #31 1348 \\ or r0, r0 1349 \\ or r0, r0 1350 \\ or r0, r0 1351 \\ or r0, r0 1352 \\ or r0, r0 1353 : 1354 : [ptr] "{r4}" (@intFromPtr(self.mapped.ptr)), 1355 [len] "{r5}" (self.mapped.len), 1356 ), 1357 .sparc => asm volatile ( 1358 \\ # See sparc64 comments below. 1359 \\ 1: 1360 \\ cmp %%fp, 0 1361 \\ beq 2f 1362 \\ nop 1363 \\ ba 1b 1364 \\ restore 1365 \\ 2: 1366 \\ mov %%g1, %%o0 // ptr 1367 \\ mov %%g2, %%o1 // len 1368 \\ mov 73, %%g1 // SYS_munmap 1369 \\ t 0x3 # ST_FLUSH_WINDOWS 1370 \\ t 0x10 1371 \\ mov 1, %%g1 // SYS_exit 1372 \\ mov 0, %%o0 1373 \\ t 0x10 1374 : 1375 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)), 1376 [len] "{g2}" (self.mapped.len), 1377 : .{ .memory = true }), 1378 .sparc64 => asm volatile ( 1379 \\ # SPARCs really don't like it when active stack frames 1380 \\ # is unmapped (it will result in a segfault), so we 1381 \\ # force-deactivate it by running `restore` until 1382 \\ # all frames are cleared. 1383 \\ 1: 1384 \\ cmp %%fp, 0 1385 \\ beq 2f 1386 \\ nop 1387 \\ ba 1b 1388 \\ restore 1389 \\ 2: 1390 \\ mov %%g1, %%o0 // ptr 1391 \\ mov %%g2, %%o1 // len 1392 \\ mov 73, %%g1 // SYS_munmap 1393 \\ # Flush register window contents to prevent background 1394 \\ # memory access before unmapping the stack. 1395 \\ flushw 1396 \\ t 0x6d 1397 \\ mov 1, %%g1 // SYS_exit 1398 \\ mov 0, %%o0 1399 \\ t 0x6d 1400 : 1401 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)), 1402 [len] "{g2}" (self.mapped.len), 1403 : .{ .memory = true }), 1404 .loongarch32, .loongarch64 => asm volatile ( 1405 \\ ori $a7, $zero, 215 # SYS_munmap 1406 \\ syscall 0 # call munmap 1407 \\ ori $a0, $zero, 0 1408 \\ ori $a7, $zero, 93 # SYS_exit 1409 \\ syscall 0 # call exit 1410 : 1411 : [ptr] "{r4}" (@intFromPtr(self.mapped.ptr)), 1412 [len] "{r5}" (self.mapped.len), 1413 : .{ .memory = true }), 1414 else => |cpu_arch| @compileError("Unsupported linux arch: " ++ @tagName(cpu_arch)), 1415 } 1416 unreachable; 1417 } 1418 }; 1419 1420 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl { 1421 const page_size = std.heap.pageSize(); 1422 const Args = @TypeOf(args); 1423 const Instance = struct { 1424 fn_args: Args, 1425 thread: ThreadCompletion, 1426 1427 fn entryFn(raw_arg: usize) callconv(.c) u8 { 1428 const self = @as(*@This(), @ptrFromInt(raw_arg)); 1429 defer switch (self.thread.completion.swap(.completed, .seq_cst)) { 1430 .running => {}, 1431 .completed => unreachable, 1432 .detached => self.thread.freeAndExit(), 1433 }; 1434 return callFn(f, self.fn_args); 1435 } 1436 }; 1437 1438 var guard_offset: usize = undefined; 1439 var stack_offset: usize = undefined; 1440 var tls_offset: usize = undefined; 1441 var instance_offset: usize = undefined; 1442 1443 const map_bytes = blk: { 1444 var bytes: usize = page_size; 1445 guard_offset = bytes; 1446 1447 bytes += @max(page_size, config.stack_size); 1448 bytes = std.mem.alignForward(usize, bytes, page_size); 1449 stack_offset = bytes; 1450 1451 bytes = std.mem.alignForward(usize, bytes, linux.tls.area_desc.alignment); 1452 tls_offset = bytes; 1453 bytes += linux.tls.area_desc.size; 1454 1455 bytes = std.mem.alignForward(usize, bytes, @alignOf(Instance)); 1456 instance_offset = bytes; 1457 bytes += @sizeOf(Instance); 1458 1459 bytes = std.mem.alignForward(usize, bytes, page_size); 1460 break :blk bytes; 1461 }; 1462 1463 // map all memory needed without read/write permissions 1464 // to avoid committing the whole region right away 1465 // anonymous mapping ensures file descriptor limits are not exceeded 1466 const mapped = posix.mmap( 1467 null, 1468 map_bytes, 1469 .{}, 1470 .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, 1471 -1, 1472 0, 1473 ) catch |err| switch (err) { 1474 error.MemoryMappingNotSupported => unreachable, 1475 error.AccessDenied => unreachable, 1476 error.PermissionDenied => unreachable, 1477 error.ProcessFdQuotaExceeded => unreachable, 1478 error.SystemFdQuotaExceeded => unreachable, 1479 error.MappingAlreadyExists => unreachable, 1480 else => |e| return e, 1481 }; 1482 assert(mapped.len >= map_bytes); 1483 errdefer posix.munmap(mapped); 1484 1485 // Map everything but the guard page as read/write. 1486 const guarded: []align(std.heap.page_size_min) u8 = @alignCast(mapped[guard_offset..]); 1487 const protection: posix.PROT = .{ .READ = true, .WRITE = true }; 1488 switch (posix.errno(posix.system.mprotect(guarded.ptr, guarded.len, protection))) { 1489 .SUCCESS => {}, 1490 .NOMEM => return error.OutOfMemory, 1491 else => |err| return posix.unexpectedErrno(err), 1492 } 1493 1494 // Prepare the TLS segment and prepare a user_desc struct when needed on x86 1495 var tls_ptr = linux.tls.prepareArea(mapped[tls_offset..][0..linux.tls.area_desc.size]); 1496 var user_desc: if (target.cpu.arch == .x86) linux.user_desc else void = undefined; 1497 if (target.cpu.arch == .x86) { 1498 defer tls_ptr = @intFromPtr(&user_desc); 1499 user_desc = .{ 1500 .entry_number = linux.tls.area_desc.gdt_entry_number, 1501 .base_addr = tls_ptr, 1502 .limit = 0xfffff, 1503 .flags = .{ 1504 .seg_32bit = 1, 1505 .contents = 0, // Data 1506 .read_exec_only = 0, 1507 .limit_in_pages = 1, 1508 .seg_not_present = 0, 1509 .useable = 1, 1510 }, 1511 }; 1512 } 1513 1514 const instance: *Instance = @ptrCast(@alignCast(&mapped[instance_offset])); 1515 instance.* = .{ 1516 .fn_args = args, 1517 .thread = .{ .mapped = mapped }, 1518 }; 1519 1520 const flags: u32 = linux.CLONE.THREAD | linux.CLONE.DETACHED | 1521 linux.CLONE.VM | linux.CLONE.FS | linux.CLONE.FILES | 1522 linux.CLONE.PARENT_SETTID | linux.CLONE.CHILD_CLEARTID | 1523 linux.CLONE.SIGHAND | linux.CLONE.SYSVSEM | linux.CLONE.SETTLS; 1524 1525 switch (linux.errno(linux.clone( 1526 Instance.entryFn, 1527 @intFromPtr(&mapped[stack_offset]), 1528 flags, 1529 @intFromPtr(instance), 1530 &instance.thread.parent_tid, 1531 tls_ptr, 1532 &instance.thread.child_tid.raw, 1533 ))) { 1534 .SUCCESS => return Impl{ .thread = &instance.thread }, 1535 .AGAIN => return error.ThreadQuotaExceeded, 1536 .INVAL => unreachable, 1537 .NOMEM => return error.SystemResources, 1538 .NOSPC => unreachable, 1539 .PERM => unreachable, 1540 .USERS => unreachable, 1541 else => |err| return posix.unexpectedErrno(err), 1542 } 1543 } 1544 1545 fn getHandle(self: Impl) ThreadHandle { 1546 return self.thread.parent_tid; 1547 } 1548 1549 fn detach(self: Impl) void { 1550 switch (self.thread.completion.swap(.detached, .seq_cst)) { 1551 .running => {}, 1552 .completed => self.join(), 1553 .detached => unreachable, 1554 } 1555 } 1556 1557 fn join(self: Impl) void { 1558 defer posix.munmap(self.thread.mapped); 1559 1560 while (true) { 1561 const tid = self.thread.child_tid.load(.seq_cst); 1562 if (tid == 0) break; 1563 1564 switch (linux.errno(linux.futex_4arg( 1565 &self.thread.child_tid.raw, 1566 .{ .cmd = .WAIT, .private = false }, 1567 @bitCast(tid), 1568 null, 1569 ))) { 1570 .SUCCESS => continue, 1571 .INTR => continue, 1572 .AGAIN => continue, 1573 else => unreachable, 1574 } 1575 } 1576 } 1577 }; 1578 1579 fn testThreadName(io: Io, thread: *Thread) !void { 1580 const testCases: []const []const u8 = &.{ 1581 "mythread", 1582 &@as([max_name_len]u8, @splat('b')), 1583 }; 1584 1585 inline for (testCases) |tc| { 1586 try thread.setName(io, tc); 1587 1588 var name_buffer: [max_name_len:0]u8 = undefined; 1589 1590 const name = try thread.getName(&name_buffer); 1591 if (name) |value| { 1592 try std.testing.expectEqual(tc.len, value.len); 1593 try std.testing.expectEqualStrings(tc, value); 1594 } 1595 } 1596 } 1597 1598 test "setName, getName" { 1599 if (builtin.single_threaded) return error.SkipZigTest; 1600 1601 const io = testing.io; 1602 1603 const Context = struct { 1604 start_wait_event: Io.Event = .unset, 1605 test_done_event: Io.Event = .unset, 1606 thread_done_event: Io.Event = .unset, 1607 1608 done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), 1609 thread: Thread = undefined, 1610 1611 pub fn run(ctx: *@This()) !void { 1612 // Wait for the main thread to have set the thread field in the context. 1613 try ctx.start_wait_event.wait(io); 1614 1615 switch (native_os) { 1616 .windows => testThreadName(io, &ctx.thread) catch |err| switch (err) { 1617 error.Unsupported => return error.SkipZigTest, 1618 else => return err, 1619 }, 1620 else => try testThreadName(io, &ctx.thread), 1621 } 1622 1623 // Signal our test is done 1624 ctx.test_done_event.set(io); 1625 1626 // wait for the thread to property exit 1627 try ctx.thread_done_event.wait(io); 1628 } 1629 }; 1630 1631 var context = Context{}; 1632 var thread = try spawn(.{}, Context.run, .{&context}); 1633 1634 context.thread = thread; 1635 context.start_wait_event.set(io); 1636 try context.test_done_event.wait(io); 1637 1638 switch (native_os) { 1639 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { 1640 const res = thread.setName(io, "foobar"); 1641 try std.testing.expectError(error.Unsupported, res); 1642 }, 1643 .windows => testThreadName(io, &thread) catch |err| switch (err) { 1644 error.Unsupported => return error.SkipZigTest, 1645 else => return err, 1646 }, 1647 else => try testThreadName(io, &thread), 1648 } 1649 1650 context.thread_done_event.set(io); 1651 thread.join(); 1652 } 1653 1654 fn testIncrementNotify(io: Io, value: *usize, event: *Io.Event) void { 1655 value.* += 1; 1656 event.set(io); 1657 } 1658 1659 test join { 1660 if (builtin.single_threaded) return error.SkipZigTest; 1661 1662 const io = testing.io; 1663 1664 var value: usize = 0; 1665 var event: Io.Event = .unset; 1666 1667 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ io, &value, &event }); 1668 thread.join(); 1669 1670 try std.testing.expectEqual(value, 1); 1671 } 1672 1673 test detach { 1674 if (builtin.single_threaded) return error.SkipZigTest; 1675 1676 const io = testing.io; 1677 1678 var value: usize = 0; 1679 var event: Io.Event = .unset; 1680 1681 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ io, &value, &event }); 1682 thread.detach(); 1683 1684 try event.wait(io); 1685 try std.testing.expectEqual(value, 1); 1686 } 1687 1688 test "Thread.getCpuCount" { 1689 if (native_os == .wasi) return error.SkipZigTest; 1690 1691 const cpu_count = try Thread.getCpuCount(); 1692 try std.testing.expect(cpu_count >= 1); 1693 } 1694 1695 fn testThreadIdFn(thread_id: *Thread.Id) void { 1696 thread_id.* = Thread.getCurrentId(); 1697 } 1698 1699 test "Thread.getCurrentId" { 1700 if (builtin.single_threaded) return error.SkipZigTest; 1701 1702 var thread_current_id: Thread.Id = undefined; 1703 const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id}); 1704 thread.join(); 1705 try std.testing.expect(Thread.getCurrentId() != thread_current_id); 1706 } 1707 1708 test "thread local storage" { 1709 if (builtin.single_threaded) return error.SkipZigTest; 1710 if (@sizeOf(usize) == 4) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25498 1711 1712 const thread1 = try Thread.spawn(.{}, testTls, .{}); 1713 const thread2 = try Thread.spawn(.{}, testTls, .{}); 1714 try testTls(); 1715 thread1.join(); 1716 thread2.join(); 1717 } 1718 1719 threadlocal var x: i32 = 1234; 1720 fn testTls() !void { 1721 if (x != 1234) return error.TlsBadStartValue; 1722 x += 1; 1723 if (x != 1235) return error.TlsBadEndValue; 1724 } 1725 1726 /// Configures the per-thread alternative signal stack requested by `std.options.signal_stack_size`. 1727 pub fn maybeAttachSignalStack() void { 1728 const size = std.options.signal_stack_size orelse return; 1729 switch (builtin.target.os.tag) { 1730 // TODO: Windows vectored exception handlers always run on the main stack, but we could use 1731 // some target-specific inline assembly to swap the stack pointer. 1732 .windows => return, 1733 .wasi => return, 1734 else => {}, 1735 } 1736 const global = struct { 1737 threadlocal var signal_stack: [size]u8 = undefined; 1738 }; 1739 std.posix.sigaltstack(&.{ 1740 .sp = &global.signal_stack, 1741 .flags = 0, 1742 .size = size, 1743 }, null) catch |err| switch (err) { 1744 error.SizeTooSmall => unreachable, // `std.options.signal_stack_size` must be sufficient for the target 1745 error.PermissionDenied => unreachable, // called `maybeAttachSignalStack` from a signal handler 1746 error.Unexpected => unreachable, 1747 }; 1748 }