authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-06-21 21:44:53+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-06-26 20:00:57+02:00
log834609038c83e0122b506fadfa38b9acb7ee9068
treeb738876952e39d484ddb642a28ce4763c4771098
parent10bf58b2db0e340137228f706ee2e8a67b46a892
signaturelock-open Commit is signed but in an unrecognized format.

std: implement `join` for WASI-threads

We now reset the Thread ID to 0 and wake up the main thread listening for the thread to finish. We use inline assembly as we cannot use the stack to set the thread ID as it could possibly clobber any of the memory. Currently, we leak the memory that was allocated for the thread. We need to implement a way where we can clean up the memory without using the stack (as the stack is stored inside this same memory).

1 files changed, 59 insertions(+), 8 deletions(-)

lib/std/Thread.zig+59-8
...@@ -790,7 +790,40 @@ const WasiThreadImpl = struct {...@@ -790,7 +790,40 @@ const WasiThreadImpl = struct {
790 }790 }
791791
792 fn join(self: Impl) void {792 fn join(self: Impl) void {
793 _ = self;793 // TODO cleanup memory
794 // The memory also contains the thread's stack, which is problematic while freeing the memory
795 // defer self.thread.allocator.free(self.thread.memory);
796
797 var spin: u8 = 10;
798 while (true) {
799 const tid = self.thread.tid.load(.SeqCst);
800 if (tid == 0) {
801 break;
802 }
803
804 if (spin > 0) {
805 spin -= 1;
806 std.atomic.spinLoopHint();
807 continue;
808 }
809
810 const result = asm (
811 \\local.get %[ptr]
812 \\local.get %[expected]
813 \\i64.const -1 # infinite
814 \\memory.atomic.wait32 0
815 \\local.set %[ret]
816 : [ret] "=r" (-> u32),
817 : [ptr] "r" (&self.thread.tid.value),
818 [expected] "r" (tid),
819 );
820 switch (result) {
821 0 => continue, // ok
822 1 => continue, // expected =! loaded
823 2 => unreachable, // timeout (infinite)
824 else => unreachable,
825 }
826 }
794 }827 }
795828
796 fn spawn(config: std.Thread.SpawnConfig, comptime f: anytype, args: anytype) !WasiThreadImpl {829 fn spawn(config: std.Thread.SpawnConfig, comptime f: anytype, args: anytype) !WasiThreadImpl {
...@@ -868,25 +901,43 @@ const WasiThreadImpl = struct {...@@ -868,25 +901,43 @@ const WasiThreadImpl = struct {
868 return .{ .thread = &instance.thread };901 return .{ .thread = &instance.thread };
869 }902 }
870903
871 export fn wasi_thread_start(tid: i32, arg: *const Instance) void {904 /// Bootstrap procedure, called by the HOST environment after thread creation.
905 export fn wasi_thread_start(tid: i32, arg: *Instance) void {
872 __set_stack_pointer(arg.thread.memory.ptr + arg.stack_pointer);906 __set_stack_pointer(arg.thread.memory.ptr + arg.stack_pointer);
873 __wasm_init_tls(arg.thread.memory.ptr + arg.tls_base);907 __wasm_init_tls(arg.thread.memory.ptr + arg.tls_base);
874 WasiThreadImpl.tls_thread_id = @intCast(u32, tid);908 WasiThreadImpl.tls_thread_id = @intCast(u32, tid);
875909
876 // finished bootstrapping, call user's procedure.910 // Finished bootstrapping, call user's procedure.
877 arg.call_back(arg.raw_ptr);911 arg.call_back(arg.raw_ptr);
912
913 // Thread finished. Reset Thread ID and wake up the main thread if needed.
914 // We use inline assembly here as we must ensure not to use the stack.
915 asm volatile (
916 \\ local.get %[ptr]
917 \\ i32.const 0
918 \\ i32.atomic.store 0
919 :
920 : [ptr] "r" (&arg.thread.tid.value),
921 );
922 asm volatile (
923 \\ local.get %[ptr]
924 \\ i32.const 1 # waiters
925 \\ memory.atomic.notify 0
926 \\ drop # no need to know the waiters
927 :
928 : [ptr] "r" (&arg.thread.tid.value),
929 );
878 }930 }
879931
880 // Asks the host to create a new thread for us.932 /// Asks the host to create a new thread for us.
881 // Newly created thread wil lcall `wasi_tread_start` with the thread ID as well933 /// Newly created thread will call `wasi_tread_start` with the thread ID as well
882 // as the input `arg` that was provided to `spawnWasiThread`934 /// as the input `arg` that was provided to `spawnWasiThread`
883 const spawnWasiThread = @"thread-spawn";935 const spawnWasiThread = @"thread-spawn";
884 extern "wasi" fn @"thread-spawn"(arg: *const Instance) i32;936 extern "wasi" fn @"thread-spawn"(arg: *Instance) i32;
885937
886 /// Initializes the TLS data segment starting at `memory`.938 /// Initializes the TLS data segment starting at `memory`.
887 /// This is a synthetic function, generated by the linker.939 /// This is a synthetic function, generated by the linker.
888 extern fn __wasm_init_tls(memory: [*]u8) void;940 extern fn __wasm_init_tls(memory: [*]u8) void;
889 extern fn __set_stack_pointer(ptr: [*]u8) void;
890941
891 /// Returns a pointer to the base of the TLS data segment for the current thread942 /// Returns a pointer to the base of the TLS data segment for the current thread
892 inline fn __tls_base() [*]u8 {943 inline fn __tls_base() [*]u8 {