| ... | ... | @@ -28,6 +28,8 @@ else if (use_pthreads) |
| 28 | 28 | PosixThreadImpl |
| 29 | 29 | else if (target.os.tag == .linux) |
| 30 | 30 | LinuxThreadImpl |
| 31 | else if (target.os.tag == .wasi) |
| 32 | WasiThreadImpl |
| 31 | 33 | else |
| 32 | 34 | UnsupportedImpl; |
| 33 | 35 | |
| ... | ... | @@ -266,6 +268,7 @@ pub const Id = switch (target.os.tag) { |
| 266 | 268 | .freebsd, |
| 267 | 269 | .openbsd, |
| 268 | 270 | .haiku, |
| 271 | .wasi, |
| 269 | 272 | => u32, |
| 270 | 273 | .macos, .ios, .watchos, .tvos => u64, |
| 271 | 274 | .windows => os.windows.DWORD, |
| ... | ... | @@ -296,6 +299,8 @@ pub const SpawnConfig = struct { |
| 296 | 299 | |
| 297 | 300 | /// Size in bytes of the Thread's stack |
| 298 | 301 | stack_size: usize = 16 * 1024 * 1024, |
| 302 | /// The allocator to be used to allocate memory for the to-be-spawned thread |
| 303 | allocator: ?std.mem.Allocator = null, |
| 299 | 304 | }; |
| 300 | 305 | |
| 301 | 306 | pub const SpawnError = error{ |
| ... | ... | @@ -733,6 +738,193 @@ const PosixThreadImpl = struct { |
| 733 | 738 | } |
| 734 | 739 | }; |
| 735 | 740 | |
| 741 | const WasiThreadImpl = struct { |
| 742 | comptime { |
| 743 | // Sets the stack pointer, which is needed after creating a new thread |
| 744 | // to ensure the stack of the main thread isn't being poluted. |
| 745 | asm ( |
| 746 | \\ .text |
| 747 | \\ .export_name	__set_stack_pointer, __set_stack_pointer |
| 748 | \\ .globaltype __stack_pointer, i32 |
| 749 | \\ .hidden wasi_thread_start |
| 750 | \\ .globl wasi_thread_start |
| 751 | \\ .type __set_stack_pointer, @function |
| 752 | \\ |
| 753 | \\ __set_stack_pointer: |
| 754 | \\	 .functype	__set_stack_pointer (i32) -> () |
| 755 | \\ local.get 0 # The raw pointer which replaces the stack pointer |
| 756 | \\ global.set __stack_pointer |
| 757 | \\ end_function |
| 758 | ); |
| 759 | } |
| 760 | thread: *WasiThread, |
| 761 | |
| 762 | pub const ThreadHandle = i32; |
| 763 | threadlocal var tls_thread_id: Id = 0; |
| 764 | |
| 765 | const WasiThread = struct { |
| 766 | tid: Atomic(i32) = Atomic(i32).init(0), |
| 767 | memory: []u8, |
| 768 | }; |
| 769 | |
| 770 | /// A meta-data structure used to bootstrap a thread |
| 771 | const Instance = struct { |
| 772 | thread: WasiThread, |
| 773 | /// Address of this `Instance` |
| 774 | base: usize, |
| 775 | /// Contains the pointer of the new __tls_base. |
| 776 | tls_base: usize, |
| 777 | /// Contains the pointer to the stack for the newly spawned thread. |
| 778 | stack_pointer: usize, |
| 779 | /// Contains the pointer to the wrapper which holds all arguments |
| 780 | /// for the callback. |
| 781 | raw_ptr: usize, |
| 782 | /// Function pointer to a wrapping function which will call the user's |
| 783 | /// function upon thread spawn. The above mentioned pointer will be passed |
| 784 | /// to this function pointer as its argument. |
| 785 | call_back: *const fn (usize) void, |
| 786 | }; |
| 787 | |
| 788 | fn getCurrentId() Id { |
| 789 | return tls_thread_id; |
| 790 | } |
| 791 | |
| 792 | fn getHandle(self: Impl) ThreadHandle { |
| 793 | return self.thread.tid; |
| 794 | } |
| 795 | |
| 796 | fn detach(self: Impl) void { |
| 797 | _ = self; |
| 798 | } |
| 799 | |
| 800 | fn join(self: Impl) void { |
| 801 | _ = self; |
| 802 | } |
| 803 | |
| 804 | fn spawn(config: std.Thread.SpawnConfig, comptime f: anytype, args: anytype) !WasiThreadImpl { |
| 805 | if (config.allocator == null) return error.OutOfMemory; // an allocator is required to spawn a WASI-thread |
| 806 | |
| 807 | // Wrapping struct required to hold the user-provided function arguments. |
| 808 | const Wrapper = struct { |
| 809 | args: @TypeOf(args), |
| 810 | fn entry(ptr: usize) void { |
| 811 | const w = @intToPtr(*@This(), ptr); |
| 812 | @call(.auto, f, w.args); |
| 813 | } |
| 814 | }; |
| 815 | |
| 816 | var guard_offset: usize = undefined; |
| 817 | var stack_offset: usize = undefined; |
| 818 | var tls_offset: usize = undefined; |
| 819 | var wrapper_offset: usize = undefined; |
| 820 | var instance_offset: usize = undefined; |
| 821 | |
| 822 | // Calculate the bytes we have to allocate to store all thread information, including: |
| 823 | // - The actual stack for the thread |
| 824 | // - The TLS segment |
| 825 | // - `Instance` - containing information about how to call the user's function. |
| 826 | const map_bytes = blk: { |
| 827 | var bytes: usize = std.wasm.page_size; |
| 828 | guard_offset = bytes; |
| 829 | |
| 830 | bytes = std.mem.alignForward(usize, bytes, 16); // align stack to 16 bytes |
| 831 | stack_offset = bytes; |
| 832 | bytes += @max(std.wasm.page_size, config.stack_size); |
| 833 | |
| 834 | bytes = std.mem.alignForward(usize, bytes, __tls_align()); |
| 835 | tls_offset = bytes; |
| 836 | bytes += __tls_size(); |
| 837 | |
| 838 | bytes = std.mem.alignForward(usize, bytes, @alignOf(Wrapper)); |
| 839 | wrapper_offset = bytes; |
| 840 | bytes += @sizeOf(Wrapper); |
| 841 | |
| 842 | bytes = std.mem.alignForward(usize, bytes, @alignOf(Instance)); |
| 843 | instance_offset = bytes; |
| 844 | bytes += @sizeOf(Instance); |
| 845 | |
| 846 | bytes = std.mem.alignForward(usize, bytes, std.wasm.page_size); |
| 847 | break :blk bytes; |
| 848 | }; |
| 849 | |
| 850 | // Allocate the amount of memory required for all meta data. |
| 851 | const allocated_memory = try config.allocator.?.alloc(u8, map_bytes); |
| 852 | |
| 853 | const wrapper = @ptrCast(*Wrapper, @alignCast(@alignOf(Wrapper), &allocated_memory[wrapper_offset])); |
| 854 | wrapper.* = .{ .args = args }; |
| 855 | |
| 856 | const instance = @ptrCast(*Instance, @alignCast(@alignOf(Instance), &allocated_memory[instance_offset])); |
| 857 | instance.* = .{ |
| 858 | .thread = .{ .memory = allocated_memory }, |
| 859 | .base = @ptrToInt(allocated_memory.ptr), |
| 860 | .tls_base = tls_offset, |
| 861 | .stack_pointer = stack_offset, |
| 862 | .raw_ptr = @ptrToInt(wrapper), |
| 863 | .call_back = &Wrapper.entry, |
| 864 | }; |
| 865 | |
| 866 | const tid = spawnWasiThread(instance); |
| 867 | // The specification says any value lower than 0 indicates an error. |
| 868 | // The values of such error are unspecified. WASI-Libc treats it as EAGAIN. |
| 869 | if (tid < 0) { |
| 870 | return error.SystemResources; |
| 871 | } |
| 872 | instance.thread.tid.store(tid, .SeqCst); |
| 873 | |
| 874 | return .{ .thread = &instance.thread }; |
| 875 | } |
| 876 | |
| 877 | export fn wasi_thread_start(tid: i32, arg: *const Instance) void { |
| 878 | __set_stack_pointer(arg.thread.memory.ptr + arg.stack_pointer); |
| 879 | __wasm_init_tls(arg.thread.memory.ptr + arg.tls_base); |
| 880 | WasiThreadImpl.tls_thread_id = @intCast(u32, tid); |
| 881 | |
| 882 | // finished bootstrapping, call user's procedure. |
| 883 | arg.call_back(arg.raw_ptr); |
| 884 | } |
| 885 | |
| 886 | // Asks the host to create a new thread for us. |
| 887 | // Newly created thread wil lcall `wasi_tread_start` with the thread ID as well |
| 888 | // as the input `arg` that was provided to `spawnWasiThread` |
| 889 | const spawnWasiThread = @"thread-spawn"; |
| 890 | extern "wasi" fn @"thread-spawn"(arg: *const Instance) i32; |
| 891 | |
| 892 | /// Initializes the TLS data segment starting at `memory`. |
| 893 | /// This is a synthetic function, generated by the linker. |
| 894 | extern fn __wasm_init_tls(memory: [*]u8) void; |
| 895 | extern fn __set_stack_pointer(ptr: [*]u8) void; |
| 896 | |
| 897 | /// Returns a pointer to the base of the TLS data segment for the current thread |
| 898 | inline fn __tls_base() [*]u8 { |
| 899 | return asm ( |
| 900 | \\ .globaltype __tls_base, i32 |
| 901 | \\ global.get __tls_base |
| 902 | \\ local.set %[ret] |
| 903 | : [ret] "=r" (-> [*]u8), |
| 904 | ); |
| 905 | } |
| 906 | |
| 907 | /// Returns the size of the TLS segment |
| 908 | inline fn __tls_size() u32 { |
| 909 | return asm volatile ( |
| 910 | \\ .globaltype __tls_size, i32, immutable |
| 911 | \\ global.get __tls_size |
| 912 | \\ local.set %[ret] |
| 913 | : [ret] "=r" (-> u32), |
| 914 | ); |
| 915 | } |
| 916 | |
| 917 | /// Returns the alignment of the TLS segment |
| 918 | inline fn __tls_align() u32 { |
| 919 | return asm ( |
| 920 | \\ .globaltype __tls_align, i32, immutable |
| 921 | \\ global.get __tls_align |
| 922 | \\ local.set %[ret] |
| 923 | : [ret] "=r" (-> u32), |
| 924 | ); |
| 925 | } |
| 926 | }; |
| 927 | |
| 736 | 928 | const LinuxThreadImpl = struct { |
| 737 | 929 | const linux = os.linux; |
| 738 | 930 | |