2021-01-14 20:41:37 -07:00
|
|
|
|
//! This struct represents a kernel thread, and acts as a namespace for concurrency
|
|
|
|
|
|
//! primitives that operate on kernel threads. For concurrency primitives that support
|
|
|
|
|
|
//! both evented I/O and async I/O, see the respective names in the top level std namespace.
|
|
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
const std = @import("std.zig");
|
2021-08-23 17:06:56 -07:00
|
|
|
|
const builtin = @import("builtin");
|
2022-01-15 17:40:45 -06:00
|
|
|
|
const math = std.math;
|
2021-06-20 15:39:23 -05:00
|
|
|
|
const assert = std.debug.assert;
|
2021-08-23 17:06:56 -07:00
|
|
|
|
const target = builtin.target;
|
2024-03-18 22:39:59 -07:00
|
|
|
|
const native_os = builtin.os.tag;
|
|
|
|
|
|
const posix = std.posix;
|
|
|
|
|
|
const windows = std.os.windows;
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2021-06-12 08:51:37 -05:00
|
|
|
|
pub const Futex = @import("Thread/Futex.zig");
|
2021-01-14 20:41:37 -07:00
|
|
|
|
pub const ResetEvent = @import("Thread/ResetEvent.zig");
|
|
|
|
|
|
pub const Mutex = @import("Thread/Mutex.zig");
|
|
|
|
|
|
pub const Semaphore = @import("Thread/Semaphore.zig");
|
|
|
|
|
|
pub const Condition = @import("Thread/Condition.zig");
|
2022-02-08 23:35:48 -05:00
|
|
|
|
pub const RwLock = @import("Thread/RwLock.zig");
|
2023-02-13 13:39:06 -07:00
|
|
|
|
pub const Pool = @import("Thread/Pool.zig");
|
|
|
|
|
|
pub const WaitGroup = @import("Thread/WaitGroup.zig");
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2024-03-18 22:39:59 -07:00
|
|
|
|
pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc;
|
2021-06-19 17:08:56 -05:00
|
|
|
|
|
2024-09-24 16:15:06 -07:00
|
|
|
|
/// Spurious wakeups are possible and no precision of timing is guaranteed.
|
|
|
|
|
|
pub fn sleep(nanoseconds: u64) void {
|
|
|
|
|
|
if (builtin.os.tag == .windows) {
|
|
|
|
|
|
const big_ms_from_ns = nanoseconds / std.time.ns_per_ms;
|
|
|
|
|
|
const ms = math.cast(windows.DWORD, big_ms_from_ns) orelse math.maxInt(windows.DWORD);
|
|
|
|
|
|
windows.kernel32.Sleep(ms);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (builtin.os.tag == .wasi) {
|
|
|
|
|
|
const w = std.os.wasi;
|
|
|
|
|
|
const userdata: w.userdata_t = 0x0123_45678;
|
|
|
|
|
|
const clock: w.subscription_clock_t = .{
|
|
|
|
|
|
.id = .MONOTONIC,
|
|
|
|
|
|
.timeout = nanoseconds,
|
|
|
|
|
|
.precision = 0,
|
|
|
|
|
|
.flags = 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
const in: w.subscription_t = .{
|
|
|
|
|
|
.userdata = userdata,
|
|
|
|
|
|
.u = .{
|
|
|
|
|
|
.tag = .CLOCK,
|
|
|
|
|
|
.u = .{ .clock = clock },
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var event: w.event_t = undefined;
|
|
|
|
|
|
var nevents: usize = undefined;
|
|
|
|
|
|
_ = w.poll_oneoff(&in, &event, 1, &nevents);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (builtin.os.tag == .uefi) {
|
|
|
|
|
|
const boot_services = std.os.uefi.system_table.boot_services.?;
|
|
|
|
|
|
const us_from_ns = nanoseconds / std.time.ns_per_us;
|
|
|
|
|
|
const us = math.cast(usize, us_from_ns) orelse math.maxInt(usize);
|
|
|
|
|
|
_ = boot_services.stall(us);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const s = nanoseconds / std.time.ns_per_s;
|
|
|
|
|
|
const ns = nanoseconds % std.time.ns_per_s;
|
|
|
|
|
|
|
|
|
|
|
|
// Newer kernel ports don't have old `nanosleep()` and `clock_nanosleep()` has been around
|
|
|
|
|
|
// since Linux 2.6 and glibc 2.1 anyway.
|
|
|
|
|
|
if (builtin.os.tag == .linux) {
|
|
|
|
|
|
const linux = std.os.linux;
|
|
|
|
|
|
|
|
|
|
|
|
var req: linux.timespec = .{
|
|
|
|
|
|
.sec = std.math.cast(linux.time_t, s) orelse std.math.maxInt(linux.time_t),
|
|
|
|
|
|
.nsec = std.math.cast(linux.time_t, ns) orelse std.math.maxInt(linux.time_t),
|
|
|
|
|
|
};
|
|
|
|
|
|
var rem: linux.timespec = undefined;
|
|
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
switch (linux.E.init(linux.clock_nanosleep(.MONOTONIC, .{ .ABSTIME = false }, &req, &rem))) {
|
|
|
|
|
|
.SUCCESS => return,
|
|
|
|
|
|
.INTR => {
|
|
|
|
|
|
req = rem;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
},
|
|
|
|
|
|
.FAULT,
|
|
|
|
|
|
.INVAL,
|
|
|
|
|
|
.OPNOTSUPP,
|
|
|
|
|
|
=> unreachable,
|
|
|
|
|
|
else => return,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
posix.nanosleep(s, ns);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
test sleep {
|
|
|
|
|
|
sleep(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-19 22:01:54 -05:00
|
|
|
|
const Thread = @This();
|
2024-03-18 22:39:59 -07:00
|
|
|
|
const Impl = if (native_os == .windows)
|
2021-06-19 17:08:56 -05:00
|
|
|
|
WindowsThreadImpl
|
|
|
|
|
|
else if (use_pthreads)
|
|
|
|
|
|
PosixThreadImpl
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else if (native_os == .linux)
|
2021-06-19 17:08:56 -05:00
|
|
|
|
LinuxThreadImpl
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else if (native_os == .wasi)
|
2023-06-20 22:19:51 +02:00
|
|
|
|
WasiThreadImpl
|
2021-06-19 17:08:56 -05:00
|
|
|
|
else
|
2021-06-26 13:00:54 -05:00
|
|
|
|
UnsupportedImpl;
|
2021-06-19 17:08:56 -05:00
|
|
|
|
|
|
|
|
|
|
impl: Impl,
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2024-03-18 22:39:59 -07:00
|
|
|
|
pub const max_name_len = switch (native_os) {
|
2021-04-21 10:59:36 +02:00
|
|
|
|
.linux => 15,
|
|
|
|
|
|
.windows => 31,
|
2024-05-09 15:04:13 +02:00
|
|
|
|
.macos, .ios, .watchos, .tvos, .visionos => 63,
|
2021-04-21 10:59:36 +02:00
|
|
|
|
.netbsd => 31,
|
|
|
|
|
|
.freebsd => 15,
|
2023-04-10 19:06:39 -04:00
|
|
|
|
.openbsd => 23,
|
2021-10-07 14:31:05 -04:00
|
|
|
|
.dragonfly => 1023,
|
2023-10-01 23:09:14 +11:00
|
|
|
|
.solaris, .illumos => 31,
|
2025-03-10 22:48:21 +00:00
|
|
|
|
// https://github.com/SerenityOS/serenity/blob/6b4c300353da49d3508b5442cf61da70bd04d757/Kernel/Tasks/Thread.h#L102
|
|
|
|
|
|
.serenity => 63,
|
2021-04-21 10:59:36 +02:00
|
|
|
|
else => 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
pub const SetNameError = error{
|
|
|
|
|
|
NameTooLong,
|
|
|
|
|
|
Unsupported,
|
|
|
|
|
|
Unexpected,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
} || posix.PrctlError || posix.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError;
|
2021-04-21 10:59:36 +02:00
|
|
|
|
|
|
|
|
|
|
pub fn setName(self: Thread, name: []const u8) SetNameError!void {
|
|
|
|
|
|
if (name.len > max_name_len) return error.NameTooLong;
|
|
|
|
|
|
|
|
|
|
|
|
const name_with_terminator = blk: {
|
|
|
|
|
|
var name_buf: [max_name_len:0]u8 = undefined;
|
2023-04-26 13:57:08 -07:00
|
|
|
|
@memcpy(name_buf[0..name.len], name);
|
2021-04-21 10:59:36 +02:00
|
|
|
|
name_buf[name.len] = 0;
|
|
|
|
|
|
break :blk name_buf[0..name.len :0];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-03-18 22:39:59 -07:00
|
|
|
|
switch (native_os) {
|
2021-04-21 10:59:36 +02:00
|
|
|
|
.linux => if (use_pthreads) {
|
2023-02-21 18:09:00 +01:00
|
|
|
|
if (self.getHandle() == std.c.pthread_self()) {
|
|
|
|
|
|
// Set the name of the calling thread (no thread id required).
|
2024-03-18 22:39:59 -07:00
|
|
|
|
const err = try posix.prctl(.SET_NAME, .{@intFromPtr(name_with_terminator.ptr)});
|
|
|
|
|
|
switch (@as(posix.E, @enumFromInt(err))) {
|
2023-02-21 18:09:00 +01:00
|
|
|
|
.SUCCESS => return,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |e| return posix.unexpectedErrno(e),
|
2023-02-21 18:09:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr);
|
2024-06-15 22:22:11 +02:00
|
|
|
|
switch (@as(posix.E, @enumFromInt(err))) {
|
2023-02-21 18:09:00 +01:00
|
|
|
|
.SUCCESS => return,
|
|
|
|
|
|
.RANGE => unreachable,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |e| return posix.unexpectedErrno(e),
|
2023-02-21 18:09:00 +01:00
|
|
|
|
}
|
2021-08-23 17:06:56 -07:00
|
|
|
|
}
|
2021-04-21 10:59:36 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
var buf: [32]u8 = undefined;
|
|
|
|
|
|
const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
|
|
|
|
|
|
|
2022-01-29 13:52:08 +00:00
|
|
|
|
const file = try std.fs.cwd().openFile(path, .{ .mode = .write_only });
|
2021-04-21 10:59:36 +02:00
|
|
|
|
defer file.close();
|
|
|
|
|
|
|
|
|
|
|
|
try file.writer().writeAll(name);
|
2021-10-07 14:31:06 -04:00
|
|
|
|
return;
|
2021-04-21 10:59:36 +02:00
|
|
|
|
},
|
2022-01-15 17:40:45 -06:00
|
|
|
|
.windows => {
|
|
|
|
|
|
var buf: [max_name_len]u16 = undefined;
|
2024-02-13 16:56:50 -08:00
|
|
|
|
const len = try std.unicode.wtf8ToWtf16Le(&buf, name);
|
2022-05-22 19:36:59 +04:30
|
|
|
|
const byte_len = math.cast(c_ushort, len * 2) orelse return error.NameTooLong;
|
2022-01-15 17:40:45 -06:00
|
|
|
|
|
|
|
|
|
|
// Note: NT allocates its own copy, no use-after-free here.
|
2024-03-18 22:39:59 -07:00
|
|
|
|
const unicode_string = windows.UNICODE_STRING{
|
2022-01-15 17:40:45 -06:00
|
|
|
|
.Length = byte_len,
|
|
|
|
|
|
.MaximumLength = byte_len,
|
|
|
|
|
|
.Buffer = &buf,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-03-18 22:39:59 -07:00
|
|
|
|
switch (windows.ntdll.NtSetInformationThread(
|
2021-04-21 10:59:36 +02:00
|
|
|
|
self.getHandle(),
|
2022-01-15 17:40:45 -06:00
|
|
|
|
.ThreadNameInformation,
|
|
|
|
|
|
&unicode_string,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
@sizeOf(windows.UNICODE_STRING),
|
2022-01-15 17:40:45 -06:00
|
|
|
|
)) {
|
|
|
|
|
|
.SUCCESS => return,
|
|
|
|
|
|
.NOT_IMPLEMENTED => return error.Unsupported,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |err| return windows.unexpectedStatus(err),
|
2022-01-15 17:40:45 -06:00
|
|
|
|
}
|
2021-04-21 10:59:36 +02:00
|
|
|
|
},
|
2024-05-09 15:04:13 +02:00
|
|
|
|
.macos, .ios, .watchos, .tvos, .visionos => if (use_pthreads) {
|
2021-04-21 10:59:36 +02:00
|
|
|
|
// There doesn't seem to be a way to set the name for an arbitrary thread, only the current one.
|
|
|
|
|
|
if (self.getHandle() != std.c.pthread_self()) return error.Unsupported;
|
|
|
|
|
|
|
|
|
|
|
|
const err = std.c.pthread_setname_np(name_with_terminator.ptr);
|
2024-06-15 22:22:11 +02:00
|
|
|
|
switch (@as(posix.E, @enumFromInt(err))) {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
.SUCCESS => return,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |e| return posix.unexpectedErrno(e),
|
2021-08-23 17:06:56 -07:00
|
|
|
|
}
|
2021-04-21 10:59:36 +02:00
|
|
|
|
},
|
2025-03-10 22:48:21 +00:00
|
|
|
|
.serenity => if (use_pthreads) {
|
|
|
|
|
|
const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr);
|
|
|
|
|
|
switch (@as(posix.E, @enumFromInt(err))) {
|
|
|
|
|
|
.SUCCESS => return,
|
|
|
|
|
|
.NAMETOOLONG => unreachable,
|
|
|
|
|
|
.SRCH => unreachable,
|
|
|
|
|
|
else => |e| return posix.unexpectedErrno(e),
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2023-10-01 23:09:14 +11:00
|
|
|
|
.netbsd, .solaris, .illumos => if (use_pthreads) {
|
2021-04-21 10:59:36 +02:00
|
|
|
|
const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr, null);
|
2024-06-15 22:22:11 +02:00
|
|
|
|
switch (@as(posix.E, @enumFromInt(err))) {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
.SUCCESS => return,
|
|
|
|
|
|
.INVAL => unreachable,
|
|
|
|
|
|
.SRCH => unreachable,
|
|
|
|
|
|
.NOMEM => unreachable,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |e| return posix.unexpectedErrno(e),
|
2021-08-23 17:06:56 -07:00
|
|
|
|
}
|
2021-04-21 10:59:36 +02:00
|
|
|
|
},
|
|
|
|
|
|
.freebsd, .openbsd => if (use_pthreads) {
|
|
|
|
|
|
// Use pthread_set_name_np for FreeBSD because pthread_setname_np is FreeBSD 12.2+ only.
|
2021-08-23 17:06:56 -07:00
|
|
|
|
// TODO maybe revisit this if depending on FreeBSD 12.2+ is acceptable because
|
|
|
|
|
|
// pthread_setname_np can return an error.
|
2021-04-21 10:59:36 +02:00
|
|
|
|
|
|
|
|
|
|
std.c.pthread_set_name_np(self.getHandle(), name_with_terminator.ptr);
|
2021-10-07 14:31:06 -04:00
|
|
|
|
return;
|
2021-04-21 10:59:36 +02:00
|
|
|
|
},
|
2021-10-07 14:31:05 -04:00
|
|
|
|
.dragonfly => if (use_pthreads) {
|
|
|
|
|
|
const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr);
|
2024-06-15 22:22:11 +02:00
|
|
|
|
switch (@as(posix.E, @enumFromInt(err))) {
|
2021-10-07 14:31:05 -04:00
|
|
|
|
.SUCCESS => return,
|
|
|
|
|
|
.INVAL => unreachable,
|
|
|
|
|
|
.FAULT => unreachable,
|
|
|
|
|
|
.NAMETOOLONG => unreachable, // already checked
|
|
|
|
|
|
.SRCH => unreachable,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |e| return posix.unexpectedErrno(e),
|
2021-10-07 14:31:05 -04:00
|
|
|
|
}
|
2021-04-21 10:59:36 +02:00
|
|
|
|
},
|
2021-10-07 14:31:06 -04:00
|
|
|
|
else => {},
|
2021-04-21 10:59:36 +02:00
|
|
|
|
}
|
2021-10-07 14:31:06 -04:00
|
|
|
|
return error.Unsupported;
|
2021-04-21 10:59:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub const GetNameError = error{
|
|
|
|
|
|
Unsupported,
|
|
|
|
|
|
Unexpected,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
} || posix.PrctlError || posix.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError;
|
2021-04-21 10:59:36 +02:00
|
|
|
|
|
2024-02-13 16:56:50 -08:00
|
|
|
|
/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
|
|
|
|
|
|
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
|
2021-04-21 10:59:36 +02:00
|
|
|
|
pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 {
|
|
|
|
|
|
buffer_ptr[max_name_len] = 0;
|
2023-01-22 16:40:00 +01:00
|
|
|
|
var buffer: [:0]u8 = buffer_ptr;
|
2021-04-21 10:59:36 +02:00
|
|
|
|
|
2024-03-18 22:39:59 -07:00
|
|
|
|
switch (native_os) {
|
2023-02-24 20:58:09 +01:00
|
|
|
|
.linux => if (use_pthreads) {
|
|
|
|
|
|
if (self.getHandle() == std.c.pthread_self()) {
|
|
|
|
|
|
// Get the name of the calling thread (no thread id required).
|
2024-03-18 22:39:59 -07:00
|
|
|
|
const err = try posix.prctl(.GET_NAME, .{@intFromPtr(buffer.ptr)});
|
|
|
|
|
|
switch (@as(posix.E, @enumFromInt(err))) {
|
2023-02-24 20:58:09 +01:00
|
|
|
|
.SUCCESS => return std.mem.sliceTo(buffer, 0),
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |e| return posix.unexpectedErrno(e),
|
2023-02-24 20:58:09 +01:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
|
2024-06-15 22:22:11 +02:00
|
|
|
|
switch (@as(posix.E, @enumFromInt(err))) {
|
2023-02-24 20:58:09 +01:00
|
|
|
|
.SUCCESS => return std.mem.sliceTo(buffer, 0),
|
|
|
|
|
|
.RANGE => unreachable,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |e| return posix.unexpectedErrno(e),
|
2023-02-24 20:58:09 +01:00
|
|
|
|
}
|
2021-08-23 17:06:56 -07:00
|
|
|
|
}
|
2023-02-24 20:58:09 +01:00
|
|
|
|
} else {
|
2021-04-21 10:59:36 +02:00
|
|
|
|
var buf: [32]u8 = undefined;
|
|
|
|
|
|
const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
|
|
|
|
|
|
|
|
|
|
|
|
const file = try std.fs.cwd().openFile(path, .{});
|
|
|
|
|
|
defer file.close();
|
|
|
|
|
|
|
|
|
|
|
|
const data_len = try file.reader().readAll(buffer_ptr[0 .. max_name_len + 1]);
|
|
|
|
|
|
|
|
|
|
|
|
return if (data_len >= 1) buffer[0 .. data_len - 1] else null;
|
|
|
|
|
|
},
|
2022-01-15 17:40:45 -06:00
|
|
|
|
.windows => {
|
2024-03-18 22:39:59 -07:00
|
|
|
|
const buf_capacity = @sizeOf(windows.UNICODE_STRING) + (@sizeOf(u16) * max_name_len);
|
|
|
|
|
|
var buf: [buf_capacity]u8 align(@alignOf(windows.UNICODE_STRING)) = undefined;
|
2021-04-21 10:59:36 +02:00
|
|
|
|
|
2024-03-18 22:39:59 -07:00
|
|
|
|
switch (windows.ntdll.NtQueryInformationThread(
|
2022-01-15 17:40:45 -06:00
|
|
|
|
self.getHandle(),
|
|
|
|
|
|
.ThreadNameInformation,
|
|
|
|
|
|
&buf,
|
|
|
|
|
|
buf_capacity,
|
|
|
|
|
|
null,
|
|
|
|
|
|
)) {
|
|
|
|
|
|
.SUCCESS => {
|
2024-03-18 22:39:59 -07:00
|
|
|
|
const string = @as(*const windows.UNICODE_STRING, @ptrCast(&buf));
|
2024-03-12 21:13:13 +11:00
|
|
|
|
const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer.?[0 .. string.Length / 2]);
|
2022-01-15 17:40:45 -06:00
|
|
|
|
return if (len > 0) buffer[0..len] else null;
|
|
|
|
|
|
},
|
|
|
|
|
|
.NOT_IMPLEMENTED => return error.Unsupported,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |err| return windows.unexpectedStatus(err),
|
2022-01-15 17:40:45 -06:00
|
|
|
|
}
|
2021-04-21 10:59:36 +02:00
|
|
|
|
},
|
2024-05-09 15:04:13 +02:00
|
|
|
|
.macos, .ios, .watchos, .tvos, .visionos => if (use_pthreads) {
|
2021-04-21 10:59:36 +02:00
|
|
|
|
const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
|
2024-06-15 22:22:11 +02:00
|
|
|
|
switch (@as(posix.E, @enumFromInt(err))) {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
.SUCCESS => return std.mem.sliceTo(buffer, 0),
|
|
|
|
|
|
.SRCH => unreachable,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |e| return posix.unexpectedErrno(e),
|
2021-08-23 17:06:56 -07:00
|
|
|
|
}
|
2021-04-21 10:59:36 +02:00
|
|
|
|
},
|
2025-03-10 22:48:21 +00:00
|
|
|
|
.serenity => if (use_pthreads) {
|
|
|
|
|
|
const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
|
|
|
|
|
|
switch (@as(posix.E, @enumFromInt(err))) {
|
|
|
|
|
|
.SUCCESS => return,
|
|
|
|
|
|
.NAMETOOLONG => unreachable,
|
|
|
|
|
|
.SRCH => unreachable,
|
|
|
|
|
|
.FAULT => unreachable,
|
|
|
|
|
|
else => |e| return posix.unexpectedErrno(e),
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2023-10-01 23:09:14 +11:00
|
|
|
|
.netbsd, .solaris, .illumos => if (use_pthreads) {
|
2021-04-21 10:59:36 +02:00
|
|
|
|
const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
|
2024-06-15 22:22:11 +02:00
|
|
|
|
switch (@as(posix.E, @enumFromInt(err))) {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
.SUCCESS => return std.mem.sliceTo(buffer, 0),
|
|
|
|
|
|
.INVAL => unreachable,
|
|
|
|
|
|
.SRCH => unreachable,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |e| return posix.unexpectedErrno(e),
|
2021-08-23 17:06:56 -07:00
|
|
|
|
}
|
2021-04-21 10:59:36 +02:00
|
|
|
|
},
|
|
|
|
|
|
.freebsd, .openbsd => if (use_pthreads) {
|
|
|
|
|
|
// Use pthread_get_name_np for FreeBSD because pthread_getname_np is FreeBSD 12.2+ only.
|
|
|
|
|
|
// TODO maybe revisit this if depending on FreeBSD 12.2+ is acceptable because pthread_getname_np can return an error.
|
|
|
|
|
|
|
|
|
|
|
|
std.c.pthread_get_name_np(self.getHandle(), buffer.ptr, max_name_len + 1);
|
|
|
|
|
|
return std.mem.sliceTo(buffer, 0);
|
|
|
|
|
|
},
|
2021-10-07 14:31:05 -04:00
|
|
|
|
.dragonfly => if (use_pthreads) {
|
|
|
|
|
|
const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
|
2024-06-15 22:22:11 +02:00
|
|
|
|
switch (@as(posix.E, @enumFromInt(err))) {
|
2021-10-07 14:31:05 -04:00
|
|
|
|
.SUCCESS => return std.mem.sliceTo(buffer, 0),
|
|
|
|
|
|
.INVAL => unreachable,
|
|
|
|
|
|
.FAULT => unreachable,
|
|
|
|
|
|
.SRCH => unreachable,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |e| return posix.unexpectedErrno(e),
|
2021-10-07 14:31:05 -04:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2021-10-07 14:31:06 -04:00
|
|
|
|
else => {},
|
2021-04-21 10:59:36 +02:00
|
|
|
|
}
|
2021-10-07 14:31:06 -04:00
|
|
|
|
return error.Unsupported;
|
2021-04-21 10:59:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-07 12:57:22 +01:00
|
|
|
|
/// Represents an ID per thread guaranteed to be unique only within a process.
|
2024-03-18 22:39:59 -07:00
|
|
|
|
pub const Id = switch (native_os) {
|
2023-01-07 12:57:22 +01:00
|
|
|
|
.linux,
|
|
|
|
|
|
.dragonfly,
|
|
|
|
|
|
.netbsd,
|
|
|
|
|
|
.freebsd,
|
|
|
|
|
|
.openbsd,
|
|
|
|
|
|
.haiku,
|
2023-06-20 22:19:51 +02:00
|
|
|
|
.wasi,
|
2025-03-10 22:48:21 +00:00
|
|
|
|
.serenity,
|
2023-01-07 12:57:22 +01:00
|
|
|
|
=> u32,
|
2024-05-09 15:04:13 +02:00
|
|
|
|
.macos, .ios, .watchos, .tvos, .visionos => u64,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
.windows => windows.DWORD,
|
2023-01-07 12:57:22 +01:00
|
|
|
|
else => usize,
|
|
|
|
|
|
};
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
/// Returns the platform ID of the callers thread.
|
|
|
|
|
|
/// Attempts to use thread locals and avoid syscalls when possible.
|
2021-01-14 20:41:37 -07:00
|
|
|
|
pub fn getCurrentId() Id {
|
2021-06-19 17:08:56 -05:00
|
|
|
|
return Impl.getCurrentId();
|
2021-01-14 20:41:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
pub const CpuCountError = error{
|
|
|
|
|
|
PermissionDenied,
|
|
|
|
|
|
SystemResources,
|
2024-07-07 13:18:53 -04:00
|
|
|
|
Unsupported,
|
2021-06-19 17:08:56 -05:00
|
|
|
|
Unexpected,
|
|
|
|
|
|
};
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
/// Returns the platforms view on the number of logical CPU cores available.
|
|
|
|
|
|
pub fn getCpuCount() CpuCountError!usize {
|
2024-07-07 13:18:53 -04:00
|
|
|
|
return try Impl.getCpuCount();
|
2021-01-14 20:41:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
/// Configuration options for hints on how to spawn threads.
|
|
|
|
|
|
pub const SpawnConfig = struct {
|
|
|
|
|
|
// TODO compile-time call graph analysis to determine stack upper bound
|
|
|
|
|
|
// https://github.com/ziglang/zig/issues/157
|
|
|
|
|
|
|
|
|
|
|
|
/// Size in bytes of the Thread's stack
|
2025-01-14 17:56:25 -05:00
|
|
|
|
stack_size: usize = default_stack_size,
|
2023-06-20 22:19:51 +02:00
|
|
|
|
/// The allocator to be used to allocate memory for the to-be-spawned thread
|
|
|
|
|
|
allocator: ?std.mem.Allocator = null,
|
2025-01-14 17:56:25 -05:00
|
|
|
|
|
|
|
|
|
|
pub const default_stack_size = 16 * 1024 * 1024;
|
2021-06-19 17:08:56 -05:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-06-28 11:27:23 -05:00
|
|
|
|
pub const SpawnError = error{
|
2021-01-14 20:41:37 -07:00
|
|
|
|
/// A system-imposed limit on the number of threads was encountered.
|
|
|
|
|
|
/// There are a number of limits that may trigger this error:
|
|
|
|
|
|
/// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),
|
|
|
|
|
|
/// which limits the number of processes and threads for a real
|
|
|
|
|
|
/// user ID, was reached;
|
|
|
|
|
|
/// * the kernel's system-wide limit on the number of processes and
|
|
|
|
|
|
/// threads, /proc/sys/kernel/threads-max, was reached (see
|
|
|
|
|
|
/// proc(5));
|
|
|
|
|
|
/// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was
|
|
|
|
|
|
/// reached (see proc(5)); or
|
|
|
|
|
|
/// * the PID limit (pids.max) imposed by the cgroup "process num‐
|
|
|
|
|
|
/// ber" (PIDs) controller was reached.
|
|
|
|
|
|
ThreadQuotaExceeded,
|
|
|
|
|
|
|
|
|
|
|
|
/// The kernel cannot allocate sufficient memory to allocate a task structure
|
|
|
|
|
|
/// for the child, or to copy those parts of the caller's context that need to
|
|
|
|
|
|
/// be copied.
|
|
|
|
|
|
SystemResources,
|
|
|
|
|
|
|
|
|
|
|
|
/// Not enough userland memory to spawn the thread.
|
|
|
|
|
|
OutOfMemory,
|
|
|
|
|
|
|
|
|
|
|
|
/// `mlockall` is enabled, and the memory needed to spawn the thread
|
|
|
|
|
|
/// would exceed the limit.
|
|
|
|
|
|
LockedMemoryLimitExceeded,
|
|
|
|
|
|
|
|
|
|
|
|
Unexpected,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2023-02-21 18:09:00 +01:00
|
|
|
|
/// Spawns a new thread which executes `function` using `args` and returns a handle to the spawned thread.
|
2023-12-29 22:23:26 -03:00
|
|
|
|
/// `config` can be used as hints to the platform for how to spawn and execute the `function`.
|
2021-06-19 17:08:56 -05:00
|
|
|
|
/// The caller must eventually either call `join()` to wait for the thread to finish and free its resources
|
2023-02-21 18:09:00 +01:00
|
|
|
|
/// or call `detach()` to excuse the caller from calling `join()` and have the thread clean up its resources on completion.
|
2021-06-20 09:56:30 -05:00
|
|
|
|
pub fn spawn(config: SpawnConfig, comptime function: anytype, args: anytype) SpawnError!Thread {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
if (builtin.single_threaded) {
|
2021-06-28 11:27:23 -05:00
|
|
|
|
@compileError("Cannot spawn thread when building in single-threaded mode");
|
2021-06-19 17:08:56 -05:00
|
|
|
|
}
|
2021-02-28 10:01:55 +01:00
|
|
|
|
|
2021-06-20 09:56:30 -05:00
|
|
|
|
const impl = try Impl.spawn(config, function, args);
|
|
|
|
|
|
return Thread{ .impl = impl };
|
2021-02-28 10:01:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-20 07:38:26 -05:00
|
|
|
|
/// Represents a kernel thread handle.
|
|
|
|
|
|
/// May be an integer or a pointer depending on the platform.
|
|
|
|
|
|
pub const Handle = Impl.ThreadHandle;
|
|
|
|
|
|
|
2022-02-25 13:38:30 -03:00
|
|
|
|
/// Returns the handle of this thread
|
2021-06-19 22:01:54 -05:00
|
|
|
|
pub fn getHandle(self: Thread) Handle {
|
|
|
|
|
|
return self.impl.getHandle();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Release the obligation of the caller to call `join()` and have the thread clean up its own resources on completion.
|
2021-06-28 11:27:23 -05:00
|
|
|
|
/// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior.
|
2021-06-19 22:01:54 -05:00
|
|
|
|
pub fn detach(self: Thread) void {
|
|
|
|
|
|
return self.impl.detach();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Waits for the thread to complete, then deallocates any resources created on `spawn()`.
|
2021-06-28 11:27:23 -05:00
|
|
|
|
/// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior.
|
2021-06-19 22:01:54 -05:00
|
|
|
|
pub fn join(self: Thread) void {
|
|
|
|
|
|
return self.impl.join();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-25 16:52:25 +05:30
|
|
|
|
pub const YieldError = error{
|
|
|
|
|
|
/// The system is not configured to allow yielding
|
|
|
|
|
|
SystemCannotYield,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// Yields the current thread potentially allowing other threads to run.
|
|
|
|
|
|
pub fn yield() YieldError!void {
|
2024-03-18 22:39:59 -07:00
|
|
|
|
if (native_os == .windows) {
|
2022-02-25 16:52:25 +05:30
|
|
|
|
// The return value has to do with how many other threads there are; it is not
|
|
|
|
|
|
// an error condition on Windows.
|
2024-03-18 22:39:59 -07:00
|
|
|
|
_ = windows.kernel32.SwitchToThread();
|
2022-02-25 16:52:25 +05:30
|
|
|
|
return;
|
|
|
|
|
|
}
|
2024-03-18 22:39:59 -07:00
|
|
|
|
switch (posix.errno(posix.system.sched_yield())) {
|
2022-02-25 16:52:25 +05:30
|
|
|
|
.SUCCESS => return,
|
|
|
|
|
|
.NOSYS => return error.SystemCannotYield,
|
|
|
|
|
|
else => return error.SystemCannotYield,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-19 22:01:54 -05:00
|
|
|
|
/// State to synchronize detachment of spawner thread to spawned thread
|
2024-07-26 14:03:20 -07:00
|
|
|
|
const Completion = std.atomic.Value(enum(if (builtin.zig_backend == .stage2_riscv64) u32 else u8) {
|
2021-06-19 22:01:54 -05:00
|
|
|
|
running,
|
|
|
|
|
|
detached,
|
|
|
|
|
|
completed,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
/// Used by the Thread implementations to call the spawned function with the arguments.
|
|
|
|
|
|
fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
|
2024-03-18 22:39:59 -07:00
|
|
|
|
WindowsThreadImpl => windows.DWORD,
|
2021-06-19 17:08:56 -05:00
|
|
|
|
LinuxThreadImpl => u8,
|
2021-12-19 06:24:45 +01:00
|
|
|
|
PosixThreadImpl => ?*anyopaque,
|
2021-06-19 17:08:56 -05:00
|
|
|
|
else => unreachable,
|
|
|
|
|
|
} {
|
|
|
|
|
|
const default_value = if (Impl == PosixThreadImpl) null else 0;
|
2024-06-01 15:04:02 -04:00
|
|
|
|
const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', '!noreturn', 'void', or '!void'";
|
2021-06-19 17:08:56 -05:00
|
|
|
|
|
2024-08-28 02:35:53 +01:00
|
|
|
|
switch (@typeInfo(@typeInfo(@TypeOf(f)).@"fn".return_type.?)) {
|
|
|
|
|
|
.noreturn => {
|
2022-12-12 15:32:37 +02:00
|
|
|
|
@call(.auto, f, args);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
},
|
2024-08-28 02:35:53 +01:00
|
|
|
|
.void => {
|
2022-12-12 15:32:37 +02:00
|
|
|
|
@call(.auto, f, args);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
return default_value;
|
|
|
|
|
|
},
|
2024-08-28 02:35:53 +01:00
|
|
|
|
.int => |info| {
|
2021-06-19 17:08:56 -05:00
|
|
|
|
if (info.bits != 8) {
|
|
|
|
|
|
@compileError(bad_fn_ret);
|
|
|
|
|
|
}
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2022-12-12 15:32:37 +02:00
|
|
|
|
const status = @call(.auto, f, args);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
if (Impl != PosixThreadImpl) {
|
|
|
|
|
|
return status;
|
|
|
|
|
|
}
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
// pthreads don't support exit status, ignore value
|
|
|
|
|
|
return default_value;
|
|
|
|
|
|
},
|
2024-08-28 02:35:53 +01:00
|
|
|
|
.error_union => |info| {
|
2024-06-01 15:04:02 -04:00
|
|
|
|
switch (info.payload) {
|
|
|
|
|
|
void, noreturn => {
|
|
|
|
|
|
@call(.auto, f, args) catch |err| {
|
|
|
|
|
|
std.debug.print("error: {s}\n", .{@errorName(err)});
|
|
|
|
|
|
if (@errorReturnTrace()) |trace| {
|
|
|
|
|
|
std.debug.dumpStackTrace(trace.*);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return default_value;
|
|
|
|
|
|
},
|
|
|
|
|
|
else => {
|
|
|
|
|
|
@compileError(bad_fn_ret);
|
|
|
|
|
|
},
|
2021-06-19 17:08:56 -05:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
else => {
|
|
|
|
|
|
@compileError(bad_fn_ret);
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-28 11:27:23 -05:00
|
|
|
|
/// We can't compile error in the `Impl` switch statement as its eagerly evaluated.
|
|
|
|
|
|
/// So instead, we compile-error on the methods themselves for platforms which don't support threads.
|
2021-06-26 13:00:54 -05:00
|
|
|
|
const UnsupportedImpl = struct {
|
|
|
|
|
|
pub const ThreadHandle = void;
|
|
|
|
|
|
|
2023-01-07 12:57:22 +01:00
|
|
|
|
fn getCurrentId() usize {
|
2021-06-26 13:00:54 -05:00
|
|
|
|
return unsupported({});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn getCpuCount() !usize {
|
|
|
|
|
|
return unsupported({});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
|
2021-06-28 11:27:23 -05:00
|
|
|
|
return unsupported(.{ config, f, args });
|
2021-06-26 13:00:54 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn getHandle(self: Impl) ThreadHandle {
|
|
|
|
|
|
return unsupported(self);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn detach(self: Impl) void {
|
|
|
|
|
|
return unsupported(self);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn join(self: Impl) void {
|
|
|
|
|
|
return unsupported(self);
|
2021-06-28 11:27:23 -05:00
|
|
|
|
}
|
2021-06-26 13:00:54 -05:00
|
|
|
|
|
2023-04-30 18:02:08 +01:00
|
|
|
|
fn unsupported(unused: anytype) noreturn {
|
|
|
|
|
|
_ = unused;
|
2024-03-18 22:39:59 -07:00
|
|
|
|
@compileError("Unsupported operating system " ++ @tagName(native_os));
|
2021-06-26 13:00:54 -05:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
const WindowsThreadImpl = struct {
|
|
|
|
|
|
pub const ThreadHandle = windows.HANDLE;
|
|
|
|
|
|
|
2023-01-07 12:57:22 +01:00
|
|
|
|
fn getCurrentId() windows.DWORD {
|
2024-07-21 17:25:28 -03:00
|
|
|
|
return windows.GetCurrentThreadId();
|
2021-06-19 17:08:56 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn getCpuCount() !usize {
|
2021-06-28 11:27:23 -05:00
|
|
|
|
// Faster than calling into GetSystemInfo(), even if amortized.
|
2021-06-19 17:08:56 -05:00
|
|
|
|
return windows.peb().NumberOfProcessors;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
thread: *ThreadCompletion,
|
|
|
|
|
|
|
|
|
|
|
|
const ThreadCompletion = struct {
|
|
|
|
|
|
completion: Completion,
|
|
|
|
|
|
heap_ptr: windows.PVOID,
|
|
|
|
|
|
heap_handle: windows.HANDLE,
|
|
|
|
|
|
thread_handle: windows.HANDLE = undefined,
|
|
|
|
|
|
|
|
|
|
|
|
fn free(self: ThreadCompletion) void {
|
|
|
|
|
|
const status = windows.kernel32.HeapFree(self.heap_handle, 0, self.heap_ptr);
|
2021-06-22 11:27:58 -05:00
|
|
|
|
assert(status != 0);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
|
|
|
|
|
|
const Args = @TypeOf(args);
|
|
|
|
|
|
const Instance = struct {
|
|
|
|
|
|
fn_args: Args,
|
|
|
|
|
|
thread: ThreadCompletion,
|
|
|
|
|
|
|
2024-11-01 01:06:00 +01:00
|
|
|
|
fn entryFn(raw_ptr: windows.PVOID) callconv(.winapi) windows.DWORD {
|
2023-06-22 18:46:56 +01:00
|
|
|
|
const self: *@This() = @ptrCast(@alignCast(raw_ptr));
|
2024-02-18 21:52:23 -08:00
|
|
|
|
defer switch (self.thread.completion.swap(.completed, .seq_cst)) {
|
2021-06-19 17:08:56 -05:00
|
|
|
|
.running => {},
|
|
|
|
|
|
.completed => unreachable,
|
|
|
|
|
|
.detached => self.thread.free(),
|
|
|
|
|
|
};
|
|
|
|
|
|
return callFn(f, self.fn_args);
|
2021-01-14 20:41:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory;
|
2021-06-19 17:08:56 -05:00
|
|
|
|
const alloc_bytes = @alignOf(Instance) + @sizeOf(Instance);
|
|
|
|
|
|
const alloc_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, alloc_bytes) orelse return error.OutOfMemory;
|
|
|
|
|
|
errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, alloc_ptr) != 0);
|
|
|
|
|
|
|
2023-06-22 18:46:56 +01:00
|
|
|
|
const instance_bytes = @as([*]u8, @ptrCast(alloc_ptr))[0..alloc_bytes];
|
2022-07-04 16:53:41 -07:00
|
|
|
|
var fba = std.heap.FixedBufferAllocator.init(instance_bytes);
|
|
|
|
|
|
const instance = fba.allocator().create(Instance) catch unreachable;
|
2021-06-19 17:08:56 -05:00
|
|
|
|
instance.* = .{
|
|
|
|
|
|
.fn_args = args,
|
|
|
|
|
|
.thread = .{
|
|
|
|
|
|
.completion = Completion.init(.running),
|
|
|
|
|
|
.heap_ptr = alloc_ptr,
|
|
|
|
|
|
.heap_handle = heap_handle,
|
2021-01-14 20:41:37 -07:00
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-06-20 15:39:23 -05:00
|
|
|
|
// Windows appears to only support SYSTEM_INFO.dwAllocationGranularity minimum stack size.
|
|
|
|
|
|
// Going lower makes it default to that specified in the executable (~1mb).
|
|
|
|
|
|
// Its also fine if the limit here is incorrect as stack size is only a hint.
|
2022-05-22 19:36:59 +04:30
|
|
|
|
var stack_size = std.math.cast(u32, config.stack_size) orelse std.math.maxInt(u32);
|
2023-06-02 22:02:45 -04:00
|
|
|
|
stack_size = @max(64 * 1024, stack_size);
|
2021-06-28 11:27:23 -05:00
|
|
|
|
|
2021-06-20 17:12:28 -05:00
|
|
|
|
instance.thread.thread_handle = windows.kernel32.CreateThread(
|
2021-06-28 11:27:23 -05:00
|
|
|
|
null,
|
|
|
|
|
|
stack_size,
|
|
|
|
|
|
Instance.entryFn,
|
2023-10-31 07:17:39 +03:00
|
|
|
|
instance,
|
2021-06-28 11:27:23 -05:00
|
|
|
|
0,
|
2021-06-20 15:39:23 -05:00
|
|
|
|
null,
|
|
|
|
|
|
) orelse {
|
2024-07-15 14:49:51 -03:00
|
|
|
|
const errno = windows.GetLastError();
|
2021-06-28 11:27:23 -05:00
|
|
|
|
return windows.unexpectedError(errno);
|
2021-01-14 20:41:37 -07:00
|
|
|
|
};
|
2021-06-19 17:08:56 -05:00
|
|
|
|
|
2021-06-22 11:27:58 -05:00
|
|
|
|
return Impl{ .thread = &instance.thread };
|
2021-01-14 20:41:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
fn getHandle(self: Impl) ThreadHandle {
|
|
|
|
|
|
return self.thread.thread_handle;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn detach(self: Impl) void {
|
|
|
|
|
|
windows.CloseHandle(self.thread.thread_handle);
|
2024-02-18 21:52:23 -08:00
|
|
|
|
switch (self.thread.completion.swap(.detached, .seq_cst)) {
|
2021-06-19 17:08:56 -05:00
|
|
|
|
.running => {},
|
|
|
|
|
|
.completed => self.thread.free(),
|
|
|
|
|
|
.detached => unreachable,
|
2021-01-14 20:41:37 -07:00
|
|
|
|
}
|
2021-06-19 17:08:56 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn join(self: Impl) void {
|
|
|
|
|
|
windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable;
|
|
|
|
|
|
windows.CloseHandle(self.thread.thread_handle);
|
2024-02-18 21:52:23 -08:00
|
|
|
|
assert(self.thread.completion.load(.seq_cst) == .completed);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
self.thread.free();
|
2021-06-28 11:27:23 -05:00
|
|
|
|
}
|
2021-06-19 17:08:56 -05:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const PosixThreadImpl = struct {
|
|
|
|
|
|
const c = std.c;
|
|
|
|
|
|
|
|
|
|
|
|
pub const ThreadHandle = c.pthread_t;
|
|
|
|
|
|
|
2021-06-20 07:38:26 -05:00
|
|
|
|
fn getCurrentId() Id {
|
2024-03-18 22:39:59 -07:00
|
|
|
|
switch (native_os) {
|
2021-06-20 07:38:26 -05:00
|
|
|
|
.linux => {
|
|
|
|
|
|
return LinuxThreadImpl.getCurrentId();
|
|
|
|
|
|
},
|
2024-05-09 15:04:13 +02:00
|
|
|
|
.macos, .ios, .watchos, .tvos, .visionos => {
|
2021-06-20 07:38:26 -05:00
|
|
|
|
var thread_id: u64 = undefined;
|
|
|
|
|
|
// Pass thread=null to get the current thread ID.
|
|
|
|
|
|
assert(c.pthread_threadid_np(null, &thread_id) == 0);
|
|
|
|
|
|
return thread_id;
|
|
|
|
|
|
},
|
|
|
|
|
|
.dragonfly => {
|
2023-06-22 18:46:56 +01:00
|
|
|
|
return @as(u32, @bitCast(c.lwp_gettid()));
|
2021-06-20 07:38:26 -05:00
|
|
|
|
},
|
|
|
|
|
|
.netbsd => {
|
2023-06-22 18:46:56 +01:00
|
|
|
|
return @as(u32, @bitCast(c._lwp_self()));
|
2021-06-20 07:38:26 -05:00
|
|
|
|
},
|
|
|
|
|
|
.freebsd => {
|
2023-06-22 18:46:56 +01:00
|
|
|
|
return @as(u32, @bitCast(c.pthread_getthreadid_np()));
|
2021-06-20 07:38:26 -05:00
|
|
|
|
},
|
|
|
|
|
|
.openbsd => {
|
2023-06-22 18:46:56 +01:00
|
|
|
|
return @as(u32, @bitCast(c.getthrid()));
|
2021-06-20 07:38:26 -05:00
|
|
|
|
},
|
|
|
|
|
|
.haiku => {
|
2023-06-22 18:46:56 +01:00
|
|
|
|
return @as(u32, @bitCast(c.find_thread(null)));
|
2021-06-20 07:38:26 -05:00
|
|
|
|
},
|
2025-03-10 22:48:21 +00:00
|
|
|
|
.serenity => {
|
|
|
|
|
|
return @as(u32, @bitCast(c.pthread_self()));
|
|
|
|
|
|
},
|
2021-06-20 07:38:26 -05:00
|
|
|
|
else => {
|
2023-06-15 13:14:16 +06:00
|
|
|
|
return @intFromPtr(c.pthread_self());
|
2021-06-20 07:38:26 -05:00
|
|
|
|
},
|
|
|
|
|
|
}
|
2021-06-19 17:08:56 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn getCpuCount() !usize {
|
2024-03-18 22:39:59 -07:00
|
|
|
|
switch (native_os) {
|
2021-06-28 11:27:23 -05:00
|
|
|
|
.linux => {
|
|
|
|
|
|
return LinuxThreadImpl.getCpuCount();
|
|
|
|
|
|
},
|
2021-06-19 17:08:56 -05:00
|
|
|
|
.openbsd => {
|
|
|
|
|
|
var count: c_int = undefined;
|
|
|
|
|
|
var count_size: usize = @sizeOf(c_int);
|
2024-03-18 22:39:59 -07:00
|
|
|
|
const mib = [_]c_int{ std.c.CTL.HW, std.c.HW.NCPUONLINE };
|
2024-03-20 11:45:41 -04:00
|
|
|
|
posix.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) {
|
2021-06-19 17:08:56 -05:00
|
|
|
|
error.NameTooLong, error.UnknownName => unreachable,
|
|
|
|
|
|
else => |e| return e,
|
|
|
|
|
|
};
|
2023-06-22 18:46:56 +01:00
|
|
|
|
return @as(usize, @intCast(count));
|
2021-06-19 17:08:56 -05:00
|
|
|
|
},
|
2025-03-10 22:48:21 +00:00
|
|
|
|
.solaris, .illumos, .serenity => {
|
2021-08-31 22:21:23 +10:00
|
|
|
|
// The "proper" way to get the cpu count would be to query
|
|
|
|
|
|
// /dev/kstat via ioctls, and traverse a linked list for each
|
2025-03-10 22:48:21 +00:00
|
|
|
|
// cpu. (solaris, illumos)
|
|
|
|
|
|
const rc = c.sysconf(@intFromEnum(std.c._SC.NPROCESSORS_ONLN));
|
2024-03-18 22:39:59 -07:00
|
|
|
|
return switch (posix.errno(rc)) {
|
2023-06-22 18:46:56 +01:00
|
|
|
|
.SUCCESS => @as(usize, @intCast(rc)),
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |err| posix.unexpectedErrno(err),
|
2021-08-31 22:21:23 +10:00
|
|
|
|
};
|
|
|
|
|
|
},
|
2021-06-19 17:08:56 -05:00
|
|
|
|
.haiku => {
|
2024-03-18 22:39:59 -07:00
|
|
|
|
var system_info: std.c.system_info = undefined;
|
|
|
|
|
|
const rc = std.c.get_system_info(&system_info); // always returns B_OK
|
|
|
|
|
|
return switch (posix.errno(rc)) {
|
2023-06-22 18:46:56 +01:00
|
|
|
|
.SUCCESS => @as(usize, @intCast(system_info.cpu_count)),
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |err| posix.unexpectedErrno(err),
|
2021-10-31 21:27:04 -05:00
|
|
|
|
};
|
2021-06-19 17:08:56 -05:00
|
|
|
|
},
|
|
|
|
|
|
else => {
|
|
|
|
|
|
var count: c_int = undefined;
|
|
|
|
|
|
var count_len: usize = @sizeOf(c_int);
|
2025-01-24 03:45:38 +01:00
|
|
|
|
const name = if (comptime target.os.tag.isDarwin()) "hw.logicalcpu" else "hw.ncpu";
|
2024-03-18 22:39:59 -07:00
|
|
|
|
posix.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) {
|
2021-06-19 17:08:56 -05:00
|
|
|
|
error.NameTooLong, error.UnknownName => unreachable,
|
|
|
|
|
|
else => |e| return e,
|
|
|
|
|
|
};
|
2023-06-22 18:46:56 +01:00
|
|
|
|
return @as(usize, @intCast(count));
|
2021-06-19 17:08:56 -05:00
|
|
|
|
},
|
2021-01-14 20:41:37 -07:00
|
|
|
|
}
|
2021-06-19 17:08:56 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handle: ThreadHandle,
|
|
|
|
|
|
|
|
|
|
|
|
fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
|
|
|
|
|
|
const Args = @TypeOf(args);
|
|
|
|
|
|
const allocator = std.heap.c_allocator;
|
2021-06-26 10:52:34 -05:00
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
const Instance = struct {
|
2024-11-01 01:06:00 +01:00
|
|
|
|
fn entryFn(raw_arg: ?*anyopaque) callconv(.c) ?*anyopaque {
|
2023-06-22 18:46:56 +01:00
|
|
|
|
const args_ptr: *Args = @ptrCast(@alignCast(raw_arg));
|
2021-06-26 10:52:34 -05:00
|
|
|
|
defer allocator.destroy(args_ptr);
|
|
|
|
|
|
return callFn(f, args_ptr.*);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const args_ptr = try allocator.create(Args);
|
2021-06-26 09:03:53 -05:00
|
|
|
|
args_ptr.* = args;
|
2021-06-19 17:08:56 -05:00
|
|
|
|
errdefer allocator.destroy(args_ptr);
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
|
|
|
|
|
var attr: c.pthread_attr_t = undefined;
|
2021-08-23 17:06:56 -07:00
|
|
|
|
if (c.pthread_attr_init(&attr) != .SUCCESS) return error.SystemResources;
|
|
|
|
|
|
defer assert(c.pthread_attr_destroy(&attr) == .SUCCESS);
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
|
|
|
|
|
// Use the same set of parameters used by the libc-less impl.
|
2023-07-31 10:54:35 -07:00
|
|
|
|
const stack_size = @max(config.stack_size, 16 * 1024);
|
2021-08-23 17:06:56 -07:00
|
|
|
|
assert(c.pthread_attr_setstacksize(&attr, stack_size) == .SUCCESS);
|
2024-10-20 14:55:57 -07:00
|
|
|
|
assert(c.pthread_attr_setguardsize(&attr, std.heap.pageSize()) == .SUCCESS);
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
var handle: c.pthread_t = undefined;
|
2021-06-25 12:43:03 -05:00
|
|
|
|
switch (c.pthread_create(
|
2021-06-19 17:08:56 -05:00
|
|
|
|
&handle,
|
2021-01-14 20:41:37 -07:00
|
|
|
|
&attr,
|
2021-06-19 17:08:56 -05:00
|
|
|
|
Instance.entryFn,
|
2024-01-29 17:07:21 +01:00
|
|
|
|
@ptrCast(args_ptr),
|
2021-06-19 17:08:56 -05:00
|
|
|
|
)) {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
.SUCCESS => return Impl{ .handle = handle },
|
|
|
|
|
|
.AGAIN => return error.SystemResources,
|
|
|
|
|
|
.PERM => unreachable,
|
|
|
|
|
|
.INVAL => unreachable,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |err| return posix.unexpectedErrno(err),
|
2021-06-25 12:59:28 -05:00
|
|
|
|
}
|
2021-06-19 17:08:56 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn getHandle(self: Impl) ThreadHandle {
|
|
|
|
|
|
return self.handle;
|
|
|
|
|
|
}
|
2021-04-26 10:44:40 -07:00
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
fn detach(self: Impl) void {
|
|
|
|
|
|
switch (c.pthread_detach(self.handle)) {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
.SUCCESS => {},
|
|
|
|
|
|
.INVAL => unreachable, // thread handle is not joinable
|
|
|
|
|
|
.SRCH => unreachable, // thread handle is invalid
|
2021-06-19 17:08:56 -05:00
|
|
|
|
else => unreachable,
|
|
|
|
|
|
}
|
2021-01-14 20:41:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
fn join(self: Impl) void {
|
|
|
|
|
|
switch (c.pthread_join(self.handle, null)) {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
.SUCCESS => {},
|
|
|
|
|
|
.INVAL => unreachable, // thread handle is not joinable (or another thread is already joining in)
|
|
|
|
|
|
.SRCH => unreachable, // thread handle is invalid
|
|
|
|
|
|
.DEADLK => unreachable, // two threads tried to join each other
|
2021-06-19 17:08:56 -05:00
|
|
|
|
else => unreachable,
|
2021-01-14 20:41:37 -07:00
|
|
|
|
}
|
2021-06-19 17:08:56 -05:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2023-06-20 22:19:51 +02:00
|
|
|
|
const WasiThreadImpl = struct {
|
|
|
|
|
|
thread: *WasiThread,
|
|
|
|
|
|
|
|
|
|
|
|
pub const ThreadHandle = i32;
|
|
|
|
|
|
threadlocal var tls_thread_id: Id = 0;
|
|
|
|
|
|
|
|
|
|
|
|
const WasiThread = struct {
|
2023-06-21 21:43:11 +02:00
|
|
|
|
/// Thread ID
|
2023-11-22 18:49:18 -07:00
|
|
|
|
tid: std.atomic.Value(i32) = std.atomic.Value(i32).init(0),
|
2023-06-21 21:43:11 +02:00
|
|
|
|
/// Contains all memory which was allocated to bootstrap this thread, including:
|
|
|
|
|
|
/// - Guard page
|
|
|
|
|
|
/// - Stack
|
|
|
|
|
|
/// - TLS segment
|
|
|
|
|
|
/// - `Instance`
|
|
|
|
|
|
/// All memory is freed upon call to `join`
|
2023-06-20 22:19:51 +02:00
|
|
|
|
memory: []u8,
|
2023-06-21 21:43:11 +02:00
|
|
|
|
/// The allocator used to allocate the thread's memory,
|
|
|
|
|
|
/// which is also used during `join` to ensure clean-up.
|
|
|
|
|
|
allocator: std.mem.Allocator,
|
2023-06-23 19:14:55 +02:00
|
|
|
|
/// The current state of the thread.
|
|
|
|
|
|
state: State = State.init(.running),
|
2023-06-20 22:19:51 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// A meta-data structure used to bootstrap a thread
|
|
|
|
|
|
const Instance = struct {
|
|
|
|
|
|
thread: WasiThread,
|
2023-06-22 19:53:07 +02:00
|
|
|
|
/// Contains the offset to the new __tls_base.
|
|
|
|
|
|
/// The offset starting from the memory's base.
|
|
|
|
|
|
tls_offset: usize,
|
|
|
|
|
|
/// Contains the offset to the stack for the newly spawned thread.
|
|
|
|
|
|
/// The offset is calculated starting from the memory's base.
|
|
|
|
|
|
stack_offset: usize,
|
|
|
|
|
|
/// Contains the raw pointer value to the wrapper which holds all arguments
|
2023-06-20 22:19:51 +02:00
|
|
|
|
/// for the callback.
|
|
|
|
|
|
raw_ptr: usize,
|
|
|
|
|
|
/// Function pointer to a wrapping function which will call the user's
|
|
|
|
|
|
/// function upon thread spawn. The above mentioned pointer will be passed
|
|
|
|
|
|
/// to this function pointer as its argument.
|
|
|
|
|
|
call_back: *const fn (usize) void,
|
2023-06-23 19:14:55 +02:00
|
|
|
|
/// When a thread is in `detached` state, we must free all of its memory
|
|
|
|
|
|
/// upon thread completion. However, as this is done while still within
|
|
|
|
|
|
/// the thread, we must first jump back to the main thread's stack or else
|
|
|
|
|
|
/// we end up freeing the stack that we're currently using.
|
|
|
|
|
|
original_stack_pointer: [*]u8,
|
2023-06-20 22:19:51 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2023-11-22 18:49:18 -07:00
|
|
|
|
const State = std.atomic.Value(enum(u8) { running, completed, detached });
|
2023-06-23 19:14:55 +02:00
|
|
|
|
|
2023-06-20 22:19:51 +02:00
|
|
|
|
fn getCurrentId() Id {
|
|
|
|
|
|
return tls_thread_id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-07 13:18:53 -04:00
|
|
|
|
fn getCpuCount() error{Unsupported}!noreturn {
|
|
|
|
|
|
return error.Unsupported;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-20 22:19:51 +02:00
|
|
|
|
fn getHandle(self: Impl) ThreadHandle {
|
2024-02-18 21:52:23 -08:00
|
|
|
|
return self.thread.tid.load(.seq_cst);
|
2023-06-20 22:19:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn detach(self: Impl) void {
|
2024-02-18 21:52:23 -08:00
|
|
|
|
switch (self.thread.state.swap(.detached, .seq_cst)) {
|
2023-06-23 19:14:55 +02:00
|
|
|
|
.running => {},
|
|
|
|
|
|
.completed => self.join(),
|
|
|
|
|
|
.detached => unreachable,
|
|
|
|
|
|
}
|
2023-06-20 22:19:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn join(self: Impl) void {
|
2023-06-22 19:53:07 +02:00
|
|
|
|
defer {
|
|
|
|
|
|
// Create a copy of the allocator so we do not free the reference to the
|
|
|
|
|
|
// original allocator while freeing the memory.
|
|
|
|
|
|
var allocator = self.thread.allocator;
|
|
|
|
|
|
allocator.free(self.thread.memory);
|
|
|
|
|
|
}
|
2023-06-21 21:44:53 +02:00
|
|
|
|
|
|
|
|
|
|
var spin: u8 = 10;
|
|
|
|
|
|
while (true) {
|
2024-02-18 21:52:23 -08:00
|
|
|
|
const tid = self.thread.tid.load(.seq_cst);
|
2023-06-21 21:44:53 +02:00
|
|
|
|
if (tid == 0) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (spin > 0) {
|
|
|
|
|
|
spin -= 1;
|
|
|
|
|
|
std.atomic.spinLoopHint();
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const result = asm (
|
2023-06-22 19:53:07 +02:00
|
|
|
|
\\ local.get %[ptr]
|
|
|
|
|
|
\\ local.get %[expected]
|
|
|
|
|
|
\\ i64.const -1 # infinite
|
|
|
|
|
|
\\ memory.atomic.wait32 0
|
|
|
|
|
|
\\ local.set %[ret]
|
2023-06-21 21:44:53 +02:00
|
|
|
|
: [ret] "=r" (-> u32),
|
2024-03-18 00:39:32 +01:00
|
|
|
|
: [ptr] "r" (&self.thread.tid.raw),
|
2023-06-21 21:44:53 +02:00
|
|
|
|
[expected] "r" (tid),
|
|
|
|
|
|
);
|
|
|
|
|
|
switch (result) {
|
|
|
|
|
|
0 => continue, // ok
|
|
|
|
|
|
1 => continue, // expected =! loaded
|
|
|
|
|
|
2 => unreachable, // timeout (infinite)
|
|
|
|
|
|
else => unreachable,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-06-20 22:19:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-18 00:39:32 +01:00
|
|
|
|
fn spawn(config: std.Thread.SpawnConfig, comptime f: anytype, args: anytype) SpawnError!WasiThreadImpl {
|
|
|
|
|
|
if (config.allocator == null) {
|
2024-03-19 13:11:33 +01:00
|
|
|
|
@panic("an allocator is required to spawn a WASI thread");
|
2024-03-18 00:39:32 +01:00
|
|
|
|
}
|
2023-06-20 22:19:51 +02:00
|
|
|
|
|
|
|
|
|
|
// Wrapping struct required to hold the user-provided function arguments.
|
|
|
|
|
|
const Wrapper = struct {
|
|
|
|
|
|
args: @TypeOf(args),
|
|
|
|
|
|
fn entry(ptr: usize) void {
|
2023-06-23 19:14:55 +02:00
|
|
|
|
const w: *@This() = @ptrFromInt(ptr);
|
2024-03-18 00:39:32 +01:00
|
|
|
|
const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";
|
2024-08-28 02:35:53 +01:00
|
|
|
|
switch (@typeInfo(@typeInfo(@TypeOf(f)).@"fn".return_type.?)) {
|
|
|
|
|
|
.noreturn, .void => {
|
2024-04-20 13:11:15 +02:00
|
|
|
|
@call(.auto, f, w.args);
|
2024-03-18 00:39:32 +01:00
|
|
|
|
},
|
2024-08-28 02:35:53 +01:00
|
|
|
|
.int => |info| {
|
2024-03-18 00:39:32 +01:00
|
|
|
|
if (info.bits != 8) {
|
|
|
|
|
|
@compileError(bad_fn_ret);
|
|
|
|
|
|
}
|
2024-04-20 13:11:15 +02:00
|
|
|
|
_ = @call(.auto, f, w.args); // WASI threads don't support exit status, ignore value
|
2024-03-18 00:39:32 +01:00
|
|
|
|
},
|
2024-08-28 02:35:53 +01:00
|
|
|
|
.error_union => |info| {
|
2024-03-18 00:39:32 +01:00
|
|
|
|
if (info.payload != void) {
|
|
|
|
|
|
@compileError(bad_fn_ret);
|
|
|
|
|
|
}
|
2024-04-20 13:11:15 +02:00
|
|
|
|
@call(.auto, f, w.args) catch |err| {
|
2024-03-18 00:39:32 +01:00
|
|
|
|
std.debug.print("error: {s}\n", .{@errorName(err)});
|
|
|
|
|
|
if (@errorReturnTrace()) |trace| {
|
|
|
|
|
|
std.debug.dumpStackTrace(trace.*);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
else => {
|
|
|
|
|
|
@compileError(bad_fn_ret);
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
2023-06-20 22:19:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var stack_offset: usize = undefined;
|
|
|
|
|
|
var tls_offset: usize = undefined;
|
|
|
|
|
|
var wrapper_offset: usize = undefined;
|
|
|
|
|
|
var instance_offset: usize = undefined;
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate the bytes we have to allocate to store all thread information, including:
|
|
|
|
|
|
// - The actual stack for the thread
|
|
|
|
|
|
// - The TLS segment
|
|
|
|
|
|
// - `Instance` - containing information about how to call the user's function.
|
|
|
|
|
|
const map_bytes = blk: {
|
2023-06-21 21:43:11 +02:00
|
|
|
|
// start with atleast a single page, which is used as a guard to prevent
|
|
|
|
|
|
// other threads clobbering our new thread.
|
|
|
|
|
|
// Unfortunately, WebAssembly has no notion of read-only segments, so this
|
2023-06-23 19:14:55 +02:00
|
|
|
|
// is only a best effort.
|
2023-06-20 22:19:51 +02:00
|
|
|
|
var bytes: usize = std.wasm.page_size;
|
|
|
|
|
|
|
|
|
|
|
|
bytes = std.mem.alignForward(usize, bytes, 16); // align stack to 16 bytes
|
|
|
|
|
|
stack_offset = bytes;
|
|
|
|
|
|
bytes += @max(std.wasm.page_size, config.stack_size);
|
|
|
|
|
|
|
|
|
|
|
|
bytes = std.mem.alignForward(usize, bytes, __tls_align());
|
|
|
|
|
|
tls_offset = bytes;
|
|
|
|
|
|
bytes += __tls_size();
|
|
|
|
|
|
|
|
|
|
|
|
bytes = std.mem.alignForward(usize, bytes, @alignOf(Wrapper));
|
|
|
|
|
|
wrapper_offset = bytes;
|
|
|
|
|
|
bytes += @sizeOf(Wrapper);
|
|
|
|
|
|
|
|
|
|
|
|
bytes = std.mem.alignForward(usize, bytes, @alignOf(Instance));
|
|
|
|
|
|
instance_offset = bytes;
|
|
|
|
|
|
bytes += @sizeOf(Instance);
|
|
|
|
|
|
|
|
|
|
|
|
bytes = std.mem.alignForward(usize, bytes, std.wasm.page_size);
|
|
|
|
|
|
break :blk bytes;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Allocate the amount of memory required for all meta data.
|
|
|
|
|
|
const allocated_memory = try config.allocator.?.alloc(u8, map_bytes);
|
|
|
|
|
|
|
2023-06-23 19:14:55 +02:00
|
|
|
|
const wrapper: *Wrapper = @ptrCast(@alignCast(&allocated_memory[wrapper_offset]));
|
2023-06-20 22:19:51 +02:00
|
|
|
|
wrapper.* = .{ .args = args };
|
|
|
|
|
|
|
2023-06-23 19:14:55 +02:00
|
|
|
|
const instance: *Instance = @ptrCast(@alignCast(&allocated_memory[instance_offset]));
|
2023-06-20 22:19:51 +02:00
|
|
|
|
instance.* = .{
|
2023-06-21 21:43:11 +02:00
|
|
|
|
.thread = .{ .memory = allocated_memory, .allocator = config.allocator.? },
|
2023-06-22 19:53:07 +02:00
|
|
|
|
.tls_offset = tls_offset,
|
|
|
|
|
|
.stack_offset = stack_offset,
|
2023-06-23 19:14:55 +02:00
|
|
|
|
.raw_ptr = @intFromPtr(wrapper),
|
2023-06-20 22:19:51 +02:00
|
|
|
|
.call_back = &Wrapper.entry,
|
2023-06-23 19:14:55 +02:00
|
|
|
|
.original_stack_pointer = __get_stack_pointer(),
|
2023-06-20 22:19:51 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const tid = spawnWasiThread(instance);
|
|
|
|
|
|
// The specification says any value lower than 0 indicates an error.
|
|
|
|
|
|
// The values of such error are unspecified. WASI-Libc treats it as EAGAIN.
|
|
|
|
|
|
if (tid < 0) {
|
|
|
|
|
|
return error.SystemResources;
|
|
|
|
|
|
}
|
2024-02-18 21:52:23 -08:00
|
|
|
|
instance.thread.tid.store(tid, .seq_cst);
|
2023-06-20 22:19:51 +02:00
|
|
|
|
|
|
|
|
|
|
return .{ .thread = &instance.thread };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-15 20:51:46 -08:00
|
|
|
|
comptime {
|
|
|
|
|
|
if (!builtin.single_threaded) {
|
2025-01-18 11:33:31 +01:00
|
|
|
|
@export(&wasi_thread_start, .{ .name = "wasi_thread_start" });
|
2023-06-26 19:10:34 +02:00
|
|
|
|
}
|
2024-12-15 20:51:46 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Called by the host environment after thread creation.
|
|
|
|
|
|
fn wasi_thread_start(tid: i32, arg: *Instance) callconv(.c) void {
|
|
|
|
|
|
comptime assert(!builtin.single_threaded);
|
2023-06-22 19:53:07 +02:00
|
|
|
|
__set_stack_pointer(arg.thread.memory.ptr + arg.stack_offset);
|
|
|
|
|
|
__wasm_init_tls(arg.thread.memory.ptr + arg.tls_offset);
|
2024-02-18 21:52:23 -08:00
|
|
|
|
@atomicStore(u32, &WasiThreadImpl.tls_thread_id, @intCast(tid), .seq_cst);
|
2023-06-20 22:19:51 +02:00
|
|
|
|
|
2023-06-21 21:44:53 +02:00
|
|
|
|
// Finished bootstrapping, call user's procedure.
|
2023-06-20 22:19:51 +02:00
|
|
|
|
arg.call_back(arg.raw_ptr);
|
2023-06-21 21:44:53 +02:00
|
|
|
|
|
2024-02-18 21:52:23 -08:00
|
|
|
|
switch (arg.thread.state.swap(.completed, .seq_cst)) {
|
2023-06-23 19:14:55 +02:00
|
|
|
|
.running => {
|
|
|
|
|
|
// reset the Thread ID
|
|
|
|
|
|
asm volatile (
|
|
|
|
|
|
\\ local.get %[ptr]
|
|
|
|
|
|
\\ i32.const 0
|
|
|
|
|
|
\\ i32.atomic.store 0
|
|
|
|
|
|
:
|
2024-03-18 00:39:32 +01:00
|
|
|
|
: [ptr] "r" (&arg.thread.tid.raw),
|
2023-06-23 19:14:55 +02:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Wake the main thread listening to this thread
|
|
|
|
|
|
asm volatile (
|
|
|
|
|
|
\\ local.get %[ptr]
|
|
|
|
|
|
\\ i32.const 1 # waiters
|
|
|
|
|
|
\\ memory.atomic.notify 0
|
|
|
|
|
|
\\ drop # no need to know the waiters
|
|
|
|
|
|
:
|
2024-03-18 00:39:32 +01:00
|
|
|
|
: [ptr] "r" (&arg.thread.tid.raw),
|
2023-06-23 19:14:55 +02:00
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
.completed => unreachable,
|
|
|
|
|
|
.detached => {
|
|
|
|
|
|
// restore the original stack pointer so we can free the memory
|
|
|
|
|
|
// without having to worry about freeing the stack
|
|
|
|
|
|
__set_stack_pointer(arg.original_stack_pointer);
|
|
|
|
|
|
// Ensure a copy so we don't free the allocator reference itself
|
|
|
|
|
|
var allocator = arg.thread.allocator;
|
|
|
|
|
|
allocator.free(arg.thread.memory);
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
2023-06-20 22:19:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-21 21:44:53 +02:00
|
|
|
|
/// Asks the host to create a new thread for us.
|
|
|
|
|
|
/// Newly created thread will call `wasi_tread_start` with the thread ID as well
|
|
|
|
|
|
/// as the input `arg` that was provided to `spawnWasiThread`
|
2023-06-20 22:19:51 +02:00
|
|
|
|
const spawnWasiThread = @"thread-spawn";
|
2023-06-21 21:44:53 +02:00
|
|
|
|
extern "wasi" fn @"thread-spawn"(arg: *Instance) i32;
|
2023-06-20 22:19:51 +02:00
|
|
|
|
|
|
|
|
|
|
/// Initializes the TLS data segment starting at `memory`.
|
|
|
|
|
|
/// This is a synthetic function, generated by the linker.
|
|
|
|
|
|
extern fn __wasm_init_tls(memory: [*]u8) void;
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns a pointer to the base of the TLS data segment for the current thread
|
|
|
|
|
|
inline fn __tls_base() [*]u8 {
|
|
|
|
|
|
return asm (
|
|
|
|
|
|
\\ .globaltype __tls_base, i32
|
|
|
|
|
|
\\ global.get __tls_base
|
|
|
|
|
|
\\ local.set %[ret]
|
|
|
|
|
|
: [ret] "=r" (-> [*]u8),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns the size of the TLS segment
|
|
|
|
|
|
inline fn __tls_size() u32 {
|
|
|
|
|
|
return asm volatile (
|
|
|
|
|
|
\\ .globaltype __tls_size, i32, immutable
|
|
|
|
|
|
\\ global.get __tls_size
|
|
|
|
|
|
\\ local.set %[ret]
|
|
|
|
|
|
: [ret] "=r" (-> u32),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns the alignment of the TLS segment
|
|
|
|
|
|
inline fn __tls_align() u32 {
|
|
|
|
|
|
return asm (
|
|
|
|
|
|
\\ .globaltype __tls_align, i32, immutable
|
|
|
|
|
|
\\ global.get __tls_align
|
|
|
|
|
|
\\ local.set %[ret]
|
|
|
|
|
|
: [ret] "=r" (-> u32),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2023-06-21 21:43:11 +02:00
|
|
|
|
|
|
|
|
|
|
/// Allows for setting the stack pointer in the WebAssembly module.
|
|
|
|
|
|
inline fn __set_stack_pointer(addr: [*]u8) void {
|
|
|
|
|
|
asm volatile (
|
|
|
|
|
|
\\ local.get %[ptr]
|
|
|
|
|
|
\\ global.set __stack_pointer
|
|
|
|
|
|
:
|
|
|
|
|
|
: [ptr] "r" (addr),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2023-06-23 19:14:55 +02:00
|
|
|
|
|
|
|
|
|
|
/// Returns the current value of the stack pointer
|
|
|
|
|
|
inline fn __get_stack_pointer() [*]u8 {
|
|
|
|
|
|
return asm (
|
|
|
|
|
|
\\ global.get __stack_pointer
|
|
|
|
|
|
\\ local.set %[stack_ptr]
|
|
|
|
|
|
: [stack_ptr] "=r" (-> [*]u8),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2023-06-20 22:19:51 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
const LinuxThreadImpl = struct {
|
2024-03-18 22:39:59 -07:00
|
|
|
|
const linux = std.os.linux;
|
2021-06-28 11:27:23 -05:00
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
pub const ThreadHandle = i32;
|
|
|
|
|
|
|
2021-06-20 07:38:26 -05:00
|
|
|
|
threadlocal var tls_thread_id: ?Id = null;
|
2021-06-19 17:08:56 -05:00
|
|
|
|
|
2021-06-20 07:38:26 -05:00
|
|
|
|
fn getCurrentId() Id {
|
2021-06-19 17:08:56 -05:00
|
|
|
|
return tls_thread_id orelse {
|
2023-06-22 18:46:56 +01:00
|
|
|
|
const tid = @as(u32, @bitCast(linux.gettid()));
|
2021-06-19 17:08:56 -05:00
|
|
|
|
tls_thread_id = tid;
|
|
|
|
|
|
return tid;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn getCpuCount() !usize {
|
2024-03-18 22:39:59 -07:00
|
|
|
|
const cpu_set = try posix.sched_getaffinity(0);
|
2024-10-27 09:40:56 +01:00
|
|
|
|
return posix.CPU_COUNT(cpu_set);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
thread: *ThreadCompletion,
|
|
|
|
|
|
|
|
|
|
|
|
const ThreadCompletion = struct {
|
|
|
|
|
|
completion: Completion = Completion.init(.running),
|
2023-11-22 18:49:18 -07:00
|
|
|
|
child_tid: std.atomic.Value(i32) = std.atomic.Value(i32).init(1),
|
2021-06-19 17:08:56 -05:00
|
|
|
|
parent_tid: i32 = undefined,
|
2025-01-29 14:16:25 -08:00
|
|
|
|
mapped: []align(std.heap.page_size_min) u8,
|
2021-07-01 17:34:23 -05:00
|
|
|
|
|
|
|
|
|
|
/// Calls `munmap(mapped.ptr, mapped.len)` then `exit(1)` without touching the stack (which lives in `mapped.ptr`).
|
|
|
|
|
|
/// Ported over from musl libc's pthread detached implementation:
|
|
|
|
|
|
/// https://github.com/ifduyue/musl/search?q=__unmapself
|
|
|
|
|
|
fn freeAndExit(self: *ThreadCompletion) noreturn {
|
2021-07-16 23:06:59 -07:00
|
|
|
|
switch (target.cpu.arch) {
|
2022-10-07 21:24:44 +03:30
|
|
|
|
.x86 => asm volatile (
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ movl $91, %%eax # SYS_munmap
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ movl %[ptr], %%ebx
|
|
|
|
|
|
\\ movl %[len], %%ecx
|
|
|
|
|
|
\\ int $128
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ movl $1, %%eax # SYS_exit
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ movl $0, %%ebx
|
|
|
|
|
|
\\ int $128
|
2021-07-16 23:06:59 -07:00
|
|
|
|
:
|
2023-06-15 13:14:16 +06:00
|
|
|
|
: [ptr] "r" (@intFromPtr(self.mapped.ptr)),
|
2021-08-29 11:57:32 +02:00
|
|
|
|
[len] "r" (self.mapped.len),
|
2021-07-16 23:06:59 -07:00
|
|
|
|
: "memory"
|
2021-07-01 17:34:23 -05:00
|
|
|
|
),
|
2021-07-16 23:06:59 -07:00
|
|
|
|
.x86_64 => asm volatile (
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ movq $11, %%rax # SYS_munmap
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ syscall
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ movq $60, %%rax # SYS_exit
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ movq $1, %%rdi
|
|
|
|
|
|
\\ syscall
|
2021-07-16 23:06:59 -07:00
|
|
|
|
:
|
2023-06-15 13:14:16 +06:00
|
|
|
|
: [ptr] "{rdi}" (@intFromPtr(self.mapped.ptr)),
|
2022-08-31 02:31:47 +00:00
|
|
|
|
[len] "{rsi}" (self.mapped.len),
|
2021-07-01 17:34:23 -05:00
|
|
|
|
),
|
2021-07-16 23:06:59 -07:00
|
|
|
|
.arm, .armeb, .thumb, .thumbeb => asm volatile (
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ mov r7, #91 // SYS_munmap
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ mov r0, %[ptr]
|
|
|
|
|
|
\\ mov r1, %[len]
|
|
|
|
|
|
\\ svc 0
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ mov r7, #1 // SYS_exit
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ mov r0, #0
|
|
|
|
|
|
\\ svc 0
|
2021-07-16 23:06:59 -07:00
|
|
|
|
:
|
2023-06-15 13:14:16 +06:00
|
|
|
|
: [ptr] "r" (@intFromPtr(self.mapped.ptr)),
|
2021-08-29 11:57:32 +02:00
|
|
|
|
[len] "r" (self.mapped.len),
|
2021-07-16 23:06:59 -07:00
|
|
|
|
: "memory"
|
2021-07-01 17:34:23 -05:00
|
|
|
|
),
|
std.Target.Cpu.Arch: Remove the `aarch64_32` tag.
This is a misfeature that we inherited from LLVM:
* https://reviews.llvm.org/D61259
* https://reviews.llvm.org/D61939
(`aarch64_32` and `arm64_32` are equivalent.)
I truly have no idea why this triple passed review in LLVM. It is, to date, the
*only* tag in the architecture component that is not, in fact, an architecture.
In reality, it is just an ILP32 ABI for AArch64 (*not* AArch32).
The triples that use `aarch64_32` look like `aarch64_32-apple-watchos`. Yes,
that triple is exactly what you think; it has no ABI component. They really,
seriously did this.
Since only Apple could come up with silliness like this, it should come as no
surprise that no one else uses `aarch64_32`. Later on, a GNU ILP32 ABI for
AArch64 was developed, and support was added to LLVM:
* https://reviews.llvm.org/D94143
* https://reviews.llvm.org/D104931
Here, sanity seems to have prevailed, and a triple using this ABI looks like
`aarch64-linux-gnu_ilp32` as you would expect.
As can be seen from the diffs in this commit, there was plenty of confusion
throughout the Zig codebase about what exactly `aarch64_32` was. So let's just
remove it. In its place, we'll use `aarch64-watchos-ilp32`,
`aarch64-linux-gnuilp32`, and so on. We'll then translate these appropriately
when talking to LLVM. Hence, this commit adds the `ilp32` ABI tag (we already
have `gnuilp32`).
2024-07-27 03:52:19 +02:00
|
|
|
|
.aarch64, .aarch64_be => asm volatile (
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ mov x8, #215 // SYS_munmap
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ mov x0, %[ptr]
|
|
|
|
|
|
\\ mov x1, %[len]
|
|
|
|
|
|
\\ svc 0
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ mov x8, #93 // SYS_exit
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ mov x0, #0
|
|
|
|
|
|
\\ svc 0
|
2021-07-16 23:06:59 -07:00
|
|
|
|
:
|
2023-06-15 13:14:16 +06:00
|
|
|
|
: [ptr] "r" (@intFromPtr(self.mapped.ptr)),
|
2021-08-29 11:57:32 +02:00
|
|
|
|
[len] "r" (self.mapped.len),
|
2021-07-16 23:06:59 -07:00
|
|
|
|
: "memory"
|
2021-07-01 17:34:23 -05:00
|
|
|
|
),
|
2024-08-18 02:29:54 +02:00
|
|
|
|
.hexagon => asm volatile (
|
|
|
|
|
|
\\ r6 = #215 // SYS_munmap
|
|
|
|
|
|
\\ r0 = %[ptr]
|
|
|
|
|
|
\\ r1 = %[len]
|
|
|
|
|
|
\\ trap0(#1)
|
|
|
|
|
|
\\ r6 = #93 // SYS_exit
|
|
|
|
|
|
\\ r0 = #0
|
|
|
|
|
|
\\ trap0(#1)
|
|
|
|
|
|
:
|
|
|
|
|
|
: [ptr] "r" (@intFromPtr(self.mapped.ptr)),
|
|
|
|
|
|
[len] "r" (self.mapped.len),
|
|
|
|
|
|
: "memory"
|
|
|
|
|
|
),
|
2024-08-12 04:10:27 +02:00
|
|
|
|
// We set `sp` to the address of the current function as a workaround for a Linux
|
|
|
|
|
|
// kernel bug that caused syscalls to return EFAULT if the stack pointer is invalid.
|
|
|
|
|
|
// The bug was introduced in 46e12c07b3b9603c60fc1d421ff18618241cb081 and fixed in
|
|
|
|
|
|
// 7928eb0370d1133d0d8cd2f5ddfca19c309079d5.
|
2021-07-16 23:06:59 -07:00
|
|
|
|
.mips, .mipsel => asm volatile (
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ move $sp, $25
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ li $2, 4091 # SYS_munmap
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ move $4, %[ptr]
|
|
|
|
|
|
\\ move $5, %[len]
|
|
|
|
|
|
\\ syscall
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ li $2, 4001 # SYS_exit
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ li $4, 0
|
|
|
|
|
|
\\ syscall
|
2021-07-16 23:06:59 -07:00
|
|
|
|
:
|
2023-06-15 13:14:16 +06:00
|
|
|
|
: [ptr] "r" (@intFromPtr(self.mapped.ptr)),
|
2021-08-29 11:57:32 +02:00
|
|
|
|
[len] "r" (self.mapped.len),
|
2021-07-16 23:06:59 -07:00
|
|
|
|
: "memory"
|
2021-07-01 17:34:23 -05:00
|
|
|
|
),
|
2021-07-16 23:06:59 -07:00
|
|
|
|
.mips64, .mips64el => asm volatile (
|
2024-08-12 12:34:40 +02:00
|
|
|
|
\\ li $2, 5011 # SYS_munmap
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ move $4, %[ptr]
|
|
|
|
|
|
\\ move $5, %[len]
|
|
|
|
|
|
\\ syscall
|
2024-08-12 12:34:40 +02:00
|
|
|
|
\\ li $2, 5058 # SYS_exit
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ li $4, 0
|
|
|
|
|
|
\\ syscall
|
2021-07-16 23:06:59 -07:00
|
|
|
|
:
|
2023-06-15 13:14:16 +06:00
|
|
|
|
: [ptr] "r" (@intFromPtr(self.mapped.ptr)),
|
2021-08-29 11:57:32 +02:00
|
|
|
|
[len] "r" (self.mapped.len),
|
2021-07-16 23:06:59 -07:00
|
|
|
|
: "memory"
|
2021-07-01 17:34:23 -05:00
|
|
|
|
),
|
2021-07-16 23:06:59 -07:00
|
|
|
|
.powerpc, .powerpcle, .powerpc64, .powerpc64le => asm volatile (
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ li 0, 91 # SYS_munmap
|
2024-08-12 12:36:38 +02:00
|
|
|
|
\\ mr 3, %[ptr]
|
|
|
|
|
|
\\ mr 4, %[len]
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ sc
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ li 0, 1 # SYS_exit
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ li 3, 0
|
|
|
|
|
|
\\ sc
|
|
|
|
|
|
\\ blr
|
2021-07-16 23:06:59 -07:00
|
|
|
|
:
|
2023-06-15 13:14:16 +06:00
|
|
|
|
: [ptr] "r" (@intFromPtr(self.mapped.ptr)),
|
2021-08-29 11:57:32 +02:00
|
|
|
|
[len] "r" (self.mapped.len),
|
2021-07-16 23:06:59 -07:00
|
|
|
|
: "memory"
|
2021-07-01 17:34:23 -05:00
|
|
|
|
),
|
2024-08-12 12:40:54 +02:00
|
|
|
|
.riscv32, .riscv64 => asm volatile (
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ li a7, 215 # SYS_munmap
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ mv a0, %[ptr]
|
|
|
|
|
|
\\ mv a1, %[len]
|
|
|
|
|
|
\\ ecall
|
2024-07-20 05:34:25 +02:00
|
|
|
|
\\ li a7, 93 # SYS_exit
|
2021-07-01 17:34:23 -05:00
|
|
|
|
\\ mv a0, zero
|
|
|
|
|
|
\\ ecall
|
2021-07-16 23:06:59 -07:00
|
|
|
|
:
|
2023-06-15 13:14:16 +06:00
|
|
|
|
: [ptr] "r" (@intFromPtr(self.mapped.ptr)),
|
2021-08-29 11:57:32 +02:00
|
|
|
|
[len] "r" (self.mapped.len),
|
2021-07-16 23:06:59 -07:00
|
|
|
|
: "memory"
|
2021-07-01 17:34:23 -05:00
|
|
|
|
),
|
2024-08-18 02:22:25 +02:00
|
|
|
|
.s390x => asm volatile (
|
|
|
|
|
|
\\ lgr %%r2, %[ptr]
|
|
|
|
|
|
\\ lgr %%r3, %[len]
|
|
|
|
|
|
\\ svc 91 # SYS_munmap
|
|
|
|
|
|
\\ lghi %%r2, 0
|
|
|
|
|
|
\\ svc 1 # SYS_exit
|
|
|
|
|
|
:
|
|
|
|
|
|
: [ptr] "r" (@intFromPtr(self.mapped.ptr)),
|
|
|
|
|
|
[len] "r" (self.mapped.len),
|
|
|
|
|
|
: "memory"
|
|
|
|
|
|
),
|
2024-08-18 02:02:47 +02:00
|
|
|
|
.sparc => asm volatile (
|
|
|
|
|
|
\\ # See sparc64 comments below.
|
|
|
|
|
|
\\ 1:
|
|
|
|
|
|
\\ cmp %%fp, 0
|
|
|
|
|
|
\\ beq 2f
|
|
|
|
|
|
\\ nop
|
|
|
|
|
|
\\ ba 1b
|
|
|
|
|
|
\\ restore
|
|
|
|
|
|
\\ 2:
|
2024-09-05 09:37:09 +02:00
|
|
|
|
\\ mov 73, %%g1 // SYS_munmap
|
2024-08-18 02:02:47 +02:00
|
|
|
|
\\ mov %[ptr], %%o0
|
|
|
|
|
|
\\ mov %[len], %%o1
|
|
|
|
|
|
\\ t 0x3 # ST_FLUSH_WINDOWS
|
|
|
|
|
|
\\ t 0x10
|
2024-09-05 09:37:09 +02:00
|
|
|
|
\\ mov 1, %%g1 // SYS_exit
|
2024-08-18 02:02:47 +02:00
|
|
|
|
\\ mov 0, %%o0
|
|
|
|
|
|
\\ t 0x10
|
|
|
|
|
|
:
|
|
|
|
|
|
: [ptr] "r" (@intFromPtr(self.mapped.ptr)),
|
|
|
|
|
|
[len] "r" (self.mapped.len),
|
|
|
|
|
|
: "memory"
|
|
|
|
|
|
),
|
2022-05-13 22:59:06 +07:00
|
|
|
|
.sparc64 => asm volatile (
|
2021-07-30 21:45:28 +07:00
|
|
|
|
\\ # SPARCs really don't like it when active stack frames
|
|
|
|
|
|
\\ # is unmapped (it will result in a segfault), so we
|
|
|
|
|
|
\\ # force-deactivate it by running `restore` until
|
|
|
|
|
|
\\ # all frames are cleared.
|
2024-08-18 02:03:22 +02:00
|
|
|
|
\\ 1:
|
2021-09-27 21:00:07 +07:00
|
|
|
|
\\ cmp %%fp, 0
|
2021-07-30 21:45:28 +07:00
|
|
|
|
\\ beq 2f
|
2021-08-03 20:14:40 +07:00
|
|
|
|
\\ nop
|
2021-09-27 21:00:07 +07:00
|
|
|
|
\\ ba 1b
|
2021-07-30 21:45:28 +07:00
|
|
|
|
\\ restore
|
2024-08-18 02:03:22 +02:00
|
|
|
|
\\ 2:
|
2024-09-05 09:37:09 +02:00
|
|
|
|
\\ mov 73, %%g1 // SYS_munmap
|
2021-07-30 21:45:28 +07:00
|
|
|
|
\\ mov %[ptr], %%o0
|
|
|
|
|
|
\\ mov %[len], %%o1
|
|
|
|
|
|
\\ # Flush register window contents to prevent background
|
|
|
|
|
|
\\ # memory access before unmapping the stack.
|
|
|
|
|
|
\\ flushw
|
|
|
|
|
|
\\ t 0x6d
|
2024-09-05 09:37:09 +02:00
|
|
|
|
\\ mov 1, %%g1 // SYS_exit
|
2024-08-18 02:03:22 +02:00
|
|
|
|
\\ mov 0, %%o0
|
2021-07-30 21:45:28 +07:00
|
|
|
|
\\ t 0x6d
|
|
|
|
|
|
:
|
2024-08-09 08:30:57 +08:00
|
|
|
|
: [ptr] "r" (@intFromPtr(self.mapped.ptr)),
|
|
|
|
|
|
[len] "r" (self.mapped.len),
|
|
|
|
|
|
: "memory"
|
|
|
|
|
|
),
|
2024-10-16 01:06:27 +02:00
|
|
|
|
.loongarch32, .loongarch64 => asm volatile (
|
2024-08-09 08:30:57 +08:00
|
|
|
|
\\ or $a0, $zero, %[ptr]
|
|
|
|
|
|
\\ or $a1, $zero, %[len]
|
|
|
|
|
|
\\ ori $a7, $zero, 215 # SYS_munmap
|
|
|
|
|
|
\\ syscall 0 # call munmap
|
|
|
|
|
|
\\ ori $a0, $zero, 0
|
|
|
|
|
|
\\ ori $a7, $zero, 93 # SYS_exit
|
|
|
|
|
|
\\ syscall 0 # call exit
|
|
|
|
|
|
:
|
2023-06-15 13:14:16 +06:00
|
|
|
|
: [ptr] "r" (@intFromPtr(self.mapped.ptr)),
|
2021-08-29 11:57:32 +02:00
|
|
|
|
[len] "r" (self.mapped.len),
|
2021-07-30 21:45:28 +07:00
|
|
|
|
: "memory"
|
|
|
|
|
|
),
|
2021-07-16 23:06:59 -07:00
|
|
|
|
else => |cpu_arch| @compileError("Unsupported linux arch: " ++ @tagName(cpu_arch)),
|
|
|
|
|
|
}
|
2021-07-01 17:34:23 -05:00
|
|
|
|
unreachable;
|
|
|
|
|
|
}
|
2021-01-14 20:41:37 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
|
2024-10-20 14:55:57 -07:00
|
|
|
|
const page_size = std.heap.pageSize();
|
2021-06-19 17:08:56 -05:00
|
|
|
|
const Args = @TypeOf(args);
|
|
|
|
|
|
const Instance = struct {
|
|
|
|
|
|
fn_args: Args,
|
|
|
|
|
|
thread: ThreadCompletion,
|
|
|
|
|
|
|
2024-11-01 01:06:00 +01:00
|
|
|
|
fn entryFn(raw_arg: usize) callconv(.c) u8 {
|
2023-06-22 18:46:56 +01:00
|
|
|
|
const self = @as(*@This(), @ptrFromInt(raw_arg));
|
2024-02-18 21:52:23 -08:00
|
|
|
|
defer switch (self.thread.completion.swap(.completed, .seq_cst)) {
|
2021-06-19 17:08:56 -05:00
|
|
|
|
.running => {},
|
|
|
|
|
|
.completed => unreachable,
|
2021-07-01 17:34:23 -05:00
|
|
|
|
.detached => self.thread.freeAndExit(),
|
2021-06-19 17:08:56 -05:00
|
|
|
|
};
|
|
|
|
|
|
return callFn(f, self.fn_args);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var guard_offset: usize = undefined;
|
|
|
|
|
|
var stack_offset: usize = undefined;
|
|
|
|
|
|
var tls_offset: usize = undefined;
|
|
|
|
|
|
var instance_offset: usize = undefined;
|
|
|
|
|
|
|
|
|
|
|
|
const map_bytes = blk: {
|
2022-05-17 15:13:20 -07:00
|
|
|
|
var bytes: usize = page_size;
|
2021-06-19 17:08:56 -05:00
|
|
|
|
guard_offset = bytes;
|
|
|
|
|
|
|
2023-06-02 22:02:45 -04:00
|
|
|
|
bytes += @max(page_size, config.stack_size);
|
2023-06-09 16:02:18 -07:00
|
|
|
|
bytes = std.mem.alignForward(usize, bytes, page_size);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
stack_offset = bytes;
|
|
|
|
|
|
|
2024-07-23 00:57:25 +02:00
|
|
|
|
bytes = std.mem.alignForward(usize, bytes, linux.tls.area_desc.alignment);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
tls_offset = bytes;
|
2024-07-23 00:57:25 +02:00
|
|
|
|
bytes += linux.tls.area_desc.size;
|
2021-06-19 17:08:56 -05:00
|
|
|
|
|
2023-06-09 16:02:18 -07:00
|
|
|
|
bytes = std.mem.alignForward(usize, bytes, @alignOf(Instance));
|
2021-06-19 17:08:56 -05:00
|
|
|
|
instance_offset = bytes;
|
|
|
|
|
|
bytes += @sizeOf(Instance);
|
|
|
|
|
|
|
2023-06-09 16:02:18 -07:00
|
|
|
|
bytes = std.mem.alignForward(usize, bytes, page_size);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
break :blk bytes;
|
|
|
|
|
|
};
|
2021-06-28 11:27:23 -05:00
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
// map all memory needed without read/write permissions
|
|
|
|
|
|
// to avoid committing the whole region right away
|
2023-03-05 18:41:52 +01:00
|
|
|
|
// anonymous mapping ensures file descriptor limits are not exceeded
|
2024-03-18 22:39:59 -07:00
|
|
|
|
const mapped = posix.mmap(
|
2021-01-14 20:41:37 -07:00
|
|
|
|
null,
|
2021-06-19 17:08:56 -05:00
|
|
|
|
map_bytes,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
posix.PROT.NONE,
|
2024-02-06 21:12:11 -07:00
|
|
|
|
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
2021-01-14 20:41:37 -07:00
|
|
|
|
-1,
|
|
|
|
|
|
0,
|
|
|
|
|
|
) catch |err| switch (err) {
|
|
|
|
|
|
error.MemoryMappingNotSupported => unreachable,
|
|
|
|
|
|
error.AccessDenied => unreachable,
|
|
|
|
|
|
error.PermissionDenied => unreachable,
|
2023-03-05 18:41:52 +01:00
|
|
|
|
error.ProcessFdQuotaExceeded => unreachable,
|
|
|
|
|
|
error.SystemFdQuotaExceeded => unreachable,
|
2025-02-22 15:07:44 -05:00
|
|
|
|
error.MappingAlreadyExists => unreachable,
|
2021-01-14 20:41:37 -07:00
|
|
|
|
else => |e| return e,
|
|
|
|
|
|
};
|
2021-06-26 09:03:53 -05:00
|
|
|
|
assert(mapped.len >= map_bytes);
|
2024-03-18 22:39:59 -07:00
|
|
|
|
errdefer posix.munmap(mapped);
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
// map everything but the guard page as read/write
|
2024-03-18 22:39:59 -07:00
|
|
|
|
posix.mprotect(
|
2023-06-22 18:46:56 +01:00
|
|
|
|
@alignCast(mapped[guard_offset..]),
|
2024-03-18 22:39:59 -07:00
|
|
|
|
posix.PROT.READ | posix.PROT.WRITE,
|
2021-01-14 20:41:37 -07:00
|
|
|
|
) catch |err| switch (err) {
|
|
|
|
|
|
error.AccessDenied => unreachable,
|
|
|
|
|
|
else => |e| return e,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2022-10-07 21:24:44 +03:30
|
|
|
|
// Prepare the TLS segment and prepare a user_desc struct when needed on x86
|
2024-07-23 00:57:25 +02:00
|
|
|
|
var tls_ptr = linux.tls.prepareArea(mapped[tls_offset..]);
|
2024-03-18 22:39:59 -07:00
|
|
|
|
var user_desc: if (target.cpu.arch == .x86) linux.user_desc else void = undefined;
|
2022-10-07 21:24:44 +03:30
|
|
|
|
if (target.cpu.arch == .x86) {
|
2023-06-15 13:14:16 +06:00
|
|
|
|
defer tls_ptr = @intFromPtr(&user_desc);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
user_desc = .{
|
2024-07-23 00:57:25 +02:00
|
|
|
|
.entry_number = linux.tls.area_desc.gdt_entry_number,
|
2021-06-28 14:17:16 -05:00
|
|
|
|
.base_addr = tls_ptr,
|
2021-06-19 17:08:56 -05:00
|
|
|
|
.limit = 0xfffff,
|
2023-07-30 03:18:10 -04:00
|
|
|
|
.flags = .{
|
|
|
|
|
|
.seg_32bit = 1,
|
|
|
|
|
|
.contents = 0, // Data
|
|
|
|
|
|
.read_exec_only = 0,
|
|
|
|
|
|
.limit_in_pages = 1,
|
|
|
|
|
|
.seg_not_present = 0,
|
|
|
|
|
|
.useable = 1,
|
|
|
|
|
|
},
|
2021-06-19 17:08:56 -05:00
|
|
|
|
};
|
|
|
|
|
|
}
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2023-06-22 18:46:56 +01:00
|
|
|
|
const instance: *Instance = @ptrCast(@alignCast(&mapped[instance_offset]));
|
2021-06-19 17:08:56 -05:00
|
|
|
|
instance.* = .{
|
|
|
|
|
|
.fn_args = args,
|
2021-06-20 09:56:30 -05:00
|
|
|
|
.thread = .{ .mapped = mapped },
|
2021-06-19 17:08:56 -05:00
|
|
|
|
};
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2021-08-25 00:00:39 -07:00
|
|
|
|
const flags: u32 = linux.CLONE.THREAD | linux.CLONE.DETACHED |
|
|
|
|
|
|
linux.CLONE.VM | linux.CLONE.FS | linux.CLONE.FILES |
|
|
|
|
|
|
linux.CLONE.PARENT_SETTID | linux.CLONE.CHILD_CLEARTID |
|
|
|
|
|
|
linux.CLONE.SIGHAND | linux.CLONE.SYSVSEM | linux.CLONE.SETTLS;
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2024-03-18 22:39:59 -07:00
|
|
|
|
switch (linux.E.init(linux.clone(
|
2021-06-19 17:08:56 -05:00
|
|
|
|
Instance.entryFn,
|
2023-06-15 13:14:16 +06:00
|
|
|
|
@intFromPtr(&mapped[stack_offset]),
|
2021-01-14 20:41:37 -07:00
|
|
|
|
flags,
|
2023-06-15 13:14:16 +06:00
|
|
|
|
@intFromPtr(instance),
|
2021-06-19 17:08:56 -05:00
|
|
|
|
&instance.thread.parent_tid,
|
|
|
|
|
|
tls_ptr,
|
2023-11-22 18:49:18 -07:00
|
|
|
|
&instance.thread.child_tid.raw,
|
2021-06-19 17:08:56 -05:00
|
|
|
|
))) {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
.SUCCESS => return Impl{ .thread = &instance.thread },
|
|
|
|
|
|
.AGAIN => return error.ThreadQuotaExceeded,
|
|
|
|
|
|
.INVAL => unreachable,
|
|
|
|
|
|
.NOMEM => return error.SystemResources,
|
|
|
|
|
|
.NOSPC => unreachable,
|
|
|
|
|
|
.PERM => unreachable,
|
|
|
|
|
|
.USERS => unreachable,
|
2024-03-18 22:39:59 -07:00
|
|
|
|
else => |err| return posix.unexpectedErrno(err),
|
2021-06-25 12:59:28 -05:00
|
|
|
|
}
|
2021-01-14 20:41:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
fn getHandle(self: Impl) ThreadHandle {
|
|
|
|
|
|
return self.thread.parent_tid;
|
|
|
|
|
|
}
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
fn detach(self: Impl) void {
|
2024-02-18 21:52:23 -08:00
|
|
|
|
switch (self.thread.completion.swap(.detached, .seq_cst)) {
|
2021-06-19 17:08:56 -05:00
|
|
|
|
.running => {},
|
|
|
|
|
|
.completed => self.join(),
|
|
|
|
|
|
.detached => unreachable,
|
|
|
|
|
|
}
|
2021-01-17 13:29:00 -06:00
|
|
|
|
}
|
2021-01-14 20:41:37 -07:00
|
|
|
|
|
2021-06-19 17:08:56 -05:00
|
|
|
|
fn join(self: Impl) void {
|
2024-03-18 22:39:59 -07:00
|
|
|
|
defer posix.munmap(self.thread.mapped);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
|
|
|
|
|
|
var spin: u8 = 10;
|
|
|
|
|
|
while (true) {
|
2024-02-18 21:52:23 -08:00
|
|
|
|
const tid = self.thread.child_tid.load(.seq_cst);
|
2021-06-19 17:08:56 -05:00
|
|
|
|
if (tid == 0) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (spin > 0) {
|
|
|
|
|
|
spin -= 1;
|
|
|
|
|
|
std.atomic.spinLoopHint();
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-18 22:39:59 -07:00
|
|
|
|
switch (linux.E.init(linux.futex_wait(
|
2023-11-22 18:49:18 -07:00
|
|
|
|
&self.thread.child_tid.raw,
|
2021-08-25 00:00:39 -07:00
|
|
|
|
linux.FUTEX.WAIT,
|
2021-06-19 17:08:56 -05:00
|
|
|
|
tid,
|
|
|
|
|
|
null,
|
|
|
|
|
|
))) {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
.SUCCESS => continue,
|
|
|
|
|
|
.INTR => continue,
|
|
|
|
|
|
.AGAIN => continue,
|
2021-06-19 17:08:56 -05:00
|
|
|
|
else => unreachable,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-01-14 20:41:37 -07:00
|
|
|
|
}
|
2021-06-28 11:27:23 -05:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-04-21 10:59:36 +02:00
|
|
|
|
fn testThreadName(thread: *Thread) !void {
|
|
|
|
|
|
const testCases = &[_][]const u8{
|
|
|
|
|
|
"mythread",
|
|
|
|
|
|
"b" ** max_name_len,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
inline for (testCases) |tc| {
|
|
|
|
|
|
try thread.setName(tc);
|
|
|
|
|
|
|
|
|
|
|
|
var name_buffer: [max_name_len:0]u8 = undefined;
|
|
|
|
|
|
|
|
|
|
|
|
const name = try thread.getName(&name_buffer);
|
|
|
|
|
|
if (name) |value| {
|
|
|
|
|
|
try std.testing.expectEqual(tc.len, value.len);
|
|
|
|
|
|
try std.testing.expectEqualStrings(tc, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
test "setName, getName" {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
if (builtin.single_threaded) return error.SkipZigTest;
|
2021-04-21 10:59:36 +02:00
|
|
|
|
|
|
|
|
|
|
const Context = struct {
|
2022-04-26 16:48:56 -05:00
|
|
|
|
start_wait_event: ResetEvent = .{},
|
|
|
|
|
|
test_done_event: ResetEvent = .{},
|
|
|
|
|
|
thread_done_event: ResetEvent = .{},
|
2021-04-21 10:59:36 +02:00
|
|
|
|
|
2023-11-22 18:49:18 -07:00
|
|
|
|
done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
|
2021-04-21 10:59:36 +02:00
|
|
|
|
thread: Thread = undefined,
|
|
|
|
|
|
|
|
|
|
|
|
pub fn run(ctx: *@This()) !void {
|
|
|
|
|
|
// Wait for the main thread to have set the thread field in the context.
|
|
|
|
|
|
ctx.start_wait_event.wait();
|
|
|
|
|
|
|
2024-03-18 22:39:59 -07:00
|
|
|
|
switch (native_os) {
|
2021-04-21 10:59:36 +02:00
|
|
|
|
.windows => testThreadName(&ctx.thread) catch |err| switch (err) {
|
|
|
|
|
|
error.Unsupported => return error.SkipZigTest,
|
|
|
|
|
|
else => return err,
|
|
|
|
|
|
},
|
|
|
|
|
|
else => try testThreadName(&ctx.thread),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Signal our test is done
|
|
|
|
|
|
ctx.test_done_event.set();
|
|
|
|
|
|
|
2022-04-26 16:48:56 -05:00
|
|
|
|
// wait for the thread to property exit
|
|
|
|
|
|
ctx.thread_done_event.wait();
|
2021-04-21 10:59:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var context = Context{};
|
|
|
|
|
|
var thread = try spawn(.{}, Context.run, .{&context});
|
2022-04-26 16:48:56 -05:00
|
|
|
|
|
2021-04-21 10:59:36 +02:00
|
|
|
|
context.thread = thread;
|
|
|
|
|
|
context.start_wait_event.set();
|
|
|
|
|
|
context.test_done_event.wait();
|
|
|
|
|
|
|
2024-03-18 22:39:59 -07:00
|
|
|
|
switch (native_os) {
|
2024-05-09 15:04:13 +02:00
|
|
|
|
.macos, .ios, .watchos, .tvos, .visionos => {
|
2021-04-21 10:59:36 +02:00
|
|
|
|
const res = thread.setName("foobar");
|
|
|
|
|
|
try std.testing.expectError(error.Unsupported, res);
|
|
|
|
|
|
},
|
|
|
|
|
|
.windows => testThreadName(&thread) catch |err| switch (err) {
|
|
|
|
|
|
error.Unsupported => return error.SkipZigTest,
|
|
|
|
|
|
else => return err,
|
|
|
|
|
|
},
|
2023-02-24 20:58:36 +01:00
|
|
|
|
else => try testThreadName(&thread),
|
2021-04-21 10:59:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-26 16:48:56 -05:00
|
|
|
|
context.thread_done_event.set();
|
2021-04-21 10:59:36 +02:00
|
|
|
|
thread.join();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-21 20:46:05 -04:00
|
|
|
|
test {
|
2021-06-28 11:27:23 -05:00
|
|
|
|
// Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint.
|
|
|
|
|
|
_ = Futex;
|
|
|
|
|
|
_ = ResetEvent;
|
|
|
|
|
|
_ = Mutex;
|
|
|
|
|
|
_ = Semaphore;
|
|
|
|
|
|
_ = Condition;
|
2023-07-26 06:19:52 -07:00
|
|
|
|
_ = RwLock;
|
2024-07-09 11:25:19 +09:00
|
|
|
|
_ = Pool;
|
2021-06-28 11:27:23 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn testIncrementNotify(value: *usize, event: *ResetEvent) void {
|
|
|
|
|
|
value.* += 1;
|
|
|
|
|
|
event.set();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-26 15:14:43 -08:00
|
|
|
|
test join {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
if (builtin.single_threaded) return error.SkipZigTest;
|
2021-06-28 11:27:23 -05:00
|
|
|
|
|
|
|
|
|
|
var value: usize = 0;
|
2022-04-26 16:48:56 -05:00
|
|
|
|
var event = ResetEvent{};
|
2021-06-28 11:27:23 -05:00
|
|
|
|
|
2021-06-30 21:49:38 -05:00
|
|
|
|
const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
|
2021-06-28 11:27:23 -05:00
|
|
|
|
thread.join();
|
|
|
|
|
|
|
|
|
|
|
|
try std.testing.expectEqual(value, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-26 15:14:43 -08:00
|
|
|
|
test detach {
|
2021-08-23 17:06:56 -07:00
|
|
|
|
if (builtin.single_threaded) return error.SkipZigTest;
|
2021-06-28 11:27:23 -05:00
|
|
|
|
|
|
|
|
|
|
var value: usize = 0;
|
2022-04-26 16:48:56 -05:00
|
|
|
|
var event = ResetEvent{};
|
2021-06-28 11:27:23 -05:00
|
|
|
|
|
2021-06-30 21:49:38 -05:00
|
|
|
|
const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
|
2021-06-28 11:27:23 -05:00
|
|
|
|
thread.detach();
|
|
|
|
|
|
|
|
|
|
|
|
event.wait();
|
|
|
|
|
|
try std.testing.expectEqual(value, 1);
|
2021-06-30 21:49:38 -05:00
|
|
|
|
}
|