| ... | ... | @@ -0,0 +1,340 @@ |
| 1 | const std = @import("std.zig"); |
| 2 | const builtin = @import("builtin"); |
| 3 | const testing = std.testing; |
| 4 | const assert = std.debug.assert; |
| 5 | const SpinLock = std.SpinLock; |
| 6 | const linux = std.os.linux; |
| 7 | const windows = std.os.windows; |
| 8 | |
| 9 | pub 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 | |
| 30 | const 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 | |
| 45 | const 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 | |
| 70 | const 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 | |
| 182 | const 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 | }; |