// inspired by the linux no-libc threading interface from nullprogram // https://nullprogram.com/blog/2023/03/23/ // @date 2023-03-26 10:01:35Z // license: this code is released in the public domain. Do what thou wilt. // (to the extent I don't infringe on nullprogram's original) const std = @import("std"); const os = std.os; const linux = os.linux; const debug = std.debug; const assert = debug.assert; pub fn main() !void{ @setAlignStack(16); // TODO superfluous? const stack = try newstack(64*1024); stack.entry = thread_body; stack.message = "hello world\n"; stack.print_count = 20; stack.join_futex = 0; newthread(stack); futex_wait(&stack.join_futex, 0); linux.exit_group(0); } fn thread_body(stack: *StackHead) noreturn { debug.print("thread_body entered entry:{p} stackmsg:{s}\n", .{stack.entry, stack.message}); const message = stack.message; const count = stack.print_count; for(0..count) |_| { write_all(os.STDOUT_FILENO, message) catch unreachable; millisleep(25); } @atomicStore(usize, &stack.join_futex, 1, std.builtin.AtomicOrder.SeqCst); futex_wake(&stack.join_futex); os.exit(0); } const thread_entry_fn_t = *const fn(sh: *StackHead) void; const StackHead = struct { entry: thread_entry_fn_t align(16), message: []const u8, print_count: usize, join_futex: usize, }; fn newthread(sh: *StackHead) callconv(.Inline) void { @call(.never_inline, _newthread, .{sh}); } //fn _newthread(sh: *StackHead) callconv(.Naked) noreturn { fn _newthread(sh: *StackHead) void { asm volatile ( \\xchg %rdi, %rsi \\syscall \\mov %rsp, %rdi \\ret \\ : // no return : [a] "{rax}" (linux.SYS.clone), [sh] "{rdi}" (sh), [S] "{rsi}" (0x50f00) : "rdi", "rcx", "r11", "memory" ); } fn newstack(size: usize) !*StackHead { const m1 = @as(isize, -1); const m4096 = @as(isize, -4096); var p = linux.syscall6(linux.SYS.mmap, 0, size, 3, 0x22, @bitCast(usize,m1), 0); if(p > @bitCast(usize,m4096)){ return error.StackCreateFailed; } const count = size / @sizeOf(StackHead); p += (count-1)*@sizeOf(StackHead); return @intToPtr(*StackHead, p); } fn futex_wait(pfutex: *usize, expect: usize) void { const futex = @ptrToInt(pfutex); _ = linux.syscall4(linux.SYS.futex, futex, linux.FUTEX.WAIT, expect, 0); } fn futex_wake(pfutex: *usize) void { const futex = @ptrToInt(pfutex); _ = linux.syscall3(linux.SYS.futex, futex, linux.FUTEX.WAKE, 0x7fffffff); } fn millisleep(ms: usize) void { const ts = [_]usize{ms/1000, ms%1000 * 1000000}; const tsptr = @ptrToInt(&ts); _ = linux.syscall2(linux.SYS.nanosleep, tsptr, tsptr); } fn write_all(ofd: os.fd_t, buf: []const u8) !void { var p: usize = 0; while(p < buf.len){ const wsz = try os.write(ofd, buf[p..]); p += wsz; } }