| ... | @@ -28,6 +28,8 @@ else if (use_pthreads) | ... | @@ -28,6 +28,8 @@ else if (use_pthreads) |
| 28 | PosixThreadImpl | 28 | PosixThreadImpl |
| 29 | else if (target.os.tag == .linux) | 29 | else if (target.os.tag == .linux) |
| 30 | LinuxThreadImpl | 30 | LinuxThreadImpl |
| | 31 | else if (target.os.tag == .wasi) |
| | 32 | WasiThreadImpl |
| 31 | else | 33 | else |
| 32 | UnsupportedImpl; | 34 | UnsupportedImpl; |
| 33 | | 35 | |
| ... | @@ -266,6 +268,7 @@ pub const Id = switch (target.os.tag) { | ... | @@ -266,6 +268,7 @@ pub const Id = switch (target.os.tag) { |
| 266 | .freebsd, | 268 | .freebsd, |
| 267 | .openbsd, | 269 | .openbsd, |
| 268 | .haiku, | 270 | .haiku, |
| | 271 | .wasi, |
| 269 | => u32, | 272 | => u32, |
| 270 | .macos, .ios, .watchos, .tvos => u64, | 273 | .macos, .ios, .watchos, .tvos => u64, |
| 271 | .windows => os.windows.DWORD, | 274 | .windows => os.windows.DWORD, |
| ... | @@ -296,6 +299,8 @@ pub const SpawnConfig = struct { | ... | @@ -296,6 +299,8 @@ pub const SpawnConfig = struct { |
| 296 | | 299 | |
| 297 | /// Size in bytes of the Thread's stack | 300 | /// Size in bytes of the Thread's stack |
| 298 | stack_size: usize = 16 * 1024 * 1024, | 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 | pub const SpawnError = error{ | 306 | pub const SpawnError = error{ |
| ... | @@ -733,6 +738,193 @@ const PosixThreadImpl = struct { | ... | @@ -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 | const LinuxThreadImpl = struct { | 928 | const LinuxThreadImpl = struct { |
| 737 | const linux = os.linux; | 929 | const linux = os.linux; |
| 738 | | 930 | |