const std = @import("std.zig");
const builtin = @import("builtin");
const math = std.math;
const os = std.os;
const assert = std.debug.assert;
const target = builtin.target;
const Atomic = std.atomic.Atomic;
pub const Futex = @import("Thread/Futex.zig");
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");
pub const RwLock = @import("Thread/RwLock.zig");
pub const use_pthreads = target.os.tag != .windows and target.os.tag != .wasi and builtin.link_libc;
const is_gnu = target.abi.isGnu();
const Thread = @This();
const Impl = if (target.os.tag == .windows)
    WindowsThreadImpl
else if (use_pthreads)
    PosixThreadImpl
else if (target.os.tag == .linux)
    LinuxThreadImpl
else
    UnsupportedImpl;
impl: Impl,
pub const max_name_len = switch (target.os.tag) {
    .linux => 15,
    .windows => 31,
    .macos, .ios, .watchos, .tvos => 63,
    .netbsd => 31,
    .freebsd => 15,
    .openbsd => 31,
    .dragonfly => 1023,
    .solaris => 31,
    else => 0,
};
pub const SetNameError = error{
    NameTooLong,
    Unsupported,
    Unexpected,
} || os.PrctlError || os.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError;
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;
        std.mem.copy(u8, &name_buf, name);
        name_buf[name.len] = 0;
        break :blk name_buf[0..name.len :0];
    };
    switch (target.os.tag) {
        .linux => if (use_pthreads) {
            const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr);
            switch (err) {
                .SUCCESS => return,
                .RANGE => unreachable,
                else => |e| return os.unexpectedErrno(e),
            }
        } else if (use_pthreads and self.getHandle() == std.c.pthread_self()) {
            
            const err = try os.prctl(.SET_NAME, .{@ptrToInt(name_with_terminator.ptr)});
            switch (@intToEnum(os.E, err)) {
                .SUCCESS => return,
                else => |e| return os.unexpectedErrno(e),
            }
        } else {
            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, .{ .mode = .write_only });
            defer file.close();
            try file.writer().writeAll(name);
            return;
        },
        .windows => {
            var buf: [max_name_len]u16 = undefined;
            const len = try std.unicode.utf8ToUtf16Le(&buf, name);
            const byte_len = math.cast(c_ushort, len * 2) orelse return error.NameTooLong;
            
            const unicode_string = os.windows.UNICODE_STRING{
                .Length = byte_len,
                .MaximumLength = byte_len,
                .Buffer = &buf,
            };
            switch (os.windows.ntdll.NtSetInformationThread(
                self.getHandle(),
                .ThreadNameInformation,
                &unicode_string,
                @sizeOf(os.windows.UNICODE_STRING),
            )) {
                .SUCCESS => return,
                .NOT_IMPLEMENTED => return error.Unsupported,
                else => |err| return os.windows.unexpectedStatus(err),
            }
        },
        .macos, .ios, .watchos, .tvos => if (use_pthreads) {
            
            if (self.getHandle() != std.c.pthread_self()) return error.Unsupported;
            const err = std.c.pthread_setname_np(name_with_terminator.ptr);
            switch (err) {
                .SUCCESS => return,
                else => |e| return os.unexpectedErrno(e),
            }
        },
        .netbsd, .solaris => if (use_pthreads) {
            const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr, null);
            switch (err) {
                .SUCCESS => return,
                .INVAL => unreachable,
                .SRCH => unreachable,
                .NOMEM => unreachable,
                else => |e| return os.unexpectedErrno(e),
            }
        },
        .freebsd, .openbsd => if (use_pthreads) {
            
            
            
            std.c.pthread_set_name_np(self.getHandle(), name_with_terminator.ptr);
            return;
        },
        .dragonfly => if (use_pthreads) {
            const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr);
            switch (err) {
                .SUCCESS => return,
                .INVAL => unreachable,
                .FAULT => unreachable,
                .NAMETOOLONG => unreachable, 
                .SRCH => unreachable,
                else => |e| return os.unexpectedErrno(e),
            }
        },
        else => {},
    }
    return error.Unsupported;
}
pub const GetNameError = error{
    
    CodepointTooLarge,
    Utf8CannotEncodeSurrogateHalf,
    DanglingSurrogateHalf,
    ExpectedSecondSurrogateHalf,
    UnexpectedSecondSurrogateHalf,
    Unsupported,
    Unexpected,
} || os.PrctlError || os.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError;
pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 {
    buffer_ptr[max_name_len] = 0;
    var buffer = std.mem.span(buffer_ptr);
    switch (target.os.tag) {
        .linux => if (use_pthreads and is_gnu) {
            const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
            switch (err) {
                .SUCCESS => return std.mem.sliceTo(buffer, 0),
                .RANGE => unreachable,
                else => |e| return os.unexpectedErrno(e),
            }
        } else if (use_pthreads and self.getHandle() == std.c.pthread_self()) {
            const err = try os.prctl(.GET_NAME, .{@ptrToInt(buffer.ptr)});
            switch (@intToEnum(os.E, err)) {
                .SUCCESS => return std.mem.sliceTo(buffer, 0),
                else => |e| return os.unexpectedErrno(e),
            }
        } else if (!use_pthreads) {
            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;
        } else {
            
            return error.Unsupported;
        },
        .windows => {
            const buf_capacity = @sizeOf(os.windows.UNICODE_STRING) + (@sizeOf(u16) * max_name_len);
            var buf: [buf_capacity]u8 align(@alignOf(os.windows.UNICODE_STRING)) = undefined;
            switch (os.windows.ntdll.NtQueryInformationThread(
                self.getHandle(),
                .ThreadNameInformation,
                &buf,
                buf_capacity,
                null,
            )) {
                .SUCCESS => {
                    const string = @ptrCast(*const os.windows.UNICODE_STRING, &buf);
                    const len = try std.unicode.utf16leToUtf8(buffer, string.Buffer[0 .. string.Length / 2]);
                    return if (len > 0) buffer[0..len] else null;
                },
                .NOT_IMPLEMENTED => return error.Unsupported,
                else => |err| return os.windows.unexpectedStatus(err),
            }
        },
        .macos, .ios, .watchos, .tvos => if (use_pthreads) {
            const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
            switch (err) {
                .SUCCESS => return std.mem.sliceTo(buffer, 0),
                .SRCH => unreachable,
                else => |e| return os.unexpectedErrno(e),
            }
        },
        .netbsd, .solaris => if (use_pthreads) {
            const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
            switch (err) {
                .SUCCESS => return std.mem.sliceTo(buffer, 0),
                .INVAL => unreachable,
                .SRCH => unreachable,
                else => |e| return os.unexpectedErrno(e),
            }
        },
        .freebsd, .openbsd => if (use_pthreads) {
            
            
            std.c.pthread_get_name_np(self.getHandle(), buffer.ptr, max_name_len + 1);
            return std.mem.sliceTo(buffer, 0);
        },
        .dragonfly => if (use_pthreads) {
            const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
            switch (err) {
                .SUCCESS => return std.mem.sliceTo(buffer, 0),
                .INVAL => unreachable,
                .FAULT => unreachable,
                .SRCH => unreachable,
                else => |e| return os.unexpectedErrno(e),
            }
        },
        else => {},
    }
    return error.Unsupported;
}
pub const Id = u64;
pub fn getCurrentId() Id {
    return Impl.getCurrentId();
}
pub const CpuCountError = error{
    PermissionDenied,
    SystemResources,
    Unexpected,
};
pub fn getCpuCount() CpuCountError!usize {
    return Impl.getCpuCount();
}
pub const SpawnConfig = struct {
    
    
    
    stack_size: usize = 16 * 1024 * 1024,
};
pub const SpawnError = error{
    
    
    
    
    
    
    
    
    
    
    
    
    ThreadQuotaExceeded,
    
    
    
    SystemResources,
    
    OutOfMemory,
    
    
    LockedMemoryLimitExceeded,
    Unexpected,
};
pub fn spawn(config: SpawnConfig, comptime function: anytype, args: anytype) SpawnError!Thread {
    if (builtin.single_threaded) {
        @compileError("Cannot spawn thread when building in single-threaded mode");
    }
    const impl = try Impl.spawn(config, function, args);
    return Thread{ .impl = impl };
}
pub const Handle = Impl.ThreadHandle;
pub fn getHandle(self: Thread) Handle {
    return self.impl.getHandle();
}
pub fn detach(self: Thread) void {
    return self.impl.detach();
}
pub fn join(self: Thread) void {
    return self.impl.join();
}
pub const YieldError = error{
    
    SystemCannotYield,
};
pub fn yield() YieldError!void {
    if (builtin.os.tag == .windows) {
        
        
        _ = os.windows.kernel32.SwitchToThread();
        return;
    }
    switch (os.errno(os.system.sched_yield())) {
        .SUCCESS => return,
        .NOSYS => return error.SystemCannotYield,
        else => return error.SystemCannotYield,
    }
}
const Completion = Atomic(enum(u8) {
    running,
    detached,
    completed,
});
fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
    WindowsThreadImpl => std.os.windows.DWORD,
    LinuxThreadImpl => u8,
    PosixThreadImpl => ?*anyopaque,
    else => unreachable,
} {
    const default_value = if (Impl == PosixThreadImpl) null else 0;
    const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";
    switch (@typeInfo(@typeInfo(@TypeOf(f)).Fn.return_type.?)) {
        .NoReturn => {
            @call(.{}, f, args);
        },
        .Void => {
            @call(.{}, f, args);
            return default_value;
        },
        .Int => |info| {
            if (info.bits != 8) {
                @compileError(bad_fn_ret);
            }
            const status = @call(.{}, f, args);
            if (Impl != PosixThreadImpl) {
                return status;
            }
            
            return default_value;
        },
        .ErrorUnion => |info| {
            if (info.payload != void) {
                @compileError(bad_fn_ret);
            }
            @call(.{}, 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);
        },
    }
}
const UnsupportedImpl = struct {
    pub const ThreadHandle = void;
    fn getCurrentId() u64 {
        return unsupported({});
    }
    fn getCpuCount() !usize {
        return unsupported({});
    }
    fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
        return unsupported(.{ config, f, args });
    }
    fn getHandle(self: Impl) ThreadHandle {
        return unsupported(self);
    }
    fn detach(self: Impl) void {
        return unsupported(self);
    }
    fn join(self: Impl) void {
        return unsupported(self);
    }
    fn unsupported(unusued: anytype) noreturn {
        _ = unusued;
        @compileError("Unsupported operating system " ++ @tagName(target.os.tag));
    }
};
const WindowsThreadImpl = struct {
    const windows = os.windows;
    pub const ThreadHandle = windows.HANDLE;
    fn getCurrentId() u64 {
        return windows.kernel32.GetCurrentThreadId();
    }
    fn getCpuCount() !usize {
        
        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);
            assert(status != 0);
        }
    };
    fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
        const Args = @TypeOf(args);
        const Instance = struct {
            fn_args: Args,
            thread: ThreadCompletion,
            fn entryFn(raw_ptr: windows.PVOID) callconv(.C) windows.DWORD {
                const self = @ptrCast(*@This(), @alignCast(@alignOf(@This()), raw_ptr));
                defer switch (self.thread.completion.swap(.completed, .SeqCst)) {
                    .running => {},
                    .completed => unreachable,
                    .detached => self.thread.free(),
                };
                return callFn(f, self.fn_args);
            }
        };
        const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory;
        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);
        const instance_bytes = @ptrCast([*]u8, alloc_ptr)[0..alloc_bytes];
        var fba = std.heap.FixedBufferAllocator.init(instance_bytes);
        const instance = fba.allocator().create(Instance) catch unreachable;
        instance.* = .{
            .fn_args = args,
            .thread = .{
                .completion = Completion.init(.running),
                .heap_ptr = alloc_ptr,
                .heap_handle = heap_handle,
            },
        };
        
        
        
        var stack_size = std.math.cast(u32, config.stack_size) orelse std.math.maxInt(u32);
        stack_size = std.math.max(64 * 1024, stack_size);
        instance.thread.thread_handle = windows.kernel32.CreateThread(
            null,
            stack_size,
            Instance.entryFn,
            @ptrCast(*anyopaque, instance),
            0,
            null,
        ) orelse {
            const errno = windows.kernel32.GetLastError();
            return windows.unexpectedError(errno);
        };
        return Impl{ .thread = &instance.thread };
    }
    fn getHandle(self: Impl) ThreadHandle {
        return self.thread.thread_handle;
    }
    fn detach(self: Impl) void {
        windows.CloseHandle(self.thread.thread_handle);
        switch (self.thread.completion.swap(.detached, .SeqCst)) {
            .running => {},
            .completed => self.thread.free(),
            .detached => unreachable,
        }
    }
    fn join(self: Impl) void {
        windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable;
        windows.CloseHandle(self.thread.thread_handle);
        assert(self.thread.completion.load(.SeqCst) == .completed);
        self.thread.free();
    }
};
const PosixThreadImpl = struct {
    const c = std.c;
    pub const ThreadHandle = c.pthread_t;
    fn getCurrentId() Id {
        switch (target.os.tag) {
            .linux => {
                return LinuxThreadImpl.getCurrentId();
            },
            .macos, .ios, .watchos, .tvos => {
                var thread_id: u64 = undefined;
                
                assert(c.pthread_threadid_np(null, &thread_id) == 0);
                return thread_id;
            },
            .dragonfly => {
                return @bitCast(u32, c.lwp_gettid());
            },
            .netbsd => {
                return @bitCast(u32, c._lwp_self());
            },
            .freebsd => {
                return @bitCast(u32, c.pthread_getthreadid_np());
            },
            .openbsd => {
                return @bitCast(u32, c.getthrid());
            },
            .haiku => {
                return @bitCast(u32, c.find_thread(null));
            },
            else => {
                return @ptrToInt(c.pthread_self());
            },
        }
    }
    fn getCpuCount() !usize {
        switch (target.os.tag) {
            .linux => {
                return LinuxThreadImpl.getCpuCount();
            },
            .openbsd => {
                var count: c_int = undefined;
                var count_size: usize = @sizeOf(c_int);
                const mib = [_]c_int{ os.CTL.HW, os.system.HW_NCPUONLINE };
                os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) {
                    error.NameTooLong, error.UnknownName => unreachable,
                    else => |e| return e,
                };
                return @intCast(usize, count);
            },
            .solaris => {
                
                
                
                const rc = c.sysconf(os._SC.NPROCESSORS_ONLN);
                return switch (os.errno(rc)) {
                    .SUCCESS => @intCast(usize, rc),
                    else => |err| os.unexpectedErrno(err),
                };
            },
            .haiku => {
                var system_info: os.system.system_info = undefined;
                const rc = os.system.get_system_info(&system_info); 
                return switch (os.errno(rc)) {
                    .SUCCESS => @intCast(usize, system_info.cpu_count),
                    else => |err| os.unexpectedErrno(err),
                };
            },
            else => {
                var count: c_int = undefined;
                var count_len: usize = @sizeOf(c_int);
                const name = if (comptime target.isDarwin()) "hw.logicalcpu" else "hw.ncpu";
                os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) {
                    error.NameTooLong, error.UnknownName => unreachable,
                    else => |e| return e,
                };
                return @intCast(usize, count);
            },
        }
    }
    handle: ThreadHandle,
    fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
        const Args = @TypeOf(args);
        const allocator = std.heap.c_allocator;
        const Instance = struct {
            fn entryFn(raw_arg: ?*anyopaque) callconv(.C) ?*anyopaque {
                
                if (@sizeOf(Args) < 1) {
                    return callFn(f, @as(Args, undefined));
                }
                const args_ptr = @ptrCast(*Args, @alignCast(@alignOf(Args), raw_arg));
                defer allocator.destroy(args_ptr);
                return callFn(f, args_ptr.*);
            }
        };
        const args_ptr = try allocator.create(Args);
        args_ptr.* = args;
        errdefer allocator.destroy(args_ptr);
        var attr: c.pthread_attr_t = undefined;
        if (c.pthread_attr_init(&attr) != .SUCCESS) return error.SystemResources;
        defer assert(c.pthread_attr_destroy(&attr) == .SUCCESS);
        
        const stack_size = std.math.max(config.stack_size, 16 * 1024);
        assert(c.pthread_attr_setstacksize(&attr, stack_size) == .SUCCESS);
        assert(c.pthread_attr_setguardsize(&attr, std.mem.page_size) == .SUCCESS);
        var handle: c.pthread_t = undefined;
        switch (c.pthread_create(
            &handle,
            &attr,
            Instance.entryFn,
            if (@sizeOf(Args) > 1) @ptrCast(*anyopaque, args_ptr) else undefined,
        )) {
            .SUCCESS => return Impl{ .handle = handle },
            .AGAIN => return error.SystemResources,
            .PERM => unreachable,
            .INVAL => unreachable,
            else => |err| return os.unexpectedErrno(err),
        }
    }
    fn getHandle(self: Impl) ThreadHandle {
        return self.handle;
    }
    fn detach(self: Impl) void {
        switch (c.pthread_detach(self.handle)) {
            .SUCCESS => {},
            .INVAL => unreachable, 
            .SRCH => unreachable, 
            else => unreachable,
        }
    }
    fn join(self: Impl) void {
        switch (c.pthread_join(self.handle, null)) {
            .SUCCESS => {},
            .INVAL => unreachable, 
            .SRCH => unreachable, 
            .DEADLK => unreachable, 
            else => unreachable,
        }
    }
};
const LinuxThreadImpl = struct {
    const linux = os.linux;
    pub const ThreadHandle = i32;
    threadlocal var tls_thread_id: ?Id = null;
    fn getCurrentId() Id {
        return tls_thread_id orelse {
            const tid = @bitCast(u32, linux.gettid());
            tls_thread_id = tid;
            return tid;
        };
    }
    fn getCpuCount() !usize {
        const cpu_set = try os.sched_getaffinity(0);
        
        return @as(usize, os.CPU_COUNT(cpu_set));
    }
    thread: *ThreadCompletion,
    const ThreadCompletion = struct {
        completion: Completion = Completion.init(.running),
        child_tid: Atomic(i32) = Atomic(i32).init(1),
        parent_tid: i32 = undefined,
        mapped: []align(std.mem.page_size) u8,
        
        
        
        fn freeAndExit(self: *ThreadCompletion) noreturn {
            switch (target.cpu.arch) {
                .i386 => asm volatile (
                    \\  movl $91, %%eax
                    \\  movl %[ptr], %%ebx
                    \\  movl %[len], %%ecx
                    \\  int $128
                    \\  movl $1, %%eax
                    \\  movl $0, %%ebx
                    \\  int $128
                    :
                    : [ptr] "r" (@ptrToInt(self.mapped.ptr)),
                      [len] "r" (self.mapped.len),
                    : "memory"
                ),
                .x86_64 => asm volatile (
                    \\  movq $11, %%rax
                    \\  syscall
                    \\  movq $60, %%rax
                    \\  movq $1, %%rdi
                    \\  syscall
                    :
                    : [ptr] "{rdi}" (@ptrToInt(self.mapped.ptr)),
                      [len] "{rsi}" (self.mapped.len),
                ),
                .arm, .armeb, .thumb, .thumbeb => asm volatile (
                    \\  mov r7, #91
                    \\  mov r0, %[ptr]
                    \\  mov r1, %[len]
                    \\  svc 0
                    \\  mov r7, #1
                    \\  mov r0, #0
                    \\  svc 0
                    :
                    : [ptr] "r" (@ptrToInt(self.mapped.ptr)),
                      [len] "r" (self.mapped.len),
                    : "memory"
                ),
                .aarch64, .aarch64_be, .aarch64_32 => asm volatile (
                    \\  mov x8, #215
                    \\  mov x0, %[ptr]
                    \\  mov x1, %[len]
                    \\  svc 0
                    \\  mov x8, #93
                    \\  mov x0, #0
                    \\  svc 0
                    :
                    : [ptr] "r" (@ptrToInt(self.mapped.ptr)),
                      [len] "r" (self.mapped.len),
                    : "memory"
                ),
                .mips, .mipsel => asm volatile (
                    \\  move $sp, $25
                    \\  li $2, 4091
                    \\  move $4, %[ptr]
                    \\  move $5, %[len]
                    \\  syscall
                    \\  li $2, 4001
                    \\  li $4, 0
                    \\  syscall
                    :
                    : [ptr] "r" (@ptrToInt(self.mapped.ptr)),
                      [len] "r" (self.mapped.len),
                    : "memory"
                ),
                .mips64, .mips64el => asm volatile (
                    \\  li $2, 4091
                    \\  move $4, %[ptr]
                    \\  move $5, %[len]
                    \\  syscall
                    \\  li $2, 4001
                    \\  li $4, 0
                    \\  syscall
                    :
                    : [ptr] "r" (@ptrToInt(self.mapped.ptr)),
                      [len] "r" (self.mapped.len),
                    : "memory"
                ),
                .powerpc, .powerpcle, .powerpc64, .powerpc64le => asm volatile (
                    \\  li 0, 91
                    \\  mr %[ptr], 3
                    \\  mr %[len], 4
                    \\  sc
                    \\  li 0, 1
                    \\  li 3, 0
                    \\  sc
                    \\  blr
                    :
                    : [ptr] "r" (@ptrToInt(self.mapped.ptr)),
                      [len] "r" (self.mapped.len),
                    : "memory"
                ),
                .riscv64 => asm volatile (
                    \\  li a7, 215
                    \\  mv a0, %[ptr]
                    \\  mv a1, %[len]
                    \\  ecall
                    \\  li a7, 93
                    \\  mv a0, zero
                    \\  ecall
                    :
                    : [ptr] "r" (@ptrToInt(self.mapped.ptr)),
                      [len] "r" (self.mapped.len),
                    : "memory"
                ),
                .sparc64 => asm volatile (
                    \\ # 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.
                    \\  1:
                    \\  cmp %%fp, 0
                    \\  beq 2f
                    \\  nop
                    \\  ba 1b
                    \\  restore
                    \\  2:
                    \\  mov 73, %%g1
                    \\  mov %[ptr], %%o0
                    \\  mov %[len], %%o1
                    \\  # Flush register window contents to prevent background
                    \\  # memory access before unmapping the stack.
                    \\  flushw
                    \\  t 0x6d
                    \\  mov 1, %%g1
                    \\  mov 1, %%o0
                    \\  t 0x6d
                    :
                    : [ptr] "r" (@ptrToInt(self.mapped.ptr)),
                      [len] "r" (self.mapped.len),
                    : "memory"
                ),
                else => |cpu_arch| @compileError("Unsupported linux arch: " ++ @tagName(cpu_arch)),
            }
            unreachable;
        }
    };
    fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
        const page_size = std.mem.page_size;
        const Args = @TypeOf(args);
        const Instance = struct {
            fn_args: Args,
            thread: ThreadCompletion,
            fn entryFn(raw_arg: usize) callconv(.C) u8 {
                const self = @intToPtr(*@This(), raw_arg);
                defer switch (self.thread.completion.swap(.completed, .SeqCst)) {
                    .running => {},
                    .completed => unreachable,
                    .detached => self.thread.freeAndExit(),
                };
                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: {
            var bytes: usize = page_size;
            guard_offset = bytes;
            bytes += std.math.max(page_size, config.stack_size);
            bytes = std.mem.alignForward(bytes, page_size);
            stack_offset = bytes;
            bytes = std.mem.alignForward(bytes, linux.tls.tls_image.alloc_align);
            tls_offset = bytes;
            bytes += linux.tls.tls_image.alloc_size;
            bytes = std.mem.alignForward(bytes, @alignOf(Instance));
            instance_offset = bytes;
            bytes += @sizeOf(Instance);
            bytes = std.mem.alignForward(bytes, page_size);
            break :blk bytes;
        };
        
        
        const mapped = os.mmap(
            null,
            map_bytes,
            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,
        };
        assert(mapped.len >= map_bytes);
        errdefer os.munmap(mapped);
        
        os.mprotect(
            @alignCast(page_size, mapped[guard_offset..]),
            os.PROT.READ | os.PROT.WRITE,
        ) catch |err| switch (err) {
            error.AccessDenied => unreachable,
            else => |e| return e,
        };
        
        var tls_ptr = os.linux.tls.prepareTLS(mapped[tls_offset..]);
        var user_desc: if (target.cpu.arch == .i386) os.linux.user_desc else void = undefined;
        if (target.cpu.arch == .i386) {
            defer tls_ptr = @ptrToInt(&user_desc);
            user_desc = .{
                .entry_number = os.linux.tls.tls_image.gdt_entry_number,
                .base_addr = tls_ptr,
                .limit = 0xfffff,
                .seg_32bit = 1,
                .contents = 0, 
                .read_exec_only = 0,
                .limit_in_pages = 1,
                .seg_not_present = 0,
                .useable = 1,
            };
        }
        const instance = @ptrCast(*Instance, @alignCast(@alignOf(Instance), &mapped[instance_offset]));
        instance.* = .{
            .fn_args = args,
            .thread = .{ .mapped = mapped },
        };
        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;
        switch (linux.getErrno(linux.clone(
            Instance.entryFn,
            @ptrToInt(&mapped[stack_offset]),
            flags,
            @ptrToInt(instance),
            &instance.thread.parent_tid,
            tls_ptr,
            &instance.thread.child_tid.value,
        ))) {
            .SUCCESS => return Impl{ .thread = &instance.thread },
            .AGAIN => return error.ThreadQuotaExceeded,
            .INVAL => unreachable,
            .NOMEM => return error.SystemResources,
            .NOSPC => unreachable,
            .PERM => unreachable,
            .USERS => unreachable,
            else => |err| return os.unexpectedErrno(err),
        }
    }
    fn getHandle(self: Impl) ThreadHandle {
        return self.thread.parent_tid;
    }
    fn detach(self: Impl) void {
        switch (self.thread.completion.swap(.detached, .SeqCst)) {
            .running => {},
            .completed => self.join(),
            .detached => unreachable,
        }
    }
    fn join(self: Impl) void {
        defer os.munmap(self.thread.mapped);
        var spin: u8 = 10;
        while (true) {
            const tid = self.thread.child_tid.load(.SeqCst);
            if (tid == 0) {
                break;
            }
            if (spin > 0) {
                spin -= 1;
                std.atomic.spinLoopHint();
                continue;
            }
            switch (linux.getErrno(linux.futex_wait(
                &self.thread.child_tid.value,
                linux.FUTEX.WAIT,
                tid,
                null,
            ))) {
                .SUCCESS => continue,
                .INTR => continue,
                .AGAIN => continue,
                else => unreachable,
            }
        }
    }
};
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" {
    if (builtin.single_threaded) return error.SkipZigTest;
    const Context = struct {
        start_wait_event: ResetEvent = .{},
        test_done_event: ResetEvent = .{},
        thread_done_event: ResetEvent = .{},
        done: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(false),
        thread: Thread = undefined,
        pub fn run(ctx: *@This()) !void {
            
            ctx.start_wait_event.wait();
            switch (target.os.tag) {
                .windows => testThreadName(&ctx.thread) catch |err| switch (err) {
                    error.Unsupported => return error.SkipZigTest,
                    else => return err,
                },
                else => try testThreadName(&ctx.thread),
            }
            
            ctx.test_done_event.set();
            
            ctx.thread_done_event.wait();
        }
    };
    var context = Context{};
    var thread = try spawn(.{}, Context.run, .{&context});
    context.thread = thread;
    context.start_wait_event.set();
    context.test_done_event.wait();
    switch (target.os.tag) {
        .macos, .ios, .watchos, .tvos => {
            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,
        },
        else => |tag| if (tag == .linux and use_pthreads and comptime target.abi.isMusl()) {
            try thread.setName("foobar");
            var name_buffer: [max_name_len:0]u8 = undefined;
            const res = thread.getName(&name_buffer);
            try std.testing.expectError(error.Unsupported, res);
        } else {
            try testThreadName(&thread);
        },
    }
    context.thread_done_event.set();
    thread.join();
}
test "std.Thread" {
    
    _ = Futex;
    _ = ResetEvent;
    _ = Mutex;
    _ = Semaphore;
    _ = Condition;
}
fn testIncrementNotify(value: *usize, event: *ResetEvent) void {
    value.* += 1;
    event.set();
}
test "Thread.join" {
    if (builtin.single_threaded) return error.SkipZigTest;
    var value: usize = 0;
    var event = ResetEvent{};
    const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
    thread.join();
    try std.testing.expectEqual(value, 1);
}
test "Thread.detach" {
    if (builtin.single_threaded) return error.SkipZigTest;
    var value: usize = 0;
    var event = ResetEvent{};
    const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
    thread.detach();
    event.wait();
    try std.testing.expectEqual(value, 1);
}