authorgravatar for kbutcher6200@gmail.comkprotty <kbutcher6200@gmail.com> 2019-11-05 15:58:58-06:00
committergravatar for kbutcher6200@gmail.comkprotty <kbutcher6200@gmail.com> 2019-11-05 15:58:58-06:00
log465ebf494df070ddb7eab9ba7787aacec394b1cc
treea5d8b0a45d17808b85be6dff1102ed7da4812ed4
parent14209cf615f2c218319e0c7a567484caf328dfe6

ThreadParker implementation


4 files changed, 363 insertions(+), 0 deletions(-)

lib/std/os/windows/kernel32.zig+4
...@@ -82,6 +82,8 @@ pub extern "kernel32" stdcallcc fn GetFileAttributesW(lpFileName: [*]const WCHAR...@@ -82,6 +82,8 @@ pub extern "kernel32" stdcallcc fn GetFileAttributesW(lpFileName: [*]const WCHAR
8282
83pub extern "kernel32" stdcallcc fn GetModuleFileNameW(hModule: ?HMODULE, lpFilename: [*]u16, nSize: DWORD) DWORD;83pub extern "kernel32" stdcallcc fn GetModuleFileNameW(hModule: ?HMODULE, lpFilename: [*]u16, nSize: DWORD) DWORD;
8484
85pub extern "kernel32" stdcallcc fn GetModuleHandleA(lpModuleName: ?[*]const CHAR) ?HMODULE;
86
85pub extern "kernel32" stdcallcc fn GetModuleHandleW(lpModuleName: ?[*]const WCHAR) HMODULE;87pub extern "kernel32" stdcallcc fn GetModuleHandleW(lpModuleName: ?[*]const WCHAR) HMODULE;
8688
87pub extern "kernel32" stdcallcc fn GetLastError() DWORD;89pub extern "kernel32" stdcallcc fn GetLastError() DWORD;
...@@ -204,6 +206,8 @@ pub extern "kernel32" stdcallcc fn WriteFile(...@@ -204,6 +206,8 @@ pub extern "kernel32" stdcallcc fn WriteFile(
204206
205pub extern "kernel32" stdcallcc fn WriteFileEx(hFile: HANDLE, lpBuffer: [*]const u8, nNumberOfBytesToWrite: DWORD, lpOverlapped: LPOVERLAPPED, lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE) BOOL;207pub extern "kernel32" stdcallcc fn WriteFileEx(hFile: HANDLE, lpBuffer: [*]const u8, nNumberOfBytesToWrite: DWORD, lpOverlapped: LPOVERLAPPED, lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE) BOOL;
206208
209pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: [*]const u8) ?HMODULE;
210
207pub extern "kernel32" stdcallcc fn LoadLibraryW(lpLibFileName: [*]const u16) ?HMODULE;211pub extern "kernel32" stdcallcc fn LoadLibraryW(lpLibFileName: [*]const u16) ?HMODULE;
208212
209pub extern "kernel32" stdcallcc fn GetProcAddress(hModule: HMODULE, lpProcName: [*]const u8) ?FARPROC;213pub extern "kernel32" stdcallcc fn GetProcAddress(hModule: HMODULE, lpProcName: [*]const u8) ?FARPROC;
lib/std/os/windows/ntdll.zig+18
...@@ -43,3 +43,21 @@ pub extern "NtDll" stdcallcc fn NtQueryDirectoryFile(...@@ -43,3 +43,21 @@ pub extern "NtDll" stdcallcc fn NtQueryDirectoryFile(
43 FileName: ?*UNICODE_STRING,43 FileName: ?*UNICODE_STRING,
44 RestartScan: BOOLEAN,44 RestartScan: BOOLEAN,
45) NTSTATUS;45) NTSTATUS;
46pub extern "NtDll" stdcallcc fn NtCreateKeyedEvent(
47 KeyedEventHandle: *HANDLE,
48 DesiredAccess: ACCESS_MASK,
49 ObjectAttributes: ?PVOID,
50 Flags: ULONG,
51) NTSTATUS;
52pub extern "NtDll" stdcallcc fn NtReleaseKeyedEvent(
53 EventHandle: HANDLE,
54 Key: *const c_void,
55 Alertable: BOOLEAN,
56 Timeout: ?*LARGE_INTEGER,
57) NTSTATUS;
58pub extern "NtDll" stdcallcc fn NtWaitForKeyedEvent(
59 EventHandle: HANDLE,
60 Key: *const c_void,
61 Alertable: BOOLEAN,
62 Timeout: ?*LARGE_INTEGER,
63) NTSTATUS;
lib/std/parker.zig created+340
...@@ -0,0 +1,340 @@
1const std = @import("std.zig");
2const builtin = @import("builtin");
3const testing = std.testing;
4const assert = std.debug.assert;
5const SpinLock = std.SpinLock;
6const linux = std.os.linux;
7const windows = std.os.windows;
8
9pub const ThreadParker = switch (builtin.os) {
10 .macosx,
11 .tvos,
12 .ios,
13 .watchos,
14 .netbsd,
15 .openbsd,
16 .freebsd,
17 .kfreebsd,
18 .dragonfly,
19 .haiku,
20 .hermit,
21 .solaris,
22 .minix,
23 .fuchsia,
24 .emscripten => if (builtin.link_libc) PosixParker else SpinParker,
25 .linux => if (builtin.link_libc) PosixParker else LinuxParker,
26 .windows => WindowsParker,
27 else => SpinParker,
28};
29
30const SpinParker = struct {
31 pub fn init() SpinParker {
32 return SpinParker{};
33 }
34 pub fn deinit(self: *SpinParker) void {}
35
36 pub fn unpark(self: *SpinParker, ptr: *const u32) void {}
37
38 pub fn park(self: *SpinParker, ptr: *const u32, expected: u32) void {
39 var backoff = SpinLock.Backoff.init();
40 while (@atomicLoad(u32, ptr, .Acquire) == expected)
41 backoff.yield();
42 }
43};
44
45const LinuxParker = struct {
46 pub fn init() LinuxParker {
47 return LinuxParker{};
48 }
49 pub fn deinit(self: *LinuxParker) void {}
50
51 pub fn unpark(self: *LinuxParker, ptr: *const u32) void {
52 const rc = linux.futex_wake(@ptrCast(*const i32, ptr), linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, 1);
53 assert(linux.getErrno(rc) == 0);
54 }
55
56 pub fn park(self: *LinuxParker, ptr: *const u32, expected: u32) void {
57 const value = @intCast(i32, expected);
58 while (@atomicLoad(u32, ptr, .Acquire) == expected) {
59 const rc = linux.futex_wait(@ptrCast(*const i32, ptr), linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, value, null);
60 switch (linux.getErrno(rc)) {
61 0, linux.EAGAIN => return,
62 linux.EINTR => continue,
63 linux.EINVAL => unreachable,
64 else => unreachable,
65 }
66 }
67 }
68};
69
70const WindowsParker = struct {
71 waiters: u32,
72
73 pub fn init() WindowsParker {
74 return WindowsParker{ .waiters = 0 };
75 }
76 pub fn deinit(self: *WindowsParker) void {}
77
78 pub fn unpark(self: *WindowsParker, ptr: *const u32) void {
79 switch (Backend.get().*) {
80 .WaitAddress => |*backend| backend.unpark(ptr, &self.waiters),
81 .KeyedEvent => |*backend| backend.unpark(ptr, &self.waiters),
82 }
83 }
84
85 pub fn park(self: *WindowsParker, ptr: *const u32, expected: u32) void {
86 switch (Backend.get().*) {
87 .WaitAddress => |*backend| backend.park(ptr, expected, &self.waiters),
88 .KeyedEvent => |*backend| backend.park(ptr, expected, &self.waiters),
89 }
90 }
91
92 const Backend = union(enum) {
93 WaitAddress: WaitAddress,
94 KeyedEvent: KeyedEvent,
95
96 var backend = std.lazyInit(Backend);
97
98 fn get() *const Backend {
99 return backend.get() orelse {
100 // Statically linking to the KeyedEvent functions should mean its supported.
101 // TODO: Maybe add a CreateSemaphore backend for systems older than Windows XP ?
102 backend.data = WaitAddress.init()
103 orelse KeyedEvent.init()
104 orelse unreachable;
105 backend.resolve();
106 return &backend.data;
107 };
108 }
109
110 const WaitAddress = struct {
111 WakeByAddressSingle: stdcallcc fn(Address: *const c_void) void,
112 WaitOnAddress: stdcallcc fn (
113 Address: *const c_void,
114 CompareAddress: *const c_void,
115 AddressSize: windows.SIZE_T,
116 dwMilliseconds: windows.DWORD,
117 ) windows.BOOL,
118
119 fn init() ?Backend {
120 const dll_name = c"api-ms-win-core-synch-l1-2-0";
121 const dll = windows.kernel32.GetModuleHandleA(dll_name)
122 orelse windows.kernel32.LoadLibraryA(dll_name)
123 orelse return null;
124
125 var self: WaitAddress = undefined;
126 const WaitOnAddress = windows.kernel32.GetProcAddress(dll, c"WaitOnAddress") orelse return null;
127 self.WaitOnAddress = @intToPtr(@typeOf(self.WaitOnAddress), @ptrToInt(WaitOnAddress));
128 const WakeByAddressSingle = windows.kernel32.GetProcAddress(dll, c"WakeByAddressSingle") orelse return null;
129 self.WakeByAddressSingle = @intToPtr(@typeOf(self.WakeByAddressSingle), @ptrToInt(WakeByAddressSingle));
130 return Backend{ .WaitAddress = self };
131 }
132
133 fn unpark(self: WaitAddress, ptr: *const u32, waiters: *u32) void {
134 const addr = @ptrCast(*const c_void, ptr);
135 self.WakeByAddressSingle(addr);
136 }
137
138 fn park(self: WaitAddress, ptr: *const u32, expected: u32, waiters: *u32) void {
139 var compare = expected;
140 const addr = @ptrCast(*const c_void, ptr);
141 const cmp = @ptrCast(*const c_void, &compare);
142 while (@atomicLoad(u32, ptr, .Acquire) == expected)
143 _ = self.WaitOnAddress(addr, cmp, @sizeOf(u32), windows.INFINITE);
144 }
145 };
146
147 const KeyedEvent = struct {
148 handle: windows.HANDLE,
149
150 fn init() ?Backend {
151 var self: KeyedEvent = undefined;
152 const access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE;
153 if (windows.ntdll.NtCreateKeyedEvent(&self.handle, access_mask, null, 0) != 0)
154 return null;
155 return Backend{ .KeyedEvent = self };
156 }
157
158 fn unpark(self: KeyedEvent, ptr: *const u32, waiters: *u32) void {
159 const key = @ptrCast(*const c_void, ptr);
160 var waiting = @atomicLoad(u32, waiters, .Acquire);
161 while (waiting != 0) {
162 waiting = @cmpxchgWeak(u32, waiters, waiting, waiting - 1, .AcqRel, .Monotonic) orelse {
163 const rc = windows.ntdll.NtReleaseKeyedEvent(self.handle, key, windows.FALSE, null);
164 assert(rc == 0);
165 return;
166 };
167 }
168 }
169
170 fn park(self: KeyedEvent, ptr: *const u32, expected: u32, waiters: *u32) void {
171 const key = @ptrCast(*const c_void, ptr);
172 while (@atomicLoad(u32, ptr, .Acquire) == expected) {
173 _ = @atomicRmw(u32, waiters, .Add, 1, .Release);
174 const rc = windows.ntdll.NtWaitForKeyedEvent(self.handle, key, windows.FALSE, null);
175 assert(rc == 0);
176 }
177 }
178 };
179 };
180};
181
182const PosixParker = struct {
183 cond: pthread_cond_t,
184 mutex: pthread_mutex_t,
185
186 pub fn init() PosixParker {
187 return PosixParker{
188 .cond = PTHREAD_COND_INITIALIZER,
189 .mutex = PTHREAD_MUTEX_INITIALIZER,
190 };
191 }
192
193 pub fn deinit(self: *PosixParker) void {
194 // On dragonfly, the destroy functions return EINVAL if they were initialized statically.
195 const retm = pthread_mutex_destroy(&self.mutex);
196 assert(retm == 0 or retm == (if (builtin.os == .dragonfly) os.EINVAL else 0));
197 const retc = pthread_cond_destroy(&self.cond);
198 assert(retc == 0 or retc == (if (builtin.os == .dragonfly) os.EINVAL else 0));
199 }
200
201 pub fn unpark(self: *PosixParker, ptr: *const u32) void {
202 assert(pthread_cond_signal(&self.cond) == 0);
203 }
204
205 pub fn park(self: *PosixParker, ptr: *const u32, expected: u32) void {
206 assert(pthread_mutex_lock(&self.mutex) == 0);
207 defer assert(pthread_mutex_unlock(&self.mutex) == 0);
208 while (@atomicLoad(u32, ptr, .Acquire) == expected)
209 assert(pthread_cond_wait(&self.cond, &self.mutex) == 0);
210 }
211
212 const PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{};
213 extern "c" fn pthread_mutex_lock(mutex: *pthread_mutex_t) c_int;
214 extern "c" fn pthread_mutex_unlock(mutex: *pthread_mutex_t) c_int;
215 extern "c" fn pthread_mutex_destroy(mutex: *pthread_mutex_t) c_int;
216
217 const PTHREAD_COND_INITIALIZER = pthread_cond_t{};
218 extern "c" fn pthread_cond_wait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t) c_int;
219 extern "c" fn pthread_cond_signal(cond: *pthread_cond_t) c_int;
220 extern "c" fn pthread_cond_destroy(cond: *pthread_cond_t) c_int;
221
222 // https://github.com/rust-lang/libc
223 usingnamespace switch (builtin.os) {
224 .macosx, .tvos, .ios, .watchos => struct {
225 pub const pthread_mutex_t = extern struct {
226 __sig: c_long = 0x32AAABA7,
227 __opaque: [__PTHREAD_MUTEX_SIZE__]u8 = [_]u8{0} ** __PTHREAD_MUTEX_SIZE__,
228 };
229 pub const pthread_cond_t = extern struct {
230 __sig: c_long = 0x3CB0B1BB,
231 __opaque: [__PTHREAD_COND_SIZE__]u8 = [_]u8{0} ** __PTHREAD_COND_SIZE__,
232 };
233 const __PTHREAD_MUTEX_SIZE__ = if (@sizeOf(usize) == 8) 56 else 40;
234 const __PTHREAD_COND_SIZE__ = if (@sizeOf(usize) == 8) 40 else 24;
235 },
236 .netbsd => struct {
237 pub const pthread_mutex_t = extern struct {
238 ptm_magic: c_uint = 0x33330003,
239 ptm_errorcheck: padded_spin_t = 0,
240 ptm_unused: padded_spin_t = 0,
241 ptm_owner: usize = 0,
242 ptm_waiters: ?*u8 = null,
243 ptm_recursed: c_uint = 0,
244 ptm_spare2: ?*c_void = null,
245 };
246 pub const pthread_cond_t = extern struct {
247 ptc_magic: c_uint = 0x55550005,
248 ptc_lock: pthread_spin_t = 0,
249 ptc_waiters_first: ?*u8 = null,
250 ptc_waiters_last: ?*u8 = null,
251 ptc_mutex: ?*pthread_mutex_t = null,
252 ptc_private: ?*c_void = null,
253 };
254 const pthread_spin_t = if (builtin.arch == .arm or .arch == .powerpc) c_int else u8;
255 const padded_spin_t = switch (builtin.arch) {
256 .sparc, .sparcel, .sparcv9, .i386, .x86_64, .le64 => u32,
257 else => spin_t,
258 };
259 },
260 .openbsd, .freebsd, .kfreebsd, .dragonfly => struct {
261 pub const pthread_mutex_t = extern struct {
262 inner: ?*c_void = null,
263 };
264 pub const pthread_cond_t = extern struct {
265 inner: ?*c_void = null,
266 };
267 },
268 .haiku => struct {
269 pub const pthread_mutex_t = extern struct {
270 flags: u32 = 0,
271 lock: i32 = 0,
272 unused: i32 = -42,
273 owner: i32 = -1,
274 owner_count: i32 = 0,
275 };
276 pub const pthread_cond_t = extern struct {
277 flags: u32 = 0,
278 unused: i32 = -42,
279 mutex: ?*c_void = null,
280 waiter_count: i32 = 0,
281 lock: i32 = 0,
282 };
283 },
284 .hermit => struct {
285 pub const pthread_mutex_t = extern struct {
286 inner: usize = ~usize(0),
287 };
288 pub const pthread_cond_t = extern struct {
289 inner: usize = ~usize(0),
290 };
291 },
292 .solaris => struct {
293 pub const pthread_mutex_t = extern struct {
294 __pthread_mutex_flag1: u16 = 0,
295 __pthread_mutex_flag2: u8 = 0,
296 __pthread_mutex_ceiling: u8 = 0,
297 __pthread_mutex_type: u16 = 0,
298 __pthread_mutex_magic: u16 = 0x4d58,
299 __pthread_mutex_lock: u64 = 0,
300 __pthread_mutex_data: u64 = 0,
301 };
302 pub const pthread_cond_t = extern struct {
303 __pthread_cond_flag: u32 = 0,
304 __pthread_cond_type: u16 = 0,
305 __pthread_cond_magic: u16 = 0x4356,
306 __pthread_cond_data: u64 = 0,
307 };
308 },
309 .fuchsia, .minix, .linux => struct {
310 pub const pthread_mutex_t = extern struct {
311 size: [__SIZEOF_PTHREAD_MUTEX_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_MUTEX_T,
312 };
313 pub const pthread_cond_t = extern struct {
314 size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T,
315 };
316 const __SIZEOF_PTHREAD_COND_T = 48;
317 const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os == .fuchsia) 40 else switch (builtin.abi) {
318 .musl, .musleabi, .musleabihf => if (@sizeOf(usize) == 8) 40 else 24,
319 .gnu, .gnuabin32, .gnuabi64, .gnueabi, .gnueabihf, .gnux32 => switch (builtin.arch) {
320 .aarch64 => 48,
321 .x86_64 => if (builtin.abi == .gnux32) 40 else 32,
322 .mips64, .powerpc64, .powerpc64le, .sparcv9 => 40,
323 else => if (@sizeOf(usize) == 8) 40 else 24,
324 },
325 else => unreachable,
326 };
327 },
328 .emscripten => struct {
329 pub const pthread_mutex_t = extern struct {
330 size: [__SIZEOF_PTHREAD_MUTEX_T]u8 align(4) = [_]u8{0} ** __SIZEOF_PTHREAD_MUTEX_T,
331 };
332 pub const pthread_cond_t = extern struct {
333 size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T,
334 };
335 const __SIZEOF_PTHREAD_COND_T = 48;
336 const __SIZEOF_PTHREAD_MUTEX_T = 28;
337 },
338 else => unreachable,
339 };
340};
lib/std/std.zig+1
...@@ -23,6 +23,7 @@ pub const StringHashMap = @import("hash_map.zig").StringHashMap;...@@ -23,6 +23,7 @@ pub const StringHashMap = @import("hash_map.zig").StringHashMap;
23pub const TailQueue = @import("linked_list.zig").TailQueue;23pub const TailQueue = @import("linked_list.zig").TailQueue;
24pub const Target = @import("target.zig").Target;24pub const Target = @import("target.zig").Target;
25pub const Thread = @import("thread.zig").Thread;25pub const Thread = @import("thread.zig").Thread;
26pub const ThreadParker = @import("parker.zig").ThreadParker;
2627
27pub const atomic = @import("atomic.zig");28pub const atomic = @import("atomic.zig");
28pub const base64 = @import("base64.zig");29pub const base64 = @import("base64.zig");