zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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 }