1//! This struct represents a kernel thread.
2const Thread = @This();
3
4const builtin = @import("builtin");
5const target = builtin.target;
6const native_os = builtin.os.tag;
7
8const std = @import("std.zig");
9const Io = std.Io;
10const math = std.math;
11const assert = std.debug.assert;
12const posix = std.posix;
13const windows = std.os.windows;
14const testing = std.testing;
15
16pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc;
17
18const Impl = if (native_os == .windows)
19 WindowsThreadImpl
20else if (use_pthreads)
21 PosixThreadImpl
22else if (native_os == .linux)
23 LinuxThreadImpl
24else if (native_os == .wasi)
25 WasiThreadImpl
26else
27 UnsupportedImpl;
28
29impl: Impl,
30
31pub 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
45pub const SetNameError = error{
46 NameTooLong,
47 Unsupported,
48 Unexpected,
49 InvalidWtf8,
50} || posix.PrctlError || Io.File.Writer.Error || Io.File.OpenError || std.mem.PrintError;
51
52pub 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, @fromBackingInt(@intCast(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.mem.print(&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, @fromBackingInt(@intCast(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, @fromBackingInt(@intCast(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, @fromBackingInt(@intCast(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, @fromBackingInt(@intCast(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
152pub const GetNameError = error{
153 Unsupported,
154 Unexpected,
155} || posix.PrctlError || posix.ReadError || Io.File.OpenError || std.mem.PrintError;
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.
159pub 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, @fromBackingInt(@intCast(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.mem.print(&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, @fromBackingInt(@intCast(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, @fromBackingInt(@intCast(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, @fromBackingInt(@intCast(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, @fromBackingInt(@intCast(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.
262pub 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.
279pub fn getCurrentId() Id {
280 return Impl.getCurrentId();
281}
282
283pub 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.
293pub fn getCpuCount() CpuCountError!usize {
294 return try Impl.getCpuCount();
295}
296
297/// Configuration options for hints on how to spawn threads.
298pub 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
310pub 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.
344pub 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.
355pub const Handle = Impl.ThreadHandle;
356
357/// Returns the handle of this thread
358pub 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.
364pub 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.
370pub fn join(self: Thread) void {
371 return self.impl.join();
372}
373
374pub 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.
380pub 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
393const 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.
401fn 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 @fromBackingInt(@intCast(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.
464const 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
497const 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 @fromBackingInt(@intCast(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 switch (windows.ntdll.NtWaitForSingleObject(self.thread.thread_handle, .FALSE, null)) {
643 windows.NTSTATUS.WAIT_0 => {},
644 else => |status| windows.unexpectedStatus(status) catch unreachable,
645 }
646 windows.CloseHandle(self.thread.thread_handle);
647 assert(self.thread.completion.load(.seq_cst) == .completed);
648 self.thread.free();
649 }
650};
651
652const PosixThreadImpl = struct {
653 const c = std.c;
654
655 pub const ThreadHandle = c.pthread_t;
656
657 fn getCurrentId() Id {
658 switch (native_os) {
659 .linux => {
660 return LinuxThreadImpl.getCurrentId();
661 },
662 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
663 var thread_id: u64 = undefined;
664 // Pass thread=null to get the current thread ID.
665 assert(c.pthread_threadid_np(null, &thread_id) == 0);
666 return thread_id;
667 },
668 .dragonfly => {
669 return @as(u32, @bitCast(c.lwp_gettid()));
670 },
671 .netbsd => {
672 return @as(u32, @bitCast(c._lwp_self()));
673 },
674 .freebsd => {
675 return @as(u32, @bitCast(c.pthread_getthreadid_np()));
676 },
677 .openbsd => {
678 return @as(u32, @bitCast(c.getthrid()));
679 },
680 .haiku => {
681 return @as(u32, @bitCast(c.find_thread(null)));
682 },
683 .serenity => {
684 return @as(u32, @bitCast(c.pthread_self()));
685 },
686 else => {
687 return @intFromPtr(c.pthread_self());
688 },
689 }
690 }
691
692 fn getCpuCount() !usize {
693 switch (native_os) {
694 .linux => {
695 return LinuxThreadImpl.getCpuCount();
696 },
697 .emscripten => {
698 return @as(usize, @intCast(std.os.emscripten.emscripten_num_logical_cores()));
699 },
700 .openbsd => {
701 var count: c_int = undefined;
702 var count_size: usize = @sizeOf(c_int);
703 const mib = [_]c_int{ std.c.CTL.HW, std.c.HW.NCPUONLINE };
704 posix.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) {
705 error.NameTooLong, error.UnknownName => unreachable,
706 else => |e| return e,
707 };
708 return @as(usize, @intCast(count));
709 },
710 .illumos, .serenity => {
711 // The "proper" way to get the cpu count would be to query
712 // /dev/kstat via ioctls, and traverse a linked list for each
713 // cpu. (illumos)
714 const rc = c.sysconf(@backingInt(std.c._SC.NPROCESSORS_ONLN));
715 return switch (posix.errno(rc)) {
716 .SUCCESS => @as(usize, @intCast(rc)),
717 else => |err| posix.unexpectedErrno(err),
718 };
719 },
720 .haiku => {
721 var system_info: std.c.system_info = undefined;
722 return switch (std.c.get_system_info(&system_info)) {
723 0 => @as(usize, @intCast(system_info.cpu_count)),
724 else => error.Unexpected,
725 };
726 },
727 else => {
728 var count: c_int = undefined;
729 var count_len: usize = @sizeOf(c_int);
730 const name = comptime if (target.os.tag.isDarwin()) "hw.logicalcpu" else "hw.ncpu";
731 switch (posix.errno(posix.system.sysctlbyname(name, &count, &count_len, null, 0))) {
732 .SUCCESS => return @intCast(count),
733 .FAULT => unreachable,
734 .PERM => return error.PermissionDenied,
735 .NOMEM => return error.SystemResources,
736 .NOENT => unreachable,
737 else => |err| return posix.unexpectedErrno(err),
738 }
739 },
740 }
741 }
742
743 handle: ThreadHandle,
744
745 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
746 const Args = @TypeOf(args);
747 const allocator = std.heap.c_allocator;
748
749 const Instance = struct {
750 fn entryFn(raw_arg: ?*anyopaque) callconv(.c) ?*anyopaque {
751 const args_ptr: *Args = @ptrCast(@alignCast(raw_arg));
752 defer allocator.destroy(args_ptr);
753 return callFn(f, args_ptr.*);
754 }
755 };
756
757 const args_ptr = try allocator.create(Args);
758 args_ptr.* = args;
759 errdefer allocator.destroy(args_ptr);
760
761 var attr: c.pthread_attr_t = undefined;
762 if (c.pthread_attr_init(&attr) != .SUCCESS) return error.SystemResources;
763 defer assert(c.pthread_attr_destroy(&attr) == .SUCCESS);
764
765 // Use the same set of parameters used by the libc-less impl.
766 const stack_size = @max(config.stack_size, 16 * 1024);
767 assert(c.pthread_attr_setstacksize(&attr, stack_size) == .SUCCESS);
768 assert(c.pthread_attr_setguardsize(&attr, std.heap.pageSize()) == .SUCCESS);
769
770 var handle: c.pthread_t = undefined;
771 switch (c.pthread_create(
772 &handle,
773 &attr,
774 Instance.entryFn,
775 @ptrCast(args_ptr),
776 )) {
777 .SUCCESS => return Impl{ .handle = handle },
778 .AGAIN => return error.SystemResources,
779 .PERM => unreachable,
780 .INVAL => unreachable,
781 else => |err| return posix.unexpectedErrno(err),
782 }
783 }
784
785 fn getHandle(self: Impl) ThreadHandle {
786 return self.handle;
787 }
788
789 fn detach(self: Impl) void {
790 switch (c.pthread_detach(self.handle)) {
791 .SUCCESS => {},
792 .INVAL => unreachable, // thread handle is not joinable
793 .SRCH => unreachable, // thread handle is invalid
794 else => unreachable,
795 }
796 }
797
798 fn join(self: Impl) void {
799 switch (c.pthread_join(self.handle, null)) {
800 .SUCCESS => {},
801 .INVAL => unreachable, // thread handle is not joinable (or another thread is already joining in)
802 .SRCH => unreachable, // thread handle is invalid
803 .DEADLK => unreachable, // two threads tried to join each other
804 else => unreachable,
805 }
806 }
807};
808
809const WasiThreadImpl = struct {
810 thread: *WasiThread,
811
812 pub const ThreadHandle = i32;
813 threadlocal var tls_thread_id: Id = 0;
814
815 const WasiThread = struct {
816 /// Thread ID
817 tid: std.atomic.Value(i32) = std.atomic.Value(i32).init(0),
818 /// Contains all memory which was allocated to bootstrap this thread, including:
819 /// - Guard page
820 /// - Stack
821 /// - TLS segment
822 /// - `Instance`
823 /// All memory is freed upon call to `join`
824 memory: []u8,
825 /// The allocator used to allocate the thread's memory,
826 /// which is also used during `join` to ensure clean-up.
827 allocator: std.mem.Allocator,
828 /// The current state of the thread.
829 state: State = State.init(.running),
830 };
831
832 /// A meta-data structure used to bootstrap a thread
833 const Instance = struct {
834 thread: WasiThread,
835 /// Contains the offset to the new __tls_base.
836 /// The offset starting from the memory's base.
837 tls_offset: usize,
838 /// Contains the offset to the stack for the newly spawned thread.
839 /// The offset is calculated starting from the memory's base.
840 stack_offset: usize,
841 /// Contains the raw pointer value to the wrapper which holds all arguments
842 /// for the callback.
843 raw_ptr: usize,
844 /// Function pointer to a wrapping function which will call the user's
845 /// function upon thread spawn. The above mentioned pointer will be passed
846 /// to this function pointer as its argument.
847 call_back: *const fn (usize) void,
848 /// When a thread is in `detached` state, we must free all of its memory
849 /// upon thread completion. However, as this is done while still within
850 /// the thread, we must first jump back to the main thread's stack or else
851 /// we end up freeing the stack that we're currently using.
852 original_stack_pointer: [*]u8,
853 };
854
855 const State = std.atomic.Value(enum(u8) { running, completed, detached });
856
857 fn getCurrentId() Id {
858 return tls_thread_id;
859 }
860
861 fn getCpuCount() error{Unsupported}!noreturn {
862 return error.Unsupported;
863 }
864
865 fn getHandle(self: Impl) ThreadHandle {
866 return self.thread.tid.load(.seq_cst);
867 }
868
869 fn detach(self: Impl) void {
870 switch (self.thread.state.swap(.detached, .seq_cst)) {
871 .running => {},
872 .completed => self.join(),
873 .detached => unreachable,
874 }
875 }
876
877 fn join(self: Impl) void {
878 defer {
879 // Create a copy of the allocator so we do not free the reference to the
880 // original allocator while freeing the memory.
881 var allocator = self.thread.allocator;
882 allocator.free(self.thread.memory);
883 }
884
885 while (true) {
886 const tid = self.thread.tid.load(.seq_cst);
887 if (tid == 0) break;
888
889 const result = asm (
890 \\ local.get %[ptr]
891 \\ local.get %[expected]
892 \\ i64.const -1 # infinite
893 \\ memory.atomic.wait32 0
894 \\ local.set %[ret]
895 : [ret] "=r" (-> u32),
896 : [ptr] "r" (&self.thread.tid.raw),
897 [expected] "r" (tid),
898 );
899 switch (result) {
900 0 => continue, // ok
901 1 => continue, // expected =! loaded
902 2 => unreachable, // timeout (infinite)
903 else => unreachable,
904 }
905 }
906 }
907
908 fn spawn(config: std.Thread.SpawnConfig, comptime f: anytype, args: anytype) SpawnError!WasiThreadImpl {
909 if (config.allocator == null) {
910 @panic("an allocator is required to spawn a WASI thread");
911 }
912
913 // Wrapping struct required to hold the user-provided function arguments.
914 const Wrapper = struct {
915 args: @TypeOf(args),
916 fn entry(ptr: usize) void {
917 const w: *@This() = @ptrFromInt(ptr);
918 const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";
919 switch (@typeInfo(@typeInfo(@TypeOf(f)).@"fn".return_type.?)) {
920 .noreturn, .void => {
921 @call(.auto, f, w.args);
922 },
923 .int => |info| {
924 if (info.bits != 8) {
925 @compileError(bad_fn_ret);
926 }
927 _ = @call(.auto, f, w.args); // WASI threads don't support exit status, ignore value
928 },
929 .error_union => |info| {
930 if (info.payload != void) {
931 @compileError(bad_fn_ret);
932 }
933 @call(.auto, f, w.args) catch |err| {
934 std.debug.print("error: {s}\n", .{@errorName(err)});
935 if (@errorReturnTrace()) |trace| {
936 std.debug.dumpErrorReturnTrace(trace);
937 }
938 };
939 },
940 else => {
941 @compileError(bad_fn_ret);
942 },
943 }
944 }
945 };
946
947 var stack_offset: usize = undefined;
948 var tls_offset: usize = undefined;
949 var wrapper_offset: usize = undefined;
950 var instance_offset: usize = undefined;
951
952 // Calculate the bytes we have to allocate to store all thread information, including:
953 // - The actual stack for the thread
954 // - The TLS segment
955 // - `Instance` - containing information about how to call the user's function.
956 const map_bytes = blk: {
957 // start with atleast a single page, which is used as a guard to prevent
958 // other threads clobbering our new thread.
959 // Unfortunately, WebAssembly has no notion of read-only segments, so this
960 // is only a best effort.
961 var bytes: usize = std.wasm.page_size;
962
963 bytes = std.mem.alignForward(usize, bytes, 16); // align stack to 16 bytes
964 stack_offset = bytes;
965 bytes += @max(std.wasm.page_size, config.stack_size);
966
967 bytes = std.mem.alignForward(usize, bytes, __tls_align());
968 tls_offset = bytes;
969 bytes += __tls_size();
970
971 bytes = std.mem.alignForward(usize, bytes, @alignOf(Wrapper));
972 wrapper_offset = bytes;
973 bytes += @sizeOf(Wrapper);
974
975 bytes = std.mem.alignForward(usize, bytes, @alignOf(Instance));
976 instance_offset = bytes;
977 bytes += @sizeOf(Instance);
978
979 bytes = std.mem.alignForward(usize, bytes, std.wasm.page_size);
980 break :blk bytes;
981 };
982
983 // Allocate the amount of memory required for all meta data.
984 const allocated_memory = try config.allocator.?.alloc(u8, map_bytes);
985
986 const wrapper: *Wrapper = @ptrCast(@alignCast(&allocated_memory[wrapper_offset]));
987 wrapper.* = .{ .args = args };
988
989 const instance: *Instance = @ptrCast(@alignCast(&allocated_memory[instance_offset]));
990 instance.* = .{
991 .thread = .{ .memory = allocated_memory, .allocator = config.allocator.? },
992 .tls_offset = tls_offset,
993 .stack_offset = stack_offset,
994 .raw_ptr = @intFromPtr(wrapper),
995 .call_back = &Wrapper.entry,
996 .original_stack_pointer = __get_stack_pointer(),
997 };
998
999 const tid = spawnWasiThread(instance);
1000 // The specification says any value lower than 0 indicates an error.
1001 // The values of such error are unspecified. WASI-Libc treats it as EAGAIN.
1002 if (tid < 0) {
1003 return error.SystemResources;
1004 }
1005 instance.thread.tid.store(tid, .seq_cst);
1006
1007 return .{ .thread = &instance.thread };
1008 }
1009
1010 comptime {
1011 if (!builtin.single_threaded) {
1012 @export(&wasi_thread_start, .{ .name = "wasi_thread_start" });
1013 }
1014 }
1015
1016 /// Called by the host environment after thread creation.
1017 fn wasi_thread_start(tid: i32, arg: *Instance) callconv(.c) void {
1018 comptime assert(!builtin.single_threaded);
1019 __set_stack_pointer(arg.thread.memory.ptr + arg.stack_offset);
1020 __wasm_init_tls(arg.thread.memory.ptr + arg.tls_offset);
1021 @atomicStore(u32, &WasiThreadImpl.tls_thread_id, @intCast(tid), .seq_cst);
1022
1023 // Finished bootstrapping, call user's procedure.
1024 arg.call_back(arg.raw_ptr);
1025
1026 switch (arg.thread.state.swap(.completed, .seq_cst)) {
1027 .running => {
1028 // reset the Thread ID
1029 asm volatile (
1030 \\ local.get %[ptr]
1031 \\ i32.const 0
1032 \\ i32.atomic.store 0
1033 :
1034 : [ptr] "r" (&arg.thread.tid.raw),
1035 );
1036
1037 // Wake the main thread listening to this thread
1038 asm volatile (
1039 \\ local.get %[ptr]
1040 \\ i32.const 1 # waiters
1041 \\ memory.atomic.notify 0
1042 \\ drop # no need to know the waiters
1043 :
1044 : [ptr] "r" (&arg.thread.tid.raw),
1045 );
1046 },
1047 .completed => unreachable,
1048 .detached => {
1049 // restore the original stack pointer so we can free the memory
1050 // without having to worry about freeing the stack
1051 __set_stack_pointer(arg.original_stack_pointer);
1052 // Ensure a copy so we don't free the allocator reference itself
1053 var allocator = arg.thread.allocator;
1054 allocator.free(arg.thread.memory);
1055 },
1056 }
1057 }
1058
1059 /// Asks the host to create a new thread for us.
1060 /// Newly created thread will call `wasi_tread_start` with the thread ID as well
1061 /// as the input `arg` that was provided to `spawnWasiThread`
1062 const spawnWasiThread = @"thread-spawn";
1063 extern "wasi" fn @"thread-spawn"(arg: *Instance) i32;
1064
1065 /// Initializes the TLS data segment starting at `memory`.
1066 /// This is a synthetic function, generated by the linker.
1067 extern fn __wasm_init_tls(memory: [*]u8) void;
1068
1069 /// Returns a pointer to the base of the TLS data segment for the current thread
1070 inline fn __tls_base() [*]u8 {
1071 return asm (
1072 \\ .globaltype __tls_base, i32
1073 \\ global.get __tls_base
1074 \\ local.set %[ret]
1075 : [ret] "=r" (-> [*]u8),
1076 );
1077 }
1078
1079 /// Returns the size of the TLS segment
1080 inline fn __tls_size() u32 {
1081 return asm volatile (
1082 \\ .globaltype __tls_size, i32, immutable
1083 \\ global.get __tls_size
1084 \\ local.set %[ret]
1085 : [ret] "=r" (-> u32),
1086 );
1087 }
1088
1089 /// Returns the alignment of the TLS segment
1090 inline fn __tls_align() u32 {
1091 return asm (
1092 \\ .globaltype __tls_align, i32, immutable
1093 \\ global.get __tls_align
1094 \\ local.set %[ret]
1095 : [ret] "=r" (-> u32),
1096 );
1097 }
1098
1099 /// Allows for setting the stack pointer in the WebAssembly module.
1100 inline fn __set_stack_pointer(addr: [*]u8) void {
1101 asm volatile (
1102 \\ local.get %[ptr]
1103 \\ global.set __stack_pointer
1104 :
1105 : [ptr] "r" (addr),
1106 );
1107 }
1108
1109 /// Returns the current value of the stack pointer
1110 inline fn __get_stack_pointer() [*]u8 {
1111 return asm (
1112 \\ global.get __stack_pointer
1113 \\ local.set %[stack_ptr]
1114 : [stack_ptr] "=r" (-> [*]u8),
1115 );
1116 }
1117};
1118
1119const LinuxThreadImpl = struct {
1120 const linux = std.os.linux;
1121
1122 pub const ThreadHandle = i32;
1123
1124 threadlocal var tls_thread_id: ?Id = null;
1125
1126 fn getCurrentId() Id {
1127 return tls_thread_id orelse {
1128 const tid: u32 = @bitCast(linux.gettid());
1129 tls_thread_id = tid;
1130 return tid;
1131 };
1132 }
1133
1134 fn getCpuCount() !usize {
1135 const cpu_set = try posix.sched_getaffinity(0);
1136 return posix.CPU_COUNT(cpu_set);
1137 }
1138
1139 thread: *ThreadCompletion,
1140
1141 const ThreadCompletion = struct {
1142 completion: Completion = Completion.init(.running),
1143 child_tid: std.atomic.Value(i32) = std.atomic.Value(i32).init(1),
1144 parent_tid: i32 = undefined,
1145 mapped: []align(std.heap.page_size_min) u8,
1146
1147 // On SPARC, the kernel needs to be able to restore the current register window from the
1148 // stack when returning from a syscall. That presents a bit of a problem in `freeAndExit`
1149 // since we're deallocating the stack! The good news is that, since we do not care about
1150 // the contents of the incoming and local registers at that point, we can just tell the
1151 // kernel that our stack is this undefined global buffer.
1152 var sparc_exit_stack: [192]u8 align(16) = undefined;
1153
1154 /// Calls `munmap(mapped.ptr, mapped.len)` then `exit(1)` without touching the stack (which lives in `mapped.ptr`).
1155 /// Ported over from musl libc's pthread detached implementation:
1156 /// https://github.com/ifduyue/musl/search?q=__unmapself
1157 fn freeAndExit(self: *ThreadCompletion) noreturn {
1158 // If we do not reset the child_tidptr to null here, the kernel would later write the
1159 // value zero to that address, which is inside the block we're unmapping below, after
1160 // our thread exits. This can sometimes corrupt memory in other mmap blocks from
1161 // unrelated concurrent threads.
1162 _ = linux.set_tid_address(null);
1163 // If a signal were delivered between SYS_munmap and SYS_exit, any installed signal
1164 // handler would immediately segfault due to the stack being unmapped. To avoid this,
1165 // we need to mask all signals before entering the inline asm.
1166 posix.sigprocmask(std.posix.SIG.BLOCK, &std.os.linux.sigfillset(), null);
1167 switch (target.cpu.arch) {
1168 .x86 => asm volatile (
1169 \\ movl $91, %%eax # SYS_munmap
1170 \\ int $128
1171 \\ movl $1, %%eax # SYS_exit
1172 \\ movl $0, %%ebx
1173 \\ int $128
1174 :
1175 : [ptr] "{ebx}" (@intFromPtr(self.mapped.ptr)),
1176 [len] "{ecx}" (self.mapped.len),
1177 ),
1178 .x86_64 => asm volatile (switch (target.abi) {
1179 .gnux32, .muslx32, .x32 =>
1180 \\ movl $0x4000000b, %%eax # SYS_munmap
1181 \\ syscall
1182 \\ movl $0x4000003c, %%eax # SYS_exit
1183 \\ xor %%rdi, %%rdi
1184 \\ syscall
1185 ,
1186 else =>
1187 \\ movl $11, %%eax # SYS_munmap
1188 \\ syscall
1189 \\ movl $60, %%eax # SYS_exit
1190 \\ xor %%rdi, %%rdi
1191 \\ syscall
1192 ,
1193 }
1194 :
1195 : [ptr] "{rdi}" (@intFromPtr(self.mapped.ptr)),
1196 [len] "{rsi}" (self.mapped.len),
1197 ),
1198 .arm, .armeb, .thumb, .thumbeb => asm volatile (
1199 \\ mov r7, #91 // SYS_munmap
1200 \\ svc 0
1201 \\ mov r7, #1 // SYS_exit
1202 \\ mov r0, #0
1203 \\ svc 0
1204 :
1205 : [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)),
1206 [len] "{r1}" (self.mapped.len),
1207 ),
1208 .aarch64, .aarch64_be => asm volatile (
1209 \\ mov x8, #215 // SYS_munmap
1210 \\ svc 0
1211 \\ mov x8, #93 // SYS_exit
1212 \\ mov x0, #0
1213 \\ svc 0
1214 :
1215 : [ptr] "{x0}" (@intFromPtr(self.mapped.ptr)),
1216 [len] "{x1}" (self.mapped.len),
1217 ),
1218 .alpha => asm volatile (
1219 \\ ldi $0, 73 # SYS_munmap
1220 \\ callsys
1221 \\ ldi $0, 1 # SYS_exit
1222 \\ ldi $16, 0
1223 \\ callsys
1224 :
1225 : [ptr] "{$16}" (@intFromPtr(self.mapped.ptr)),
1226 [len] "{$17}" (self.mapped.len),
1227 ),
1228 .arc, .arceb => asm volatile (
1229 \\ mov r8, 215 # SYS_munmap
1230 \\ trap_s 0
1231 \\ mov r8, 93 # SYS_exit
1232 \\ mov r0, 0
1233 \\ trap_s 0
1234 :
1235 : [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)),
1236 [len] "{r1}" (self.mapped.len),
1237 ),
1238 .hexagon => asm volatile (
1239 \\ r6 = #215 // SYS_munmap
1240 \\ trap0(#1)
1241 \\ r6 = #93 // SYS_exit
1242 \\ r0 = #0
1243 \\ trap0(#1)
1244 :
1245 : [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)),
1246 [len] "{r1}" (self.mapped.len),
1247 ),
1248 .hppa => asm volatile (
1249 \\ ble 0x100(%%sr2, %%r0)
1250 \\ ldi 91, %%r20 /* SYS_munmap */
1251 \\ ldi 0, %%r26
1252 \\ ble 0x100(%%sr2, %%r0)
1253 \\ ldi 1, %%r20 /* SYS_exit */
1254 :
1255 : [ptr] "{r26}" (@intFromPtr(self.mapped.ptr)),
1256 [len] "{r25}" (self.mapped.len),
1257 ),
1258 .m68k => asm volatile (
1259 \\ move.l #91, %%d0 // SYS_munmap
1260 \\ trap #0
1261 \\ move.l #1, %%d0 // SYS_exit
1262 \\ move.l #0, %%d1
1263 \\ trap #0
1264 :
1265 : [ptr] "{d1}" (@intFromPtr(self.mapped.ptr)),
1266 [len] "{d2}" (self.mapped.len),
1267 ),
1268 .microblaze, .microblazeel => asm volatile (
1269 \\ ori r12, r0, 91 # SYS_munmap
1270 \\ brki r14, 0x8
1271 \\ ori r12, r0, 1 # SYS_exit
1272 \\ ori r5, r0, 0
1273 \\ brki r14, 0x8
1274 :
1275 : [ptr] "{r5}" (@intFromPtr(self.mapped.ptr)),
1276 [len] "{r6}" (self.mapped.len),
1277 ),
1278 .mips, .mipsel => asm volatile (
1279 \\ li $v0, 4091 # SYS_munmap
1280 \\ syscall
1281 \\ li $v0, 4001 # SYS_exit
1282 \\ li $a0, 0
1283 \\ syscall
1284 :
1285 : [ptr] "{$4}" (@intFromPtr(self.mapped.ptr)),
1286 [len] "{$5}" (self.mapped.len),
1287 ),
1288 .mips64, .mips64el => asm volatile (switch (target.abi) {
1289 .gnuabin32, .muslabin32, .abin32 =>
1290 \\ li $v0, 6011 # SYS_munmap
1291 \\ syscall
1292 \\ li $v0, 6058 # SYS_exit
1293 \\ li $a0, 0
1294 \\ syscall
1295 ,
1296 else =>
1297 \\ li $v0, 5011 # SYS_munmap
1298 \\ syscall
1299 \\ li $v0, 5058 # SYS_exit
1300 \\ li $a0, 0
1301 \\ syscall
1302 ,
1303 }
1304 :
1305 : [ptr] "{$4}" (@intFromPtr(self.mapped.ptr)),
1306 [len] "{$5}" (self.mapped.len),
1307 ),
1308 .or1k => asm volatile (
1309 \\ l.ori r11, r0, 215 # SYS_munmap
1310 \\ l.sys 1
1311 \\ l.ori r11, r0, 93 # SYS_exit
1312 \\ l.ori r3, r0, 0
1313 \\ l.sys 1
1314 :
1315 : [ptr] "{r3}" (@intFromPtr(self.mapped.ptr)),
1316 [len] "{r4}" (self.mapped.len),
1317 ),
1318 .powerpc, .powerpcle, .powerpc64, .powerpc64le => asm volatile (
1319 \\ li 0, 91 # SYS_munmap
1320 \\ sc
1321 \\ li 0, 1 # SYS_exit
1322 \\ li 3, 0
1323 \\ sc
1324 :
1325 : [ptr] "{r3}" (@intFromPtr(self.mapped.ptr)),
1326 [len] "{r4}" (self.mapped.len),
1327 ),
1328 .riscv32, .riscv64 => asm volatile (
1329 \\ li a7, 215 # SYS_munmap
1330 \\ ecall
1331 \\ li a7, 93 # SYS_exit
1332 \\ mv a0, zero
1333 \\ ecall
1334 :
1335 : [ptr] "{a0}" (@intFromPtr(self.mapped.ptr)),
1336 [len] "{a1}" (self.mapped.len),
1337 ),
1338 .s390x => asm volatile (
1339 \\ svc 91 # SYS_munmap
1340 \\ lghi %%r2, 0
1341 \\ svc 1 # SYS_exit
1342 :
1343 : [ptr] "{r2}" (@intFromPtr(self.mapped.ptr)),
1344 [len] "{r3}" (self.mapped.len),
1345 ),
1346 .sh, .sheb => asm volatile (
1347 \\ mov #91, r3 ! SYS_munmap
1348 \\ trapa #31
1349 \\ or r0, r0
1350 \\ or r0, r0
1351 \\ or r0, r0
1352 \\ or r0, r0
1353 \\ or r0, r0
1354 \\ mov #1, r3 ! SYS_exit
1355 \\ mov #0, r4
1356 \\ trapa #31
1357 \\ or r0, r0
1358 \\ or r0, r0
1359 \\ or r0, r0
1360 \\ or r0, r0
1361 \\ or r0, r0
1362 :
1363 : [ptr] "{r4}" (@intFromPtr(self.mapped.ptr)),
1364 [len] "{r5}" (self.mapped.len),
1365 ),
1366 .sparc => asm volatile (
1367 \\ // See sparc64 comments below.
1368 \\ t 0x3 // ST_FLUSH_WINDOWS
1369 \\ mov %%g3, %%sp
1370 \\ mov %%g1, %%o0
1371 \\ mov %%g2, %%o1
1372 \\ mov 73, %%g1 // SYS_munmap
1373 \\ t 0x10
1374 \\ mov 1, %%g1 // SYS_exit
1375 \\ mov 0, %%o0
1376 \\ t 0x10
1377 :
1378 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),
1379 [len] "{g2}" (self.mapped.len),
1380 [stack] "{g3}" (&sparc_exit_stack),
1381 ),
1382 .sparc64 => asm volatile (
1383 \\ // Ensure that the kernel only has to flush the current register window.
1384 \\ flushw
1385 \\ // Set up a fake stack for the syscall to restore l/i registers from. Local
1386 \\ // and incoming registers must be treated as effectively garbage past this
1387 \\ // instruction!
1388 \\ sub %%g3, 2047, %%sp
1389 \\ mov %%g1, %%o0
1390 \\ mov %%g2, %%o1
1391 \\ mov 73, %%g1 // SYS_munmap
1392 \\ t 0x6d
1393 \\ mov 1, %%g1 // SYS_exit
1394 \\ mov 0, %%o0
1395 \\ t 0x6d
1396 :
1397 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),
1398 [len] "{g2}" (self.mapped.len),
1399 [stack] "{g3}" (&sparc_exit_stack),
1400 ),
1401 .loongarch32, .loongarch64 => asm volatile (
1402 \\ ori $a7, $zero, 215 # SYS_munmap
1403 \\ syscall 0 # call munmap
1404 \\ ori $a0, $zero, 0
1405 \\ ori $a7, $zero, 93 # SYS_exit
1406 \\ syscall 0 # call exit
1407 :
1408 : [ptr] "{r4}" (@intFromPtr(self.mapped.ptr)),
1409 [len] "{r5}" (self.mapped.len),
1410 ),
1411 .csky => asm volatile (
1412 \\ movi r7, 215 # SYS_munmap
1413 \\ trap 0
1414 \\ movi r7, 93 # SYS_exit
1415 \\ movi r0, 0
1416 \\ trap 0
1417 :
1418 : [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)),
1419 [len] "{r1}" (self.mapped.len),
1420 ),
1421 .xtensa, .xtensaeb => asm volatile (
1422 \\ movi a2, 81 // SYS_munmap
1423 \\ syscall
1424 \\ movi a6, 0
1425 \\ movi a2, 118 // SYS_exit
1426 \\ syscall
1427 :
1428 : [ptr] "{a6}" (@intFromPtr(self.mapped.ptr)),
1429 [len] "{a3}" (self.mapped.len),
1430 ),
1431 else => |cpu_arch| @compileError("Unsupported linux arch: " ++ @tagName(cpu_arch)),
1432 }
1433 unreachable;
1434 }
1435 };
1436
1437 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
1438 const page_size = std.heap.pageSize();
1439 const Args = @TypeOf(args);
1440 const Instance = struct {
1441 fn_args: Args,
1442 thread: ThreadCompletion,
1443
1444 fn entryFn(raw_arg: usize) callconv(.c) u8 {
1445 const self = @as(*@This(), @ptrFromInt(raw_arg));
1446 defer switch (self.thread.completion.swap(.completed, .seq_cst)) {
1447 .running => {},
1448 .completed => unreachable,
1449 .detached => self.thread.freeAndExit(),
1450 };
1451 return callFn(f, self.fn_args);
1452 }
1453 };
1454
1455 var guard_offset: usize = undefined;
1456 var stack_offset: usize = undefined;
1457 var tls_offset: usize = undefined;
1458 var instance_offset: usize = undefined;
1459
1460 const map_bytes = blk: {
1461 var bytes: usize = page_size;
1462 guard_offset = bytes;
1463
1464 bytes += @max(page_size, config.stack_size);
1465 bytes = std.mem.alignForward(usize, bytes, page_size);
1466 stack_offset = bytes;
1467
1468 bytes = std.mem.alignForward(usize, bytes, linux.tls.area_desc.alignment);
1469 tls_offset = bytes;
1470 bytes += linux.tls.area_desc.size;
1471
1472 bytes = std.mem.alignForward(usize, bytes, @alignOf(Instance));
1473 instance_offset = bytes;
1474 bytes += @sizeOf(Instance);
1475
1476 bytes = std.mem.alignForward(usize, bytes, page_size);
1477 break :blk bytes;
1478 };
1479
1480 // map all memory needed without read/write permissions
1481 // to avoid committing the whole region right away
1482 // anonymous mapping ensures file descriptor limits are not exceeded
1483 const mapped = posix.mmap(
1484 null,
1485 map_bytes,
1486 .{},
1487 .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
1488 -1,
1489 0,
1490 ) catch |err| switch (err) {
1491 error.MemoryMappingNotSupported => unreachable,
1492 error.AccessDenied => unreachable,
1493 error.PermissionDenied => unreachable,
1494 error.ProcessFdQuotaExceeded => unreachable,
1495 error.SystemFdQuotaExceeded => unreachable,
1496 error.MappingAlreadyExists => unreachable,
1497 else => |e| return e,
1498 };
1499 assert(mapped.len >= map_bytes);
1500 errdefer posix.munmap(mapped);
1501
1502 // Map everything but the guard page as read/write.
1503 const guarded: []align(std.heap.page_size_min) u8 = @alignCast(mapped[guard_offset..]);
1504 const protection: posix.PROT = .{ .READ = true, .WRITE = true };
1505 switch (posix.errno(posix.system.mprotect(guarded.ptr, guarded.len, protection))) {
1506 .SUCCESS => {},
1507 .NOMEM => return error.OutOfMemory,
1508 else => |err| return posix.unexpectedErrno(err),
1509 }
1510
1511 // Prepare the TLS segment and prepare a user_desc struct when needed on x86
1512 var tls_ptr = linux.tls.prepareArea(mapped[tls_offset..][0..linux.tls.area_desc.size]);
1513 var user_desc: if (target.cpu.arch == .x86) linux.user_desc else void = undefined;
1514 if (target.cpu.arch == .x86) {
1515 defer tls_ptr = @intFromPtr(&user_desc);
1516 user_desc = .{
1517 .entry_number = linux.tls.area_desc.gdt_entry_number,
1518 .base_addr = tls_ptr,
1519 .limit = 0xfffff,
1520 .flags = .{
1521 .seg_32bit = 1,
1522 .contents = 0, // Data
1523 .read_exec_only = 0,
1524 .limit_in_pages = 1,
1525 .seg_not_present = 0,
1526 .useable = 1,
1527 },
1528 };
1529 }
1530
1531 const instance: *Instance = @ptrCast(@alignCast(&mapped[instance_offset]));
1532 instance.* = .{
1533 .fn_args = args,
1534 .thread = .{ .mapped = mapped },
1535 };
1536
1537 const flags: u32 = linux.CLONE.THREAD | linux.CLONE.DETACHED |
1538 linux.CLONE.VM | linux.CLONE.FS | linux.CLONE.FILES |
1539 linux.CLONE.PARENT_SETTID | linux.CLONE.CHILD_CLEARTID |
1540 linux.CLONE.SIGHAND | linux.CLONE.SYSVSEM | linux.CLONE.SETTLS;
1541
1542 switch (linux.errno(linux.clone(
1543 Instance.entryFn,
1544 @intFromPtr(&mapped[stack_offset]),
1545 flags,
1546 @intFromPtr(instance),
1547 &instance.thread.parent_tid,
1548 tls_ptr,
1549 &instance.thread.child_tid.raw,
1550 ))) {
1551 .SUCCESS => return Impl{ .thread = &instance.thread },
1552 .AGAIN => return error.ThreadQuotaExceeded,
1553 .INVAL => unreachable,
1554 .NOMEM => return error.SystemResources,
1555 .NOSPC => unreachable,
1556 .PERM => unreachable,
1557 .USERS => unreachable,
1558 else => |err| return posix.unexpectedErrno(err),
1559 }
1560 }
1561
1562 fn getHandle(self: Impl) ThreadHandle {
1563 return self.thread.parent_tid;
1564 }
1565
1566 fn detach(self: Impl) void {
1567 switch (self.thread.completion.swap(.detached, .seq_cst)) {
1568 .running => {},
1569 .completed => self.join(),
1570 .detached => unreachable,
1571 }
1572 }
1573
1574 fn join(self: Impl) void {
1575 defer posix.munmap(self.thread.mapped);
1576
1577 while (true) {
1578 const tid = self.thread.child_tid.load(.seq_cst);
1579 if (tid == 0) break;
1580
1581 switch (linux.errno(linux.futex_4arg(
1582 &self.thread.child_tid.raw,
1583 .{ .cmd = .WAIT, .private = false },
1584 @bitCast(tid),
1585 null,
1586 ))) {
1587 .SUCCESS => continue,
1588 .INTR => continue,
1589 .AGAIN => continue,
1590 else => unreachable,
1591 }
1592 }
1593 }
1594};
1595
1596fn testThreadName(io: Io, thread: *Thread) !void {
1597 const testCases: []const []const u8 = &.{
1598 "mythread",
1599 &@as([max_name_len]u8, @splat('b')),
1600 };
1601
1602 inline for (testCases) |tc| {
1603 try thread.setName(io, tc);
1604
1605 var name_buffer: [max_name_len:0]u8 = undefined;
1606
1607 const name = try thread.getName(&name_buffer);
1608 if (name) |value| {
1609 try std.testing.expectEqual(tc.len, value.len);
1610 try std.testing.expectEqualStrings(tc, value);
1611 }
1612 }
1613}
1614
1615test "setName, getName" {
1616 if (builtin.single_threaded) return error.SkipZigTest;
1617
1618 const io = testing.io;
1619
1620 const Context = struct {
1621 start_wait_event: Io.Event = .unset,
1622 test_done_event: Io.Event = .unset,
1623 thread_done_event: Io.Event = .unset,
1624
1625 done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
1626 thread: Thread = undefined,
1627
1628 pub fn run(ctx: *@This()) !void {
1629 // Wait for the main thread to have set the thread field in the context.
1630 try ctx.start_wait_event.wait(io);
1631
1632 switch (native_os) {
1633 .windows => testThreadName(io, &ctx.thread) catch |err| switch (err) {
1634 error.Unsupported => return error.SkipZigTest,
1635 else => return err,
1636 },
1637 else => try testThreadName(io, &ctx.thread),
1638 }
1639
1640 // Signal our test is done
1641 ctx.test_done_event.set(io);
1642
1643 // wait for the thread to property exit
1644 try ctx.thread_done_event.wait(io);
1645 }
1646 };
1647
1648 var context = Context{};
1649 var thread = try spawn(.{}, Context.run, .{&context});
1650
1651 context.thread = thread;
1652 context.start_wait_event.set(io);
1653 try context.test_done_event.wait(io);
1654
1655 switch (native_os) {
1656 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
1657 const res = thread.setName(io, "foobar");
1658 try std.testing.expectError(error.Unsupported, res);
1659 },
1660 .windows => testThreadName(io, &thread) catch |err| switch (err) {
1661 error.Unsupported => return error.SkipZigTest,
1662 else => return err,
1663 },
1664 else => try testThreadName(io, &thread),
1665 }
1666
1667 context.thread_done_event.set(io);
1668 thread.join();
1669}
1670
1671fn testIncrementNotify(io: Io, value: *usize, event: *Io.Event) void {
1672 value.* += 1;
1673 event.set(io);
1674}
1675
1676test join {
1677 if (builtin.single_threaded) return error.SkipZigTest;
1678
1679 const io = testing.io;
1680
1681 var value: usize = 0;
1682 var event: Io.Event = .unset;
1683
1684 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ io, &value, &event });
1685 thread.join();
1686
1687 try std.testing.expectEqual(value, 1);
1688}
1689
1690test detach {
1691 if (builtin.single_threaded) return error.SkipZigTest;
1692
1693 const io = testing.io;
1694
1695 var value: usize = 0;
1696 var event: Io.Event = .unset;
1697
1698 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ io, &value, &event });
1699 thread.detach();
1700
1701 try event.wait(io);
1702 try std.testing.expectEqual(value, 1);
1703}
1704
1705test "Thread.getCpuCount" {
1706 if (native_os == .wasi) return error.SkipZigTest;
1707
1708 const cpu_count = try Thread.getCpuCount();
1709 try std.testing.expect(cpu_count >= 1);
1710}
1711
1712fn testThreadIdFn(thread_id: *Thread.Id) void {
1713 thread_id.* = Thread.getCurrentId();
1714}
1715
1716test "Thread.getCurrentId" {
1717 if (builtin.single_threaded) return error.SkipZigTest;
1718
1719 var thread_current_id: Thread.Id = undefined;
1720 const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id});
1721 thread.join();
1722 try std.testing.expect(Thread.getCurrentId() != thread_current_id);
1723}
1724
1725test "thread local storage" {
1726 if (builtin.single_threaded) return error.SkipZigTest;
1727 if (@sizeOf(usize) == 4) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25498
1728
1729 const thread1 = try Thread.spawn(.{}, testTls, .{});
1730 const thread2 = try Thread.spawn(.{}, testTls, .{});
1731 try testTls();
1732 thread1.join();
1733 thread2.join();
1734}
1735
1736threadlocal var x: i32 = 1234;
1737fn testTls() !void {
1738 if (x != 1234) return error.TlsBadStartValue;
1739 x += 1;
1740 if (x != 1235) return error.TlsBadEndValue;
1741}
1742
1743/// Configures the per-thread alternative signal stack requested by `std.options.signal_stack_size`.
1744pub fn maybeAttachSignalStack() void {
1745 const size = std.options.signal_stack_size orelse return;
1746 switch (builtin.target.os.tag) {
1747 // TODO: Windows vectored exception handlers always run on the main stack, but we could use
1748 // some target-specific inline assembly to swap the stack pointer.
1749 .windows => return,
1750 .wasi => return,
1751 else => {},
1752 }
1753 const global = struct {
1754 threadlocal var signal_stack: [size]u8 = undefined;
1755 };
1756 std.posix.sigaltstack(&.{
1757 .sp = &global.signal_stack,
1758 .flags = 0,
1759 .size = size,
1760 }, null) catch |err| switch (err) {
1761 error.SizeTooSmall => unreachable, // `std.options.signal_stack_size` must be sufficient for the target
1762 error.PermissionDenied => unreachable, // called `maybeAttachSignalStack` from a signal handler
1763 error.Unexpected => unreachable,
1764 };
1765}