feature. See also
. The project being documented here (as the example) is the Zig library itself.
Thread.LinuxThreadImpl
const LinuxThreadImpl = struct
File
Code
const LinuxThreadImpl = struct {
const linux = std.os.linux;
pub const ThreadHandle = i32;
threadlocal var tls_thread_id: ?Id = null;
fn getCurrentId() Id {
return tls_thread_id orelse {
const tid: u32 = @bitCast(linux.gettid());
tls_thread_id = tid;
return tid;
};
}
fn getCpuCount() !usize {
const cpu_set = try posix.sched_getaffinity(0);
return posix.CPU_COUNT(cpu_set);
}
thread: *ThreadCompletion,
const ThreadCompletion = struct {
completion: Completion = Completion.init(.running),
child_tid: std.atomic.Value(i32) = std.atomic.Value(i32).init(1),
parent_tid: i32 = undefined,
mapped: []align(std.heap.page_size_min) u8,
fn freeAndExit(self: *ThreadCompletion) noreturn {
// value zero to that address, which is inside the block we're unmapping below, after
// our thread exits. This can sometimes corrupt memory in other mmap blocks from
// unrelated concurrent threads.
_ = linux.set_tid_address(null);
// handler would immediately segfault due to the stack being unmapped. To avoid this,
// we need to mask all signals before entering the inline asm.
posix.sigprocmask(std.posix.SIG.BLOCK, &std.os.linux.sigfillset(), null);
switch (target.cpu.arch) {
.x86 => asm volatile (
\\ movl $91, %%eax # SYS_munmap
\\ int $128
\\ movl $1, %%eax # SYS_exit
\\ movl $0, %%ebx
\\ int $128
:
: [ptr] "{ebx}" (@intFromPtr(self.mapped.ptr)),
[len] "{ecx}" (self.mapped.len),
),
.x86_64 => asm volatile (switch (target.abi) {
.gnux32, .muslx32, .x32 =>
\\ movl $0x4000000b, %%eax # SYS_munmap
\\ syscall
\\ movl $0x4000003c, %%eax # SYS_exit
\\ xor %%rdi, %%rdi
\\ syscall
,
else =>
\\ movl $11, %%eax # SYS_munmap
\\ syscall
\\ movl $60, %%eax # SYS_exit
\\ xor %%rdi, %%rdi
\\ syscall
,
}
:
: [ptr] "{rdi}" (@intFromPtr(self.mapped.ptr)),
[len] "{rsi}" (self.mapped.len),
),
.arm, .armeb, .thumb, .thumbeb => asm volatile (
\\ mov r7, #91 // SYS_munmap
\\ svc 0
\\ mov r7, #1 // SYS_exit
\\ mov r0, #0
\\ svc 0
:
: [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)),
[len] "{r1}" (self.mapped.len),
),
.aarch64, .aarch64_be => asm volatile (
\\ mov x8, #215 // SYS_munmap
\\ svc 0
\\ mov x8, #93 // SYS_exit
\\ mov x0, #0
\\ svc 0
:
: [ptr] "{x0}" (@intFromPtr(self.mapped.ptr)),
[len] "{x1}" (self.mapped.len),
),
.alpha => asm volatile (
\\ ldi $0, 73 # SYS_munmap
\\ callsys
\\ ldi $0, 1 # SYS_exit
\\ ldi $16, 0
\\ callsys
:
: [ptr] "{$16}" (@intFromPtr(self.mapped.ptr)),
[len] "{$17}" (self.mapped.len),
),
.arc, .arceb => asm volatile (
\\ mov r8, 215 # SYS_munmap
\\ trap_s 0
\\ mov r8, 93 # SYS_exit
\\ mov r0, 0
\\ trap_s 0
:
: [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)),
[len] "{r1}" (self.mapped.len),
),
.hexagon => asm volatile (
\\ r6 = #215 // SYS_munmap
\\ trap0(#1)
\\ r6 = #93 // SYS_exit
\\ r0 = #0
\\ trap0(#1)
:
: [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)),
[len] "{r1}" (self.mapped.len),
),
.hppa => asm volatile (
\\ ldi 91, %%r20 /* SYS_munmap */
\\ ble 0x100(%%sr2, %%r0)
\\ ldi 1, %%r20 /* SYS_exit */
\\ ldi 0, %%r26
\\ ble 0x100(%%sr2, %%r0)
:
: [ptr] "{r26}" (@intFromPtr(self.mapped.ptr)),
[len] "{r25}" (self.mapped.len),
),
.m68k => asm volatile (
\\ move.l #91, %%d0 // SYS_munmap
\\ trap #0
\\ move.l #1, %%d0 // SYS_exit
\\ move.l #0, %%d1
\\ trap #0
:
: [ptr] "{d1}" (@intFromPtr(self.mapped.ptr)),
[len] "{d2}" (self.mapped.len),
),
.microblaze, .microblazeel => asm volatile (
\\ ori r12, r0, 91 # SYS_munmap
\\ brki r14, 0x8
\\ ori r12, r0, 1 # SYS_exit
\\ ori r5, r0, 0
\\ brki r14, 0x8
:
: [ptr] "{r5}" (@intFromPtr(self.mapped.ptr)),
[len] "{r6}" (self.mapped.len),
),
// kernel bug that caused syscalls to return EFAULT if the stack pointer is invalid.
// The bug was introduced in 46e12c07b3b9603c60fc1d421ff18618241cb081 and fixed in
// 7928eb0370d1133d0d8cd2f5ddfca19c309079d5.
.mips, .mipsel => asm volatile (
\\ move $sp, $t9
\\ li $v0, 4091 # SYS_munmap
\\ syscall
\\ li $v0, 4001 # SYS_exit
\\ li $a0, 0
\\ syscall
:
: [ptr] "{$4}" (@intFromPtr(self.mapped.ptr)),
[len] "{$5}" (self.mapped.len),
),
.mips64, .mips64el => asm volatile (switch (target.abi) {
.gnuabin32, .muslabin32, .abin32 =>
\\ li $v0, 6011 # SYS_munmap
\\ syscall
\\ li $v0, 6058 # SYS_exit
\\ li $a0, 0
\\ syscall
,
else =>
\\ li $v0, 5011 # SYS_munmap
\\ syscall
\\ li $v0, 5058 # SYS_exit
\\ li $a0, 0
\\ syscall
,
}
:
: [ptr] "{$4}" (@intFromPtr(self.mapped.ptr)),
[len] "{$5}" (self.mapped.len),
),
.or1k => asm volatile (
\\ l.ori r11, r0, 215 # SYS_munmap
\\ l.sys 1
\\ l.ori r11, r0, 93 # SYS_exit
\\ l.ori r3, r0, r0
\\ l.sys 1
:
: [ptr] "{r3}" (@intFromPtr(self.mapped.ptr)),
[len] "{r4}" (self.mapped.len),
),
.powerpc, .powerpcle, .powerpc64, .powerpc64le => asm volatile (
\\ li 0, 91 # SYS_munmap
\\ sc
\\ li 0, 1 # SYS_exit
\\ li 3, 0
\\ sc
\\ blr
:
: [ptr] "{r3}" (@intFromPtr(self.mapped.ptr)),
[len] "{r4}" (self.mapped.len),
),
.riscv32, .riscv64 => asm volatile (
\\ li a7, 215 # SYS_munmap
\\ ecall
\\ li a7, 93 # SYS_exit
\\ mv a0, zero
\\ ecall
:
: [ptr] "{a0}" (@intFromPtr(self.mapped.ptr)),
[len] "{a1}" (self.mapped.len),
),
.s390x => asm volatile (
\\ svc 91 # SYS_munmap
\\ lghi %%r2, 0
\\ svc 1 # SYS_exit
:
: [ptr] "{r2}" (@intFromPtr(self.mapped.ptr)),
[len] "{r3}" (self.mapped.len),
),
.sh, .sheb => asm volatile (
\\ mov #91, r3 ! SYS_munmap
\\ trapa #31
\\ or r0, r0
\\ or r0, r0
\\ or r0, r0
\\ or r0, r0
\\ or r0, r0
\\ mov #1, r3 ! SYS_exit
\\ mov #0, r4
\\ trapa #31
\\ or r0, r0
\\ or r0, r0
\\ or r0, r0
\\ or r0, r0
\\ or r0, r0
:
: [ptr] "{r4}" (@intFromPtr(self.mapped.ptr)),
[len] "{r5}" (self.mapped.len),
),
.sparc => asm volatile (
\\ # See sparc64 comments below.
\\ 1:
\\ cmp %%fp, 0
\\ beq 2f
\\ nop
\\ ba 1b
\\ restore
\\ 2:
\\ mov %%g1, %%o0 // ptr
\\ mov %%g2, %%o1 // len
\\ mov 73, %%g1 // SYS_munmap
\\ t 0x3 // ST_FLUSH_WINDOWS
\\ t 0x10
\\ mov 1, %%g1 // SYS_exit
\\ mov 0, %%o0
\\ t 0x10
:
: [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),
[len] "{g2}" (self.mapped.len),
: .{ .memory = true }),
.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 %%g1, %%o0 // ptr
\\ mov %%g2, %%o1 // len
\\ mov 73, %%g1 // SYS_munmap
\\ # Flush register window contents to prevent background
\\ # memory access before unmapping the stack.
\\ flushw
\\ t 0x6d
\\ mov 1, %%g1 // SYS_exit
\\ mov 0, %%o0
\\ t 0x6d
:
: [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),
[len] "{g2}" (self.mapped.len),
: .{ .memory = true }),
.loongarch32, .loongarch64 => asm volatile (
\\ ori $a7, $zero, 215 # SYS_munmap
\\ syscall 0 # call munmap
\\ ori $a0, $zero, 0
\\ ori $a7, $zero, 93 # SYS_exit
\\ syscall 0 # call exit
:
: [ptr] "{r4}" (@intFromPtr(self.mapped.ptr)),
[len] "{r5}" (self.mapped.len),
: .{ .memory = true }),
.csky => asm volatile (
\\ movi r7, 215 # SYS_munmap
\\ trap 0
\\ movi r7, 93 # SYS_exit
\\ movi r0, 0
\\ trap 0
:
: [ptr] "{r0}" (@intFromPtr(self.mapped.ptr)),
[len] "{r1}" (self.mapped.len),
: .{ .memory = true }),
.xtensa, .xtensaeb => asm volatile (
\\ movi a2, 81 // SYS_munmap
\\ syscall
\\ movi a6, 0
\\ movi a2, 118 // SYS_exit
\\ syscall
:
: [ptr] "{a6}" (@intFromPtr(self.mapped.ptr)),
[len] "{a3}" (self.mapped.len),
: .{ .memory = true }),
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.heap.pageSize();
const Args = @TypeOf(args);
const Instance = struct {
fn_args: Args,
thread: ThreadCompletion,
fn entryFn(raw_arg: usize) callconv(.c) u8 {
const self = @as(*@This(), @ptrFromInt(raw_arg));
defer switch (self.thread.completion.swap(.completed, .seq_cst)) {
.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 += @max(page_size, config.stack_size);
bytes = std.mem.alignForward(usize, bytes, page_size);
stack_offset = bytes;
bytes = std.mem.alignForward(usize, bytes, linux.tls.area_desc.alignment);
tls_offset = bytes;
bytes += linux.tls.area_desc.size;
bytes = std.mem.alignForward(usize, bytes, @alignOf(Instance));
instance_offset = bytes;
bytes += @sizeOf(Instance);
bytes = std.mem.alignForward(usize, bytes, page_size);
break :blk bytes;
};
// to avoid committing the whole region right away
// anonymous mapping ensures file descriptor limits are not exceeded
const mapped = posix.mmap(
null,
map_bytes,
.{},
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
-1,
0,
) catch |err| switch (err) {
error.MemoryMappingNotSupported => unreachable,
error.AccessDenied => unreachable,
error.PermissionDenied => unreachable,
error.ProcessFdQuotaExceeded => unreachable,
error.SystemFdQuotaExceeded => unreachable,
error.MappingAlreadyExists => unreachable,
else => |e| return e,
};
assert(mapped.len >= map_bytes);
errdefer posix.munmap(mapped);
const guarded: []align(std.heap.page_size_min) u8 = @alignCast(mapped[guard_offset..]);
const protection: posix.PROT = .{ .READ = true, .WRITE = true };
switch (posix.errno(posix.system.mprotect(guarded.ptr, guarded.len, protection))) {
.SUCCESS => {},
.NOMEM => return error.OutOfMemory,
else => |err| return posix.unexpectedErrno(err),
}
var tls_ptr = linux.tls.prepareArea(mapped[tls_offset..][0..linux.tls.area_desc.size]);
var user_desc: if (target.cpu.arch == .x86) linux.user_desc else void = undefined;
if (target.cpu.arch == .x86) {
defer tls_ptr = @intFromPtr(&user_desc);
user_desc = .{
.entry_number = linux.tls.area_desc.gdt_entry_number,
.base_addr = tls_ptr,
.limit = 0xfffff,
.flags = .{
.seg_32bit = 1,
.contents = 0,
.read_exec_only = 0,
.limit_in_pages = 1,
.seg_not_present = 0,
.useable = 1,
},
};
}
const instance: *Instance = @ptrCast(@alignCast(&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.errno(linux.clone(
Instance.entryFn,
@intFromPtr(&mapped[stack_offset]),
flags,
@intFromPtr(instance),
&instance.thread.parent_tid,
tls_ptr,
&instance.thread.child_tid.raw,
))) {
.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 posix.unexpectedErrno(err),
}
}
fn getHandle(self: Impl) ThreadHandle {
return self.thread.parent_tid;
}
fn detach(self: Impl) void {
switch (self.thread.completion.swap(.detached, .seq_cst)) {
.running => {},
.completed => self.join(),
.detached => unreachable,
}
}
fn join(self: Impl) void {
defer posix.munmap(self.thread.mapped);
while (true) {
const tid = self.thread.child_tid.load(.seq_cst);
if (tid == 0) break;
switch (linux.errno(linux.futex_4arg(
&self.thread.child_tid.raw,
.{ .cmd = .WAIT, .private = false },
@bitCast(tid),
null,
))) {
.SUCCESS => continue,
.INTR => continue,
.AGAIN => continue,
else => unreachable,
}
}
}
}