2019-05-24 15:27:18 -07:00
|
|
|
|
const std = @import("std.zig");
|
2020-02-29 19:13:09 -08:00
|
|
|
|
const builtin = std.builtin;
|
2019-05-25 10:07:44 -07:00
|
|
|
|
const os = std.os;
|
2019-05-26 20:35:26 -07:00
|
|
|
|
const mem = std.mem;
|
2019-05-24 15:27:18 -07:00
|
|
|
|
const windows = std.os.windows;
|
2019-05-25 10:07:44 -07:00
|
|
|
|
const c = std.c;
|
2019-05-26 20:35:26 -07:00
|
|
|
|
const assert = std.debug.assert;
|
2019-05-24 15:27:18 -07:00
|
|
|
|
|
2020-03-18 01:35:44 -07:00
|
|
|
|
const bad_startfn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";
|
|
|
|
|
|
2019-05-24 15:27:18 -07:00
|
|
|
|
pub const Thread = struct {
|
|
|
|
|
data: Data,
|
|
|
|
|
|
2020-02-29 19:13:09 -08:00
|
|
|
|
pub const use_pthreads = std.Target.current.os.tag != .windows and builtin.link_libc;
|
2019-05-24 15:27:18 -07:00
|
|
|
|
|
|
|
|
|
/// Represents a kernel thread handle.
|
|
|
|
|
/// May be an integer or a pointer depending on the platform.
|
|
|
|
|
/// On Linux and POSIX, this is the same as Id.
|
|
|
|
|
pub const Handle = if (use_pthreads)
|
|
|
|
|
c.pthread_t
|
2020-02-29 19:13:09 -08:00
|
|
|
|
else switch (std.Target.current.os.tag) {
|
2019-05-25 10:07:44 -07:00
|
|
|
|
.linux => i32,
|
|
|
|
|
.windows => windows.HANDLE,
|
2019-12-02 12:02:17 -08:00
|
|
|
|
else => void,
|
2019-05-24 15:27:18 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Represents a unique ID per thread.
|
|
|
|
|
/// May be an integer or pointer depending on the platform.
|
|
|
|
|
/// On Linux and POSIX, this is the same as Handle.
|
2020-02-29 19:13:09 -08:00
|
|
|
|
pub const Id = switch (std.Target.current.os.tag) {
|
2019-05-25 10:07:44 -07:00
|
|
|
|
.windows => windows.DWORD,
|
2019-05-24 15:27:18 -07:00
|
|
|
|
else => Handle,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub const Data = if (use_pthreads)
|
|
|
|
|
struct {
|
|
|
|
|
handle: Thread.Handle,
|
2019-05-26 20:35:26 -07:00
|
|
|
|
memory: []align(mem.page_size) u8,
|
2019-05-24 15:27:18 -07:00
|
|
|
|
}
|
2020-02-29 19:13:09 -08:00
|
|
|
|
else switch (std.Target.current.os.tag) {
|
2019-05-25 10:07:44 -07:00
|
|
|
|
.linux => struct {
|
2019-05-24 15:27:18 -07:00
|
|
|
|
handle: Thread.Handle,
|
2019-05-26 20:35:26 -07:00
|
|
|
|
memory: []align(mem.page_size) u8,
|
2019-05-24 15:27:18 -07:00
|
|
|
|
},
|
2019-05-25 10:07:44 -07:00
|
|
|
|
.windows => struct {
|
2019-05-24 15:27:18 -07:00
|
|
|
|
handle: Thread.Handle,
|
|
|
|
|
alloc_start: *c_void,
|
|
|
|
|
heap_handle: windows.HANDLE,
|
|
|
|
|
},
|
2019-12-02 12:02:17 -08:00
|
|
|
|
else => struct {},
|
2019-05-24 15:27:18 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Returns the ID of the calling thread.
|
|
|
|
|
/// Makes a syscall every time the function is called.
|
|
|
|
|
/// On Linux and POSIX, this Id is the same as a Handle.
|
|
|
|
|
pub fn getCurrentId() Id {
|
|
|
|
|
if (use_pthreads) {
|
|
|
|
|
return c.pthread_self();
|
|
|
|
|
} else
|
2020-02-29 19:13:09 -08:00
|
|
|
|
return switch (std.Target.current.os.tag) {
|
2019-05-26 20:35:26 -07:00
|
|
|
|
.linux => os.linux.gettid(),
|
2019-05-26 22:35:58 -07:00
|
|
|
|
.windows => windows.kernel32.GetCurrentThreadId(),
|
2019-05-24 15:27:18 -07:00
|
|
|
|
else => @compileError("Unsupported OS"),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns the handle of this thread.
|
|
|
|
|
/// On Linux and POSIX, this is the same as Id.
|
|
|
|
|
/// On Linux, it is possible that the thread spawned with `spawn`
|
|
|
|
|
/// finishes executing entirely before the clone syscall completes. In this
|
|
|
|
|
/// case, this function will return 0 rather than the no-longer-existing thread's
|
|
|
|
|
/// pid.
|
|
|
|
|
pub fn handle(self: Thread) Handle {
|
|
|
|
|
return self.data.handle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn wait(self: *const Thread) void {
|
|
|
|
|
if (use_pthreads) {
|
|
|
|
|
const err = c.pthread_join(self.data.handle, null);
|
|
|
|
|
switch (err) {
|
|
|
|
|
0 => {},
|
2019-05-25 10:07:44 -07:00
|
|
|
|
os.EINVAL => unreachable,
|
|
|
|
|
os.ESRCH => unreachable,
|
|
|
|
|
os.EDEADLK => unreachable,
|
2019-05-24 15:27:18 -07:00
|
|
|
|
else => unreachable,
|
|
|
|
|
}
|
2019-05-26 20:35:26 -07:00
|
|
|
|
os.munmap(self.data.memory);
|
2020-02-29 19:13:09 -08:00
|
|
|
|
} else switch (std.Target.current.os.tag) {
|
2019-05-25 10:07:44 -07:00
|
|
|
|
.linux => {
|
2019-05-24 15:27:18 -07:00
|
|
|
|
while (true) {
|
|
|
|
|
const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst);
|
|
|
|
|
if (pid_value == 0) break;
|
2019-05-26 20:35:26 -07:00
|
|
|
|
const rc = os.linux.futex_wait(&self.data.handle, os.linux.FUTEX_WAIT, pid_value, null);
|
|
|
|
|
switch (os.linux.getErrno(rc)) {
|
2019-05-24 15:27:18 -07:00
|
|
|
|
0 => continue,
|
2019-05-25 10:07:44 -07:00
|
|
|
|
os.EINTR => continue,
|
|
|
|
|
os.EAGAIN => continue,
|
2019-05-24 15:27:18 -07:00
|
|
|
|
else => unreachable,
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-26 20:35:26 -07:00
|
|
|
|
os.munmap(self.data.memory);
|
2019-05-24 15:27:18 -07:00
|
|
|
|
},
|
2019-05-25 10:07:44 -07:00
|
|
|
|
.windows => {
|
2019-11-18 21:31:24 -08:00
|
|
|
|
windows.WaitForSingleObjectEx(self.data.handle, windows.INFINITE, false) catch unreachable;
|
2019-05-26 22:35:58 -07:00
|
|
|
|
windows.CloseHandle(self.data.handle);
|
|
|
|
|
windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start);
|
2019-05-24 15:27:18 -07:00
|
|
|
|
},
|
|
|
|
|
else => @compileError("Unsupported OS"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const SpawnError = error{
|
|
|
|
|
/// 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,
|
|
|
|
|
|
2019-05-26 20:35:26 -07:00
|
|
|
|
/// `mlockall` is enabled, and the memory needed to spawn the thread
|
|
|
|
|
/// would exceed the limit.
|
|
|
|
|
LockedMemoryLimitExceeded,
|
|
|
|
|
|
2019-05-24 15:27:18 -07:00
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// caller must call wait on the returned thread
|
2019-12-09 12:59:12 -08:00
|
|
|
|
/// fn startFn(@TypeOf(context)) T
|
2019-05-24 15:27:18 -07:00
|
|
|
|
/// where T is u8, noreturn, void, or !void
|
|
|
|
|
/// caller must call wait on the returned thread
|
|
|
|
|
pub fn spawn(context: var, comptime startFn: var) SpawnError!*Thread {
|
|
|
|
|
if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode");
|
|
|
|
|
// TODO compile-time call graph analysis to determine stack upper bound
|
|
|
|
|
// https://github.com/ziglang/zig/issues/157
|
2019-09-11 17:22:49 -07:00
|
|
|
|
const default_stack_size = 16 * 1024 * 1024;
|
2019-05-24 15:27:18 -07:00
|
|
|
|
|
2019-12-09 12:56:19 -08:00
|
|
|
|
const Context = @TypeOf(context);
|
2020-02-24 13:39:03 -08:00
|
|
|
|
comptime assert(@typeInfo(@TypeOf(startFn)).Fn.args[0].arg_type.? == Context);
|
2019-05-24 15:27:18 -07:00
|
|
|
|
|
2020-02-29 19:13:09 -08:00
|
|
|
|
if (std.Target.current.os.tag == .windows) {
|
2019-05-24 15:27:18 -07:00
|
|
|
|
const WinThread = struct {
|
|
|
|
|
const OuterContext = struct {
|
|
|
|
|
thread: Thread,
|
|
|
|
|
inner: Context,
|
|
|
|
|
};
|
2020-01-06 12:34:50 -08:00
|
|
|
|
fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD {
|
2019-05-24 15:27:18 -07:00
|
|
|
|
const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*;
|
2020-03-18 01:35:44 -07:00
|
|
|
|
|
2020-02-24 13:03:30 -08:00
|
|
|
|
switch (@typeInfo(@TypeOf(startFn).ReturnType)) {
|
2020-03-18 01:35:44 -07:00
|
|
|
|
.NoReturn => {
|
|
|
|
|
startFn(arg);
|
2019-05-24 15:27:18 -07:00
|
|
|
|
},
|
2019-05-25 10:07:44 -07:00
|
|
|
|
.Void => {
|
2019-05-24 15:27:18 -07:00
|
|
|
|
startFn(arg);
|
|
|
|
|
return 0;
|
|
|
|
|
},
|
2020-03-18 01:35:44 -07:00
|
|
|
|
.Int => |info| {
|
|
|
|
|
if (info.bits != 8) {
|
|
|
|
|
@compileError(bad_startfn_ret);
|
|
|
|
|
}
|
|
|
|
|
return startFn(arg);
|
|
|
|
|
},
|
|
|
|
|
.ErrorUnion => |info| {
|
|
|
|
|
if (info.payload != void) {
|
|
|
|
|
@compileError(bad_startfn_ret);
|
|
|
|
|
}
|
|
|
|
|
startFn(arg) catch |err| {
|
|
|
|
|
std.debug.warn("error: {}\n", .{@errorName(err)});
|
|
|
|
|
if (@errorReturnTrace()) |trace| {
|
|
|
|
|
std.debug.dumpStackTrace(trace.*);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return 0;
|
|
|
|
|
},
|
|
|
|
|
else => @compileError(bad_startfn_ret),
|
2019-05-24 15:27:18 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2019-05-26 22:35:58 -07:00
|
|
|
|
const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory;
|
2019-05-24 15:27:18 -07:00
|
|
|
|
const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext);
|
2019-05-26 22:35:58 -07:00
|
|
|
|
const bytes_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory;
|
|
|
|
|
errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, bytes_ptr) != 0);
|
2019-05-24 15:27:18 -07:00
|
|
|
|
const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count];
|
|
|
|
|
const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable;
|
|
|
|
|
outer_context.* = WinThread.OuterContext{
|
|
|
|
|
.thread = Thread{
|
|
|
|
|
.data = Thread.Data{
|
|
|
|
|
.heap_handle = heap_handle,
|
|
|
|
|
.alloc_start = bytes_ptr,
|
|
|
|
|
.handle = undefined,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
.inner = context,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner);
|
2019-05-26 22:35:58 -07:00
|
|
|
|
outer_context.thread.data.handle = windows.kernel32.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse {
|
|
|
|
|
switch (windows.kernel32.GetLastError()) {
|
|
|
|
|
else => |err| return windows.unexpectedError(err),
|
2019-05-24 15:27:18 -07:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return &outer_context.thread;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const MainFuncs = struct {
|
2020-01-06 12:34:50 -08:00
|
|
|
|
fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 {
|
2019-05-24 15:27:18 -07:00
|
|
|
|
const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*;
|
|
|
|
|
|
2020-02-24 13:03:30 -08:00
|
|
|
|
switch (@typeInfo(@TypeOf(startFn).ReturnType)) {
|
2020-03-18 01:35:44 -07:00
|
|
|
|
.NoReturn => {
|
|
|
|
|
startFn(arg);
|
2019-05-24 15:27:18 -07:00
|
|
|
|
},
|
2019-05-25 10:07:44 -07:00
|
|
|
|
.Void => {
|
2019-05-24 15:27:18 -07:00
|
|
|
|
startFn(arg);
|
|
|
|
|
return 0;
|
|
|
|
|
},
|
2020-03-18 01:35:44 -07:00
|
|
|
|
.Int => |info| {
|
|
|
|
|
if (info.bits != 8) {
|
|
|
|
|
@compileError(bad_startfn_ret);
|
|
|
|
|
}
|
|
|
|
|
return startFn(arg);
|
|
|
|
|
},
|
|
|
|
|
.ErrorUnion => |info| {
|
|
|
|
|
if (info.payload != void) {
|
|
|
|
|
@compileError(bad_startfn_ret);
|
|
|
|
|
}
|
|
|
|
|
startFn(arg) catch |err| {
|
|
|
|
|
std.debug.warn("error: {}\n", .{@errorName(err)});
|
|
|
|
|
if (@errorReturnTrace()) |trace| {
|
|
|
|
|
std.debug.dumpStackTrace(trace.*);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return 0;
|
|
|
|
|
},
|
|
|
|
|
else => @compileError(bad_startfn_ret),
|
2019-05-24 15:27:18 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-01-06 12:34:50 -08:00
|
|
|
|
fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void {
|
2020-04-20 08:18:50 -07:00
|
|
|
|
const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*;
|
|
|
|
|
|
|
|
|
|
switch (@typeInfo(@TypeOf(startFn).ReturnType)) {
|
|
|
|
|
.NoReturn => {
|
|
|
|
|
startFn(arg);
|
|
|
|
|
},
|
|
|
|
|
.Void => {
|
|
|
|
|
startFn(arg);
|
|
|
|
|
return null;
|
|
|
|
|
},
|
|
|
|
|
.Int => |info| {
|
|
|
|
|
if (info.bits != 8) {
|
|
|
|
|
@compileError(bad_startfn_ret);
|
|
|
|
|
}
|
|
|
|
|
// pthreads don't support exit status, ignore value
|
|
|
|
|
_ = startFn(arg);
|
|
|
|
|
return null;
|
|
|
|
|
},
|
|
|
|
|
.ErrorUnion => |info| {
|
|
|
|
|
if (info.payload != void) {
|
|
|
|
|
@compileError(bad_startfn_ret);
|
|
|
|
|
}
|
|
|
|
|
startFn(arg) catch |err| {
|
|
|
|
|
std.debug.warn("error: {}\n", .{@errorName(err)});
|
|
|
|
|
if (@errorReturnTrace()) |trace| {
|
|
|
|
|
std.debug.dumpStackTrace(trace.*);
|
|
|
|
|
}
|
|
|
|
|
};
|
2020-05-05 01:14:11 -07:00
|
|
|
|
return null;
|
2020-04-20 08:18:50 -07:00
|
|
|
|
},
|
|
|
|
|
else => @compileError(bad_startfn_ret),
|
2019-05-24 15:27:18 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2019-05-28 09:26:38 -07:00
|
|
|
|
var guard_end_offset: usize = undefined;
|
2019-05-24 15:27:18 -07:00
|
|
|
|
var stack_end_offset: usize = undefined;
|
|
|
|
|
var thread_start_offset: usize = undefined;
|
|
|
|
|
var context_start_offset: usize = undefined;
|
|
|
|
|
var tls_start_offset: usize = undefined;
|
|
|
|
|
const mmap_len = blk: {
|
2019-05-28 09:26:38 -07:00
|
|
|
|
var l: usize = mem.page_size;
|
|
|
|
|
// Allocate a guard page right after the end of the stack region
|
|
|
|
|
guard_end_offset = l;
|
|
|
|
|
// The stack itself, which grows downwards.
|
|
|
|
|
l = mem.alignForward(l + default_stack_size, mem.page_size);
|
2019-05-24 15:27:18 -07:00
|
|
|
|
stack_end_offset = l;
|
|
|
|
|
// Above the stack, so that it can be in the same mmap call, put the Thread object.
|
|
|
|
|
l = mem.alignForward(l, @alignOf(Thread));
|
|
|
|
|
thread_start_offset = l;
|
|
|
|
|
l += @sizeOf(Thread);
|
|
|
|
|
// Next, the Context object.
|
|
|
|
|
if (@sizeOf(Context) != 0) {
|
|
|
|
|
l = mem.alignForward(l, @alignOf(Context));
|
|
|
|
|
context_start_offset = l;
|
|
|
|
|
l += @sizeOf(Context);
|
|
|
|
|
}
|
|
|
|
|
// Finally, the Thread Local Storage, if any.
|
|
|
|
|
if (!Thread.use_pthreads) {
|
2020-03-25 04:08:50 -07:00
|
|
|
|
l = mem.alignForward(l, os.linux.tls.tls_image.alloc_align);
|
|
|
|
|
tls_start_offset = l;
|
|
|
|
|
l += os.linux.tls.tls_image.alloc_size;
|
2019-05-24 15:27:18 -07:00
|
|
|
|
}
|
2020-03-23 11:50:29 -07:00
|
|
|
|
// Round the size to the page size.
|
|
|
|
|
break :blk mem.alignForward(l, mem.page_size);
|
2019-05-24 15:27:18 -07:00
|
|
|
|
};
|
2019-05-28 09:26:38 -07:00
|
|
|
|
|
2020-03-23 11:50:29 -07:00
|
|
|
|
const mmap_slice = mem: {
|
|
|
|
|
if (std.Target.current.os.tag != .netbsd) {
|
|
|
|
|
// Map the whole stack with no rw permissions to avoid
|
|
|
|
|
// committing the whole region right away
|
|
|
|
|
const mmap_slice = os.mmap(
|
|
|
|
|
null,
|
|
|
|
|
mmap_len,
|
|
|
|
|
os.PROT_NONE,
|
|
|
|
|
os.MAP_PRIVATE | os.MAP_ANONYMOUS,
|
|
|
|
|
-1,
|
|
|
|
|
0,
|
|
|
|
|
) catch |err| switch (err) {
|
|
|
|
|
error.MemoryMappingNotSupported => unreachable,
|
|
|
|
|
error.AccessDenied => unreachable,
|
|
|
|
|
error.PermissionDenied => unreachable,
|
|
|
|
|
else => |e| return e,
|
|
|
|
|
};
|
|
|
|
|
errdefer os.munmap(mmap_slice);
|
|
|
|
|
|
|
|
|
|
// Map everything but the guard page as rw
|
|
|
|
|
os.mprotect(
|
|
|
|
|
mmap_slice[guard_end_offset..],
|
|
|
|
|
os.PROT_READ | os.PROT_WRITE,
|
|
|
|
|
) catch |err| switch (err) {
|
|
|
|
|
error.AccessDenied => unreachable,
|
|
|
|
|
else => |e| return e,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
break :mem mmap_slice;
|
|
|
|
|
} else {
|
|
|
|
|
// NetBSD mprotect is very strict and doesn't allow to "upgrade"
|
|
|
|
|
// a PROT_NONE mapping to a RW one so let's allocate everything
|
|
|
|
|
// right away
|
|
|
|
|
const mmap_slice = os.mmap(
|
|
|
|
|
null,
|
|
|
|
|
mmap_len,
|
|
|
|
|
os.PROT_READ | os.PROT_WRITE,
|
|
|
|
|
os.MAP_PRIVATE | os.MAP_ANONYMOUS,
|
|
|
|
|
-1,
|
|
|
|
|
0,
|
|
|
|
|
) catch |err| switch (err) {
|
|
|
|
|
error.MemoryMappingNotSupported => unreachable,
|
|
|
|
|
error.AccessDenied => unreachable,
|
|
|
|
|
error.PermissionDenied => unreachable,
|
|
|
|
|
else => |e| return e,
|
|
|
|
|
};
|
|
|
|
|
errdefer os.munmap(mmap_slice);
|
|
|
|
|
|
|
|
|
|
// Remap the guard page with no permissions
|
|
|
|
|
os.mprotect(
|
|
|
|
|
mmap_slice[0..guard_end_offset],
|
|
|
|
|
os.PROT_NONE,
|
|
|
|
|
) catch |err| switch (err) {
|
|
|
|
|
error.AccessDenied => unreachable,
|
|
|
|
|
else => |e| return e,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
break :mem mmap_slice;
|
|
|
|
|
}
|
2019-05-28 09:26:38 -07:00
|
|
|
|
};
|
|
|
|
|
|
2019-05-26 20:35:26 -07:00
|
|
|
|
const mmap_addr = @ptrToInt(mmap_slice.ptr);
|
2019-05-24 15:27:18 -07:00
|
|
|
|
|
|
|
|
|
const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset));
|
2019-05-26 20:35:26 -07:00
|
|
|
|
thread_ptr.data.memory = mmap_slice;
|
2019-05-24 15:27:18 -07:00
|
|
|
|
|
|
|
|
|
var arg: usize = undefined;
|
|
|
|
|
if (@sizeOf(Context) != 0) {
|
|
|
|
|
arg = mmap_addr + context_start_offset;
|
|
|
|
|
const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg));
|
|
|
|
|
context_ptr.* = context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Thread.use_pthreads) {
|
|
|
|
|
// use pthreads
|
|
|
|
|
var attr: c.pthread_attr_t = undefined;
|
|
|
|
|
if (c.pthread_attr_init(&attr) != 0) return error.SystemResources;
|
|
|
|
|
defer assert(c.pthread_attr_destroy(&attr) == 0);
|
|
|
|
|
|
2020-03-23 12:12:10 -07:00
|
|
|
|
// Tell pthread where the effective stack start is and its size
|
|
|
|
|
assert(c.pthread_attr_setstack(
|
|
|
|
|
&attr,
|
|
|
|
|
mmap_slice.ptr + guard_end_offset,
|
|
|
|
|
stack_end_offset - guard_end_offset,
|
|
|
|
|
) == 0);
|
2020-03-23 15:26:34 -07:00
|
|
|
|
// Even though pthread's man pages state that the guard size is
|
|
|
|
|
// ignored when the stack address is explicitly given, on some
|
|
|
|
|
// plaforms such as NetBSD we still have to zero it to prevent
|
|
|
|
|
// random crashes in pthread_join calls
|
|
|
|
|
assert(c.pthread_attr_setguardsize(&attr, 0) == 0);
|
2019-05-24 15:27:18 -07:00
|
|
|
|
|
|
|
|
|
const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(*c_void, arg));
|
|
|
|
|
switch (err) {
|
|
|
|
|
0 => return thread_ptr,
|
2019-05-25 10:07:44 -07:00
|
|
|
|
os.EAGAIN => return error.SystemResources,
|
|
|
|
|
os.EPERM => unreachable,
|
|
|
|
|
os.EINVAL => unreachable,
|
|
|
|
|
else => return os.unexpectedErrno(@intCast(usize, err)),
|
2019-05-24 15:27:18 -07:00
|
|
|
|
}
|
2020-02-29 19:13:09 -08:00
|
|
|
|
} else if (std.Target.current.os.tag == .linux) {
|
2020-03-25 04:08:50 -07:00
|
|
|
|
const flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES |
|
|
|
|
|
os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM |
|
|
|
|
|
os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID |
|
|
|
|
|
os.CLONE_DETACHED | os.CLONE_SETTLS;
|
2019-11-30 07:13:33 -08:00
|
|
|
|
// This structure is only needed when targeting i386
|
2020-02-29 19:13:09 -08:00
|
|
|
|
var user_desc: if (std.Target.current.cpu.arch == .i386) os.linux.user_desc else void = undefined;
|
2019-11-30 07:13:33 -08:00
|
|
|
|
|
2020-03-25 04:08:50 -07:00
|
|
|
|
const tls_area = mmap_slice[tls_start_offset..];
|
|
|
|
|
const tp_value = os.linux.tls.prepareTLS(tls_area);
|
|
|
|
|
|
|
|
|
|
const newtls = blk: {
|
2020-02-29 19:13:09 -08:00
|
|
|
|
if (std.Target.current.cpu.arch == .i386) {
|
2019-11-30 07:13:33 -08:00
|
|
|
|
user_desc = os.linux.user_desc{
|
2020-03-25 04:08:50 -07:00
|
|
|
|
.entry_number = os.linux.tls.tls_image.gdt_entry_number,
|
|
|
|
|
.base_addr = tp_value,
|
2019-11-30 07:13:33 -08:00
|
|
|
|
.limit = 0xfffff,
|
|
|
|
|
.seg_32bit = 1,
|
|
|
|
|
.contents = 0, // Data
|
|
|
|
|
.read_exec_only = 0,
|
|
|
|
|
.limit_in_pages = 1,
|
|
|
|
|
.seg_not_present = 0,
|
|
|
|
|
.useable = 1,
|
|
|
|
|
};
|
2020-03-25 04:08:50 -07:00
|
|
|
|
break :blk @ptrToInt(&user_desc);
|
2019-11-30 07:13:33 -08:00
|
|
|
|
} else {
|
2020-03-25 04:08:50 -07:00
|
|
|
|
break :blk tp_value;
|
2019-11-30 07:13:33 -08:00
|
|
|
|
}
|
2020-03-25 04:08:50 -07:00
|
|
|
|
};
|
2019-11-30 07:13:33 -08:00
|
|
|
|
|
|
|
|
|
const rc = os.linux.clone(
|
|
|
|
|
MainFuncs.linuxThreadMain,
|
|
|
|
|
mmap_addr + stack_end_offset,
|
|
|
|
|
flags,
|
|
|
|
|
arg,
|
|
|
|
|
&thread_ptr.data.handle,
|
|
|
|
|
newtls,
|
|
|
|
|
&thread_ptr.data.handle,
|
|
|
|
|
);
|
2019-05-25 10:07:44 -07:00
|
|
|
|
switch (os.errno(rc)) {
|
2019-05-24 15:27:18 -07:00
|
|
|
|
0 => return thread_ptr,
|
2019-05-25 10:07:44 -07:00
|
|
|
|
os.EAGAIN => return error.ThreadQuotaExceeded,
|
|
|
|
|
os.EINVAL => unreachable,
|
|
|
|
|
os.ENOMEM => return error.SystemResources,
|
|
|
|
|
os.ENOSPC => unreachable,
|
|
|
|
|
os.EPERM => unreachable,
|
|
|
|
|
os.EUSERS => unreachable,
|
|
|
|
|
else => |err| return os.unexpectedErrno(err),
|
2019-05-24 15:27:18 -07:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
@compileError("Unsupported OS");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const CpuCountError = error{
|
|
|
|
|
PermissionDenied,
|
2019-05-27 11:12:50 -07:00
|
|
|
|
SystemResources,
|
2019-05-24 15:27:18 -07:00
|
|
|
|
Unexpected,
|
|
|
|
|
};
|
|
|
|
|
|
2019-05-25 10:07:44 -07:00
|
|
|
|
pub fn cpuCount() CpuCountError!usize {
|
2020-02-29 19:13:09 -08:00
|
|
|
|
if (std.Target.current.os.tag == .linux) {
|
2019-05-25 10:07:44 -07:00
|
|
|
|
const cpu_set = try os.sched_getaffinity(0);
|
2019-11-06 20:25:57 -08:00
|
|
|
|
return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast
|
2019-05-25 10:07:44 -07:00
|
|
|
|
}
|
2020-02-29 19:13:09 -08:00
|
|
|
|
if (std.Target.current.os.tag == .windows) {
|
|
|
|
|
return os.windows.peb().NumberOfProcessors;
|
2019-05-24 15:27:18 -07:00
|
|
|
|
}
|
2019-05-25 10:07:44 -07:00
|
|
|
|
var count: c_int = undefined;
|
|
|
|
|
var count_len: usize = @sizeOf(c_int);
|
2019-11-19 17:29:08 -08:00
|
|
|
|
const name = if (comptime std.Target.current.isDarwin()) "hw.logicalcpu" else "hw.ncpu";
|
2020-03-30 11:23:22 -07:00
|
|
|
|
os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) {
|
2020-02-29 14:11:36 -08:00
|
|
|
|
error.NameTooLong, error.UnknownName => unreachable,
|
2019-05-27 14:28:59 -07:00
|
|
|
|
else => |e| return e,
|
|
|
|
|
};
|
2019-05-25 10:07:44 -07:00
|
|
|
|
return @intCast(usize, count);
|
2019-05-24 15:27:18 -07:00
|
|
|
|
}
|
|
|
|
|
};
|