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