| ... | @@ -757,6 +757,8 @@ const WasiThreadImpl = struct { | ... | @@ -757,6 +757,8 @@ const WasiThreadImpl = struct { |
| 757 | /// The allocator used to allocate the thread's memory, | 757 | /// The allocator used to allocate the thread's memory, |
| 758 | /// which is also used during `join` to ensure clean-up. | 758 | /// which is also used during `join` to ensure clean-up. |
| 759 | allocator: std.mem.Allocator, | 759 | allocator: std.mem.Allocator, |
| | 760 | /// The current state of the thread. |
| | 761 | state: State = State.init(.running), |
| 760 | }; | 762 | }; |
| 761 | | 763 | |
| 762 | /// A meta-data structure used to bootstrap a thread | 764 | /// A meta-data structure used to bootstrap a thread |
| ... | @@ -775,8 +777,15 @@ const WasiThreadImpl = struct { | ... | @@ -775,8 +777,15 @@ const WasiThreadImpl = struct { |
| 775 | /// function upon thread spawn. The above mentioned pointer will be passed | 777 | /// function upon thread spawn. The above mentioned pointer will be passed |
| 776 | /// to this function pointer as its argument. | 778 | /// to this function pointer as its argument. |
| 777 | call_back: *const fn (usize) void, | 779 | call_back: *const fn (usize) void, |
| | 780 | /// When a thread is in `detached` state, we must free all of its memory |
| | 781 | /// upon thread completion. However, as this is done while still within |
| | 782 | /// the thread, we must first jump back to the main thread's stack or else |
| | 783 | /// we end up freeing the stack that we're currently using. |
| | 784 | original_stack_pointer: [*]u8, |
| 778 | }; | 785 | }; |
| 779 | | 786 | |
| | 787 | const State = Atomic(enum(u8) { running, completed, detached }); |
| | 788 | |
| 780 | fn getCurrentId() Id { | 789 | fn getCurrentId() Id { |
| 781 | return tls_thread_id; | 790 | return tls_thread_id; |
| 782 | } | 791 | } |
| ... | @@ -786,7 +795,11 @@ const WasiThreadImpl = struct { | ... | @@ -786,7 +795,11 @@ const WasiThreadImpl = struct { |
| 786 | } | 795 | } |
| 787 | | 796 | |
| 788 | fn detach(self: Impl) void { | 797 | fn detach(self: Impl) void { |
| 789 | _ = self; | 798 | switch (self.thread.state.swap(.detached, .SeqCst)) { |
| | 799 | .running => {}, |
| | 800 | .completed => self.join(), |
| | 801 | .detached => unreachable, |
| | 802 | } |
| 790 | } | 803 | } |
| 791 | | 804 | |
| 792 | fn join(self: Impl) void { | 805 | fn join(self: Impl) void { |
| ... | @@ -836,7 +849,7 @@ const WasiThreadImpl = struct { | ... | @@ -836,7 +849,7 @@ const WasiThreadImpl = struct { |
| 836 | const Wrapper = struct { | 849 | const Wrapper = struct { |
| 837 | args: @TypeOf(args), | 850 | args: @TypeOf(args), |
| 838 | fn entry(ptr: usize) void { | 851 | fn entry(ptr: usize) void { |
| 839 | const w = @intToPtr(*@This(), ptr); | 852 | const w: *@This() = @ptrFromInt(ptr); |
| 840 | @call(.auto, f, w.args); | 853 | @call(.auto, f, w.args); |
| 841 | } | 854 | } |
| 842 | }; | 855 | }; |
| ... | @@ -854,7 +867,7 @@ const WasiThreadImpl = struct { | ... | @@ -854,7 +867,7 @@ const WasiThreadImpl = struct { |
| 854 | // start with atleast a single page, which is used as a guard to prevent | 867 | // start with atleast a single page, which is used as a guard to prevent |
| 855 | // other threads clobbering our new thread. | 868 | // other threads clobbering our new thread. |
| 856 | // Unfortunately, WebAssembly has no notion of read-only segments, so this | 869 | // Unfortunately, WebAssembly has no notion of read-only segments, so this |
| 857 | // is only a temporary measure until the entire page is "run over". | 870 | // is only a best effort. |
| 858 | var bytes: usize = std.wasm.page_size; | 871 | var bytes: usize = std.wasm.page_size; |
| 859 | | 872 | |
| 860 | bytes = std.mem.alignForward(usize, bytes, 16); // align stack to 16 bytes | 873 | bytes = std.mem.alignForward(usize, bytes, 16); // align stack to 16 bytes |
| ... | @@ -880,16 +893,17 @@ const WasiThreadImpl = struct { | ... | @@ -880,16 +893,17 @@ const WasiThreadImpl = struct { |
| 880 | // Allocate the amount of memory required for all meta data. | 893 | // Allocate the amount of memory required for all meta data. |
| 881 | const allocated_memory = try config.allocator.?.alloc(u8, map_bytes); | 894 | const allocated_memory = try config.allocator.?.alloc(u8, map_bytes); |
| 882 | | 895 | |
| 883 | const wrapper = @ptrCast(*Wrapper, @alignCast(@alignOf(Wrapper), &allocated_memory[wrapper_offset])); | 896 | const wrapper: *Wrapper = @ptrCast(@alignCast(&allocated_memory[wrapper_offset])); |
| 884 | wrapper.* = .{ .args = args }; | 897 | wrapper.* = .{ .args = args }; |
| 885 | | 898 | |
| 886 | const instance = @ptrCast(*Instance, @alignCast(@alignOf(Instance), &allocated_memory[instance_offset])); | 899 | const instance: *Instance = @ptrCast(@alignCast(&allocated_memory[instance_offset])); |
| 887 | instance.* = .{ | 900 | instance.* = .{ |
| 888 | .thread = .{ .memory = allocated_memory, .allocator = config.allocator.? }, | 901 | .thread = .{ .memory = allocated_memory, .allocator = config.allocator.? }, |
| 889 | .tls_offset = tls_offset, | 902 | .tls_offset = tls_offset, |
| 890 | .stack_offset = stack_offset, | 903 | .stack_offset = stack_offset, |
| 891 | .raw_ptr = @ptrToInt(wrapper), | 904 | .raw_ptr = @intFromPtr(wrapper), |
| 892 | .call_back = &Wrapper.entry, | 905 | .call_back = &Wrapper.entry, |
| | 906 | .original_stack_pointer = __get_stack_pointer(), |
| 893 | }; | 907 | }; |
| 894 | | 908 | |
| 895 | const tid = spawnWasiThread(instance); | 909 | const tid = spawnWasiThread(instance); |
| ... | @@ -903,32 +917,46 @@ const WasiThreadImpl = struct { | ... | @@ -903,32 +917,46 @@ const WasiThreadImpl = struct { |
| 903 | return .{ .thread = &instance.thread }; | 917 | return .{ .thread = &instance.thread }; |
| 904 | } | 918 | } |
| 905 | | 919 | |
| 906 | /// Bootstrap procedure, called by the HOST environment after thread creation. | 920 | /// Bootstrap procedure, called by the host environment after thread creation. |
| 907 | export fn wasi_thread_start(tid: i32, arg: *Instance) void { | 921 | export fn wasi_thread_start(tid: i32, arg: *Instance) void { |
| 908 | __set_stack_pointer(arg.thread.memory.ptr + arg.stack_offset); | 922 | __set_stack_pointer(arg.thread.memory.ptr + arg.stack_offset); |
| 909 | __wasm_init_tls(arg.thread.memory.ptr + arg.tls_offset); | 923 | __wasm_init_tls(arg.thread.memory.ptr + arg.tls_offset); |
| 910 | WasiThreadImpl.tls_thread_id = @intCast(u32, tid); | 924 | @atomicStore(u32, &WasiThreadImpl.tls_thread_id, @intCast(tid), .SeqCst); |
| 911 | | 925 | |
| 912 | // Finished bootstrapping, call user's procedure. | 926 | // Finished bootstrapping, call user's procedure. |
| 913 | arg.call_back(arg.raw_ptr); | 927 | arg.call_back(arg.raw_ptr); |
| 914 | | 928 | |
| 915 | // Thread finished. Reset Thread ID and wake up the main thread if needed. | 929 | switch (arg.thread.state.swap(.completed, .SeqCst)) { |
| 916 | // We use inline assembly here as we must ensure not to use the stack. | 930 | .running => { |
| 917 | asm volatile ( | 931 | // reset the Thread ID |
| 918 | \\ local.get %[ptr] | 932 | asm volatile ( |
| 919 | \\ i32.const 0 | 933 | \\ local.get %[ptr] |
| 920 | \\ i32.atomic.store 0 | 934 | \\ i32.const 0 |
| 921 | : | 935 | \\ i32.atomic.store 0 |
| 922 | : [ptr] "r" (&arg.thread.tid.value), | 936 | : |
| 923 | ); | 937 | : [ptr] "r" (&arg.thread.tid.value), |
| 924 | asm volatile ( | 938 | ); |
| 925 | \\ local.get %[ptr] | 939 | |
| 926 | \\ i32.const 1 # waiters | 940 | // Wake the main thread listening to this thread |
| 927 | \\ memory.atomic.notify 0 | 941 | asm volatile ( |
| 928 | \\ drop # no need to know the waiters | 942 | \\ local.get %[ptr] |
| 929 | : | 943 | \\ i32.const 1 # waiters |
| 930 | : [ptr] "r" (&arg.thread.tid.value), | 944 | \\ memory.atomic.notify 0 |
| 931 | ); | 945 | \\ drop # no need to know the waiters |
| | 946 | : |
| | 947 | : [ptr] "r" (&arg.thread.tid.value), |
| | 948 | ); |
| | 949 | }, |
| | 950 | .completed => unreachable, |
| | 951 | .detached => { |
| | 952 | // restore the original stack pointer so we can free the memory |
| | 953 | // without having to worry about freeing the stack |
| | 954 | __set_stack_pointer(arg.original_stack_pointer); |
| | 955 | // Ensure a copy so we don't free the allocator reference itself |
| | 956 | var allocator = arg.thread.allocator; |
| | 957 | allocator.free(arg.thread.memory); |
| | 958 | }, |
| | 959 | } |
| 932 | } | 960 | } |
| 933 | | 961 | |
| 934 | /// Asks the host to create a new thread for us. | 962 | /// Asks the host to create a new thread for us. |
| ... | @@ -980,6 +1008,15 @@ const WasiThreadImpl = struct { | ... | @@ -980,6 +1008,15 @@ const WasiThreadImpl = struct { |
| 980 | : [ptr] "r" (addr), | 1008 | : [ptr] "r" (addr), |
| 981 | ); | 1009 | ); |
| 982 | } | 1010 | } |
| | 1011 | |
| | 1012 | /// Returns the current value of the stack pointer |
| | 1013 | inline fn __get_stack_pointer() [*]u8 { |
| | 1014 | return asm ( |
| | 1015 | \\ global.get __stack_pointer |
| | 1016 | \\ local.set %[stack_ptr] |
| | 1017 | : [stack_ptr] "=r" (-> [*]u8), |
| | 1018 | ); |
| | 1019 | } |
| 983 | }; | 1020 | }; |
| 984 | | 1021 | |
| 985 | const LinuxThreadImpl = struct { | 1022 | const LinuxThreadImpl = struct { |