authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-23 04:55:28-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-11-23 04:55:28-05:00
log2bffd810157a8e7c0b2500a13921dd8c45694b8a
tree2f8cfbc0403c5bd492ca68f1aa7ba6b02886ef2c
parent115ec25f2e4eed5033f34eaee8bf3477ff417ecc
parent70931dbdea96d92feb60406c827e39e566317863
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #18085 from ziglang/std-atomics

rework std.atomic

17 files changed, 441 insertions(+), 1330 deletions(-)

CMakeLists.txt-3
......@@ -209,9 +209,6 @@ set(ZIG_STAGE2_SOURCES
209209 "${CMAKE_SOURCE_DIR}/lib/std/array_list.zig"
210210 "${CMAKE_SOURCE_DIR}/lib/std/ascii.zig"
211211 "${CMAKE_SOURCE_DIR}/lib/std/atomic.zig"
212 "${CMAKE_SOURCE_DIR}/lib/std/atomic/Atomic.zig"
213 "${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig"
214 "${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig"
215212 "${CMAKE_SOURCE_DIR}/lib/std/base64.zig"
216213 "${CMAKE_SOURCE_DIR}/lib/std/BitStack.zig"
217214 "${CMAKE_SOURCE_DIR}/lib/std/buf_map.zig"
lib/std/Thread.zig+7-8
......@@ -8,7 +8,6 @@ const math = std.math;
88const os = std.os;
99const assert = std.debug.assert;
1010const target = builtin.target;
11const Atomic = std.atomic.Atomic;
1211
1312pub const Futex = @import("Thread/Futex.zig");
1413pub const ResetEvent = @import("Thread/ResetEvent.zig");
......@@ -388,7 +387,7 @@ pub fn yield() YieldError!void {
388387}
389388
390389/// State to synchronize detachment of spawner thread to spawned thread
391const Completion = Atomic(enum(u8) {
390const Completion = std.atomic.Value(enum(u8) {
392391 running,
393392 detached,
394393 completed,
......@@ -746,7 +745,7 @@ const WasiThreadImpl = struct {
746745
747746 const WasiThread = struct {
748747 /// Thread ID
749 tid: Atomic(i32) = Atomic(i32).init(0),
748 tid: std.atomic.Value(i32) = std.atomic.Value(i32).init(0),
750749 /// Contains all memory which was allocated to bootstrap this thread, including:
751750 /// - Guard page
752751 /// - Stack
......@@ -784,7 +783,7 @@ const WasiThreadImpl = struct {
784783 original_stack_pointer: [*]u8,
785784 };
786785
787 const State = Atomic(enum(u8) { running, completed, detached });
786 const State = std.atomic.Value(enum(u8) { running, completed, detached });
788787
789788 fn getCurrentId() Id {
790789 return tls_thread_id;
......@@ -1048,7 +1047,7 @@ const LinuxThreadImpl = struct {
10481047
10491048 const ThreadCompletion = struct {
10501049 completion: Completion = Completion.init(.running),
1051 child_tid: Atomic(i32) = Atomic(i32).init(1),
1050 child_tid: std.atomic.Value(i32) = std.atomic.Value(i32).init(1),
10521051 parent_tid: i32 = undefined,
10531052 mapped: []align(std.mem.page_size) u8,
10541053
......@@ -1304,7 +1303,7 @@ const LinuxThreadImpl = struct {
13041303 @intFromPtr(instance),
13051304 &instance.thread.parent_tid,
13061305 tls_ptr,
1307 &instance.thread.child_tid.value,
1306 &instance.thread.child_tid.raw,
13081307 ))) {
13091308 .SUCCESS => return Impl{ .thread = &instance.thread },
13101309 .AGAIN => return error.ThreadQuotaExceeded,
......@@ -1346,7 +1345,7 @@ const LinuxThreadImpl = struct {
13461345 }
13471346
13481347 switch (linux.getErrno(linux.futex_wait(
1349 &self.thread.child_tid.value,
1348 &self.thread.child_tid.raw,
13501349 linux.FUTEX.WAIT,
13511350 tid,
13521351 null,
......@@ -1387,7 +1386,7 @@ test "setName, getName" {
13871386 test_done_event: ResetEvent = .{},
13881387 thread_done_event: ResetEvent = .{},
13891388
1390 done: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(false),
1389 done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
13911390 thread: Thread = undefined,
13921391
13931392 pub fn run(ctx: *@This()) !void {
lib/std/Thread/Condition.zig+6-7
......@@ -50,7 +50,6 @@ const Mutex = std.Thread.Mutex;
5050const os = std.os;
5151const assert = std.debug.assert;
5252const testing = std.testing;
53const Atomic = std.atomic.Atomic;
5453const Futex = std.Thread.Futex;
5554
5655impl: Impl = .{},
......@@ -193,8 +192,8 @@ const WindowsImpl = struct {
193192};
194193
195194const FutexImpl = struct {
196 state: Atomic(u32) = Atomic(u32).init(0),
197 epoch: Atomic(u32) = Atomic(u32).init(0),
195 state: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),
196 epoch: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),
198197
199198 const one_waiter = 1;
200199 const waiter_mask = 0xffff;
......@@ -232,12 +231,12 @@ const FutexImpl = struct {
232231 // Acquire barrier ensures code before the wake() which added the signal happens before we decrement it and return.
233232 while (state & signal_mask != 0) {
234233 const new_state = state - one_waiter - one_signal;
235 state = self.state.tryCompareAndSwap(state, new_state, .Acquire, .Monotonic) orelse return;
234 state = self.state.cmpxchgWeak(state, new_state, .Acquire, .Monotonic) orelse return;
236235 }
237236
238237 // Remove the waiter we added and officially return timed out.
239238 const new_state = state - one_waiter;
240 state = self.state.tryCompareAndSwap(state, new_state, .Monotonic, .Monotonic) orelse return err;
239 state = self.state.cmpxchgWeak(state, new_state, .Monotonic, .Monotonic) orelse return err;
241240 }
242241 },
243242 };
......@@ -249,7 +248,7 @@ const FutexImpl = struct {
249248 // Acquire barrier ensures code before the wake() which added the signal happens before we decrement it and return.
250249 while (state & signal_mask != 0) {
251250 const new_state = state - one_waiter - one_signal;
252 state = self.state.tryCompareAndSwap(state, new_state, .Acquire, .Monotonic) orelse return;
251 state = self.state.cmpxchgWeak(state, new_state, .Acquire, .Monotonic) orelse return;
253252 }
254253 }
255254 }
......@@ -276,7 +275,7 @@ const FutexImpl = struct {
276275 // Reserve the amount of waiters to wake by incrementing the signals count.
277276 // Release barrier ensures code before the wake() happens before the signal it posted and consumed by the wait() threads.
278277 const new_state = state + (one_signal * to_wake);
279 state = self.state.tryCompareAndSwap(state, new_state, .Release, .Monotonic) orelse {
278 state = self.state.cmpxchgWeak(state, new_state, .Release, .Monotonic) orelse {
280279 // Wake up the waiting threads we reserved above by changing the epoch value.
281280 // NOTE: a waiting thread could miss a wake up if *exactly* ((1<<32)-1) wake()s happen between it observing the epoch and sleeping on it.
282281 // This is very unlikely due to how many precise amount of Futex.wake() calls that would be between the waiting thread's potential preemption.
lib/std/Thread/Futex.zig+37-37
......@@ -10,7 +10,7 @@ const Futex = @This();
1010const os = std.os;
1111const assert = std.debug.assert;
1212const testing = std.testing;
13const Atomic = std.atomic.Atomic;
13const atomic = std.atomic;
1414
1515/// Checks if `ptr` still contains the value `expect` and, if so, blocks the caller until either:
1616/// - The value at `ptr` is no longer equal to `expect`.
......@@ -19,7 +19,7 @@ const Atomic = std.atomic.Atomic;
1919///
2020/// The checking of `ptr` and `expect`, along with blocking the caller, is done atomically
2121/// and totally ordered (sequentially consistent) with respect to other wait()/wake() calls on the same `ptr`.
22pub fn wait(ptr: *const Atomic(u32), expect: u32) void {
22pub fn wait(ptr: *const atomic.Value(u32), expect: u32) void {
2323 @setCold(true);
2424
2525 Impl.wait(ptr, expect, null) catch |err| switch (err) {
......@@ -35,7 +35,7 @@ pub fn wait(ptr: *const Atomic(u32), expect: u32) void {
3535///
3636/// The checking of `ptr` and `expect`, along with blocking the caller, is done atomically
3737/// and totally ordered (sequentially consistent) with respect to other wait()/wake() calls on the same `ptr`.
38pub fn timedWait(ptr: *const Atomic(u32), expect: u32, timeout_ns: u64) error{Timeout}!void {
38pub fn timedWait(ptr: *const atomic.Value(u32), expect: u32, timeout_ns: u64) error{Timeout}!void {
3939 @setCold(true);
4040
4141 // Avoid calling into the OS for no-op timeouts.
......@@ -48,7 +48,7 @@ pub fn timedWait(ptr: *const Atomic(u32), expect: u32, timeout_ns: u64) error{Ti
4848}
4949
5050/// Unblocks at most `max_waiters` callers blocked in a `wait()` call on `ptr`.
51pub fn wake(ptr: *const Atomic(u32), max_waiters: u32) void {
51pub fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
5252 @setCold(true);
5353
5454 // Avoid calling into the OS if there's nothing to wake up.
......@@ -83,11 +83,11 @@ else
8383/// We can't do @compileError() in the `Impl` switch statement above as its eagerly evaluated.
8484/// So instead, we @compileError() on the methods themselves for platforms which don't support futex.
8585const UnsupportedImpl = struct {
86 fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
86 fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
8787 return unsupported(.{ ptr, expect, timeout });
8888 }
8989
90 fn wake(ptr: *const Atomic(u32), max_waiters: u32) void {
90 fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
9191 return unsupported(.{ ptr, max_waiters });
9292 }
9393
......@@ -98,8 +98,8 @@ const UnsupportedImpl = struct {
9898};
9999
100100const SingleThreadedImpl = struct {
101 fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
102 if (ptr.loadUnchecked() != expect) {
101 fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
102 if (ptr.raw != expect) {
103103 return;
104104 }
105105
......@@ -113,7 +113,7 @@ const SingleThreadedImpl = struct {
113113 return error.Timeout;
114114 }
115115
116 fn wake(ptr: *const Atomic(u32), max_waiters: u32) void {
116 fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
117117 // There are no other threads to possibly wake up
118118 _ = ptr;
119119 _ = max_waiters;
......@@ -123,7 +123,7 @@ const SingleThreadedImpl = struct {
123123// We use WaitOnAddress through NtDll instead of API-MS-Win-Core-Synch-l1-2-0.dll
124124// as it's generally already a linked target and is autoloaded into all processes anyway.
125125const WindowsImpl = struct {
126 fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
126 fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
127127 var timeout_value: os.windows.LARGE_INTEGER = undefined;
128128 var timeout_ptr: ?*const os.windows.LARGE_INTEGER = null;
129129
......@@ -152,7 +152,7 @@ const WindowsImpl = struct {
152152 }
153153 }
154154
155 fn wake(ptr: *const Atomic(u32), max_waiters: u32) void {
155 fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
156156 const address: ?*const anyopaque = ptr;
157157 assert(max_waiters != 0);
158158
......@@ -164,7 +164,7 @@ const WindowsImpl = struct {
164164};
165165
166166const DarwinImpl = struct {
167 fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
167 fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
168168 // Darwin XNU 7195.50.7.100.1 introduced __ulock_wait2 and migrated code paths (notably pthread_cond_t) towards it:
169169 // https://github.com/apple/darwin-xnu/commit/d4061fb0260b3ed486147341b72468f836ed6c8f#diff-08f993cc40af475663274687b7c326cc6c3031e0db3ac8de7b24624610616be6
170170 //
......@@ -220,7 +220,7 @@ const DarwinImpl = struct {
220220 }
221221 }
222222
223 fn wake(ptr: *const Atomic(u32), max_waiters: u32) void {
223 fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
224224 var flags: u32 = os.darwin.UL_COMPARE_AND_WAIT | os.darwin.ULF_NO_ERRNO;
225225 if (max_waiters > 1) {
226226 flags |= os.darwin.ULF_WAKE_ALL;
......@@ -244,7 +244,7 @@ const DarwinImpl = struct {
244244
245245// https://man7.org/linux/man-pages/man2/futex.2.html
246246const LinuxImpl = struct {
247 fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
247 fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
248248 var ts: os.timespec = undefined;
249249 if (timeout) |timeout_ns| {
250250 ts.tv_sec = @as(@TypeOf(ts.tv_sec), @intCast(timeout_ns / std.time.ns_per_s));
......@@ -252,7 +252,7 @@ const LinuxImpl = struct {
252252 }
253253
254254 const rc = os.linux.futex_wait(
255 @as(*const i32, @ptrCast(&ptr.value)),
255 @as(*const i32, @ptrCast(&ptr.raw)),
256256 os.linux.FUTEX.PRIVATE_FLAG | os.linux.FUTEX.WAIT,
257257 @as(i32, @bitCast(expect)),
258258 if (timeout != null) &ts else null,
......@@ -272,9 +272,9 @@ const LinuxImpl = struct {
272272 }
273273 }
274274
275 fn wake(ptr: *const Atomic(u32), max_waiters: u32) void {
275 fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
276276 const rc = os.linux.futex_wake(
277 @as(*const i32, @ptrCast(&ptr.value)),
277 @as(*const i32, @ptrCast(&ptr.raw)),
278278 os.linux.FUTEX.PRIVATE_FLAG | os.linux.FUTEX.WAKE,
279279 std.math.cast(i32, max_waiters) orelse std.math.maxInt(i32),
280280 );
......@@ -290,7 +290,7 @@ const LinuxImpl = struct {
290290
291291// https://www.freebsd.org/cgi/man.cgi?query=_umtx_op&sektion=2&n=1
292292const FreebsdImpl = struct {
293 fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
293 fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
294294 var tm_size: usize = 0;
295295 var tm: os.freebsd._umtx_time = undefined;
296296 var tm_ptr: ?*const os.freebsd._umtx_time = null;
......@@ -326,7 +326,7 @@ const FreebsdImpl = struct {
326326 }
327327 }
328328
329 fn wake(ptr: *const Atomic(u32), max_waiters: u32) void {
329 fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
330330 const rc = os.freebsd._umtx_op(
331331 @intFromPtr(&ptr.value),
332332 @intFromEnum(os.freebsd.UMTX_OP.WAKE_PRIVATE),
......@@ -346,7 +346,7 @@ const FreebsdImpl = struct {
346346
347347// https://man.openbsd.org/futex.2
348348const OpenbsdImpl = struct {
349 fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
349 fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
350350 var ts: os.timespec = undefined;
351351 if (timeout) |timeout_ns| {
352352 ts.tv_sec = @as(@TypeOf(ts.tv_sec), @intCast(timeout_ns / std.time.ns_per_s));
......@@ -377,7 +377,7 @@ const OpenbsdImpl = struct {
377377 }
378378 }
379379
380 fn wake(ptr: *const Atomic(u32), max_waiters: u32) void {
380 fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
381381 const rc = os.openbsd.futex(
382382 @as(*const volatile u32, @ptrCast(&ptr.value)),
383383 os.openbsd.FUTEX_WAKE | os.openbsd.FUTEX_PRIVATE_FLAG,
......@@ -393,7 +393,7 @@ const OpenbsdImpl = struct {
393393
394394// https://man.dragonflybsd.org/?command=umtx&section=2
395395const DragonflyImpl = struct {
396 fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
396 fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
397397 // Dragonfly uses a scheme where 0 timeout means wait until signaled or spurious wake.
398398 // It's reporting of timeout's is also unrealiable so we use an external timing source (Timer) instead.
399399 var timeout_us: c_int = 0;
......@@ -435,7 +435,7 @@ const DragonflyImpl = struct {
435435 }
436436 }
437437
438 fn wake(ptr: *const Atomic(u32), max_waiters: u32) void {
438 fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
439439 // A count of zero means wake all waiters.
440440 assert(max_waiters != 0);
441441 const to_wake = std.math.cast(c_int, max_waiters) orelse 0;
......@@ -449,7 +449,7 @@ const DragonflyImpl = struct {
449449};
450450
451451const WasmImpl = struct {
452 fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
452 fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
453453 if (!comptime std.Target.wasm.featureSetHas(builtin.target.cpu.features, .atomics)) {
454454 @compileError("WASI target missing cpu feature 'atomics'");
455455 }
......@@ -473,7 +473,7 @@ const WasmImpl = struct {
473473 }
474474 }
475475
476 fn wake(ptr: *const Atomic(u32), max_waiters: u32) void {
476 fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
477477 if (!comptime std.Target.wasm.featureSetHas(builtin.target.cpu.features, .atomics)) {
478478 @compileError("WASI target missing cpu feature 'atomics'");
479479 }
......@@ -732,8 +732,8 @@ const PosixImpl = struct {
732732 };
733733
734734 const Bucket = struct {
735 mutex: std.c.pthread_mutex_t align(std.atomic.cache_line) = .{},
736 pending: Atomic(usize) = Atomic(usize).init(0),
735 mutex: std.c.pthread_mutex_t align(atomic.cache_line) = .{},
736 pending: atomic.Value(usize) = atomic.Value(usize).init(0),
737737 treap: Treap = .{},
738738
739739 // Global array of buckets that addresses map to.
......@@ -757,9 +757,9 @@ const PosixImpl = struct {
757757 };
758758
759759 const Address = struct {
760 fn from(ptr: *const Atomic(u32)) usize {
760 fn from(ptr: *const atomic.Value(u32)) usize {
761761 // Get the alignment of the pointer.
762 const alignment = @alignOf(Atomic(u32));
762 const alignment = @alignOf(atomic.Value(u32));
763763 comptime assert(std.math.isPowerOfTwo(alignment));
764764
765765 // Make sure the pointer is aligned,
......@@ -770,7 +770,7 @@ const PosixImpl = struct {
770770 }
771771 };
772772
773 fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
773 fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void {
774774 const address = Address.from(ptr);
775775 const bucket = Bucket.from(address);
776776
......@@ -831,7 +831,7 @@ const PosixImpl = struct {
831831 };
832832 }
833833
834 fn wake(ptr: *const Atomic(u32), max_waiters: u32) void {
834 fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
835835 const address = Address.from(ptr);
836836 const bucket = Bucket.from(address);
837837
......@@ -882,7 +882,7 @@ const PosixImpl = struct {
882882};
883883
884884test "Futex - smoke test" {
885 var value = Atomic(u32).init(0);
885 var value = atomic.Value(u32).init(0);
886886
887887 // Try waits with invalid values.
888888 Futex.wait(&value, 0xdeadbeef);
......@@ -908,7 +908,7 @@ test "Futex - signaling" {
908908 const num_iterations = 4;
909909
910910 const Paddle = struct {
911 value: Atomic(u32) = Atomic(u32).init(0),
911 value: atomic.Value(u32) = atomic.Value(u32).init(0),
912912 current: u32 = 0,
913913
914914 fn hit(self: *@This()) void {
......@@ -962,8 +962,8 @@ test "Futex - broadcasting" {
962962 const num_iterations = 4;
963963
964964 const Barrier = struct {
965 count: Atomic(u32) = Atomic(u32).init(num_threads),
966 futex: Atomic(u32) = Atomic(u32).init(0),
965 count: atomic.Value(u32) = atomic.Value(u32).init(num_threads),
966 futex: atomic.Value(u32) = atomic.Value(u32).init(0),
967967
968968 fn wait(self: *@This()) !void {
969969 // Decrement the counter.
......@@ -1036,7 +1036,7 @@ pub const Deadline = struct {
10361036 /// - `Futex.wake()` is called on the `ptr`.
10371037 /// - A spurious wake occurs.
10381038 /// - The deadline expires; In which case `error.Timeout` is returned.
1039 pub fn wait(self: *Deadline, ptr: *const Atomic(u32), expect: u32) error{Timeout}!void {
1039 pub fn wait(self: *Deadline, ptr: *const atomic.Value(u32), expect: u32) error{Timeout}!void {
10401040 @setCold(true);
10411041
10421042 // Check if we actually have a timeout to wait until.
......@@ -1056,7 +1056,7 @@ pub const Deadline = struct {
10561056
10571057test "Futex - Deadline" {
10581058 var deadline = Deadline.init(100 * std.time.ns_per_ms);
1059 var futex_word = Atomic(u32).init(0);
1059 var futex_word = atomic.Value(u32).init(0);
10601060
10611061 while (true) {
10621062 deadline.wait(&futex_word, 0) catch break;
lib/std/Thread/Mutex.zig+9-18
......@@ -26,7 +26,6 @@ const Mutex = @This();
2626const os = std.os;
2727const assert = std.debug.assert;
2828const testing = std.testing;
29const Atomic = std.atomic.Atomic;
3029const Thread = std.Thread;
3130const Futex = Thread.Futex;
3231
......@@ -67,7 +66,7 @@ else
6766 FutexImpl;
6867
6968const DebugImpl = struct {
70 locking_thread: Atomic(Thread.Id) = Atomic(Thread.Id).init(0), // 0 means it's not locked.
69 locking_thread: std.atomic.Value(Thread.Id) = std.atomic.Value(Thread.Id).init(0), // 0 means it's not locked.
7170 impl: ReleaseImpl = .{},
7271
7372 inline fn tryLock(self: *@This()) bool {
......@@ -151,37 +150,29 @@ const DarwinImpl = struct {
151150};
152151
153152const FutexImpl = struct {
154 state: Atomic(u32) = Atomic(u32).init(unlocked),
153 state: std.atomic.Value(u32) = std.atomic.Value(u32).init(unlocked),
155154
156 const unlocked = 0b00;
157 const locked = 0b01;
158 const contended = 0b11; // must contain the `locked` bit for x86 optimization below
159
160 fn tryLock(self: *@This()) bool {
161 // Lock with compareAndSwap instead of tryCompareAndSwap to avoid reporting spurious CAS failure.
162 return self.lockFast("compareAndSwap");
163 }
155 const unlocked: u32 = 0b00;
156 const locked: u32 = 0b01;
157 const contended: u32 = 0b11; // must contain the `locked` bit for x86 optimization below
164158
165159 fn lock(self: *@This()) void {
166 // Lock with tryCompareAndSwap instead of compareAndSwap due to being more inline-able on LL/SC archs like ARM.
167 if (!self.lockFast("tryCompareAndSwap")) {
160 if (!self.tryLock())
168161 self.lockSlow();
169 }
170162 }
171163
172 inline fn lockFast(self: *@This(), comptime cas_fn_name: []const u8) bool {
164 fn tryLock(self: *@This()) bool {
173165 // On x86, use `lock bts` instead of `lock cmpxchg` as:
174166 // - they both seem to mark the cache-line as modified regardless: https://stackoverflow.com/a/63350048
175167 // - `lock bts` is smaller instruction-wise which makes it better for inlining
176168 if (comptime builtin.target.cpu.arch.isX86()) {
177 const locked_bit = @ctz(@as(u32, locked));
169 const locked_bit = @ctz(locked);
178170 return self.state.bitSet(locked_bit, .Acquire) == 0;
179171 }
180172
181173 // Acquire barrier ensures grabbing the lock happens before the critical section
182174 // and that the previous lock holder's critical section happens before we grab the lock.
183 const casFn = @field(@TypeOf(self.state), cas_fn_name);
184 return casFn(&self.state, unlocked, locked, .Acquire, .Monotonic) == null;
175 return self.state.cmpxchgWeak(unlocked, locked, .Acquire, .Monotonic) == null;
185176 }
186177
187178 fn lockSlow(self: *@This()) void {
lib/std/Thread/ResetEvent.zig+3-4
......@@ -9,7 +9,6 @@ const ResetEvent = @This();
99const os = std.os;
1010const assert = std.debug.assert;
1111const testing = std.testing;
12const Atomic = std.atomic.Atomic;
1312const Futex = std.Thread.Futex;
1413
1514impl: Impl = .{},
......@@ -89,7 +88,7 @@ const SingleThreadedImpl = struct {
8988};
9089
9190const FutexImpl = struct {
92 state: Atomic(u32) = Atomic(u32).init(unset),
91 state: std.atomic.Value(u32) = std.atomic.Value(u32).init(unset),
9392
9493 const unset = 0;
9594 const waiting = 1;
......@@ -115,7 +114,7 @@ const FutexImpl = struct {
115114 // We avoid using any strict barriers until the end when we know the ResetEvent is set.
116115 var state = self.state.load(.Monotonic);
117116 if (state == unset) {
118 state = self.state.compareAndSwap(state, waiting, .Monotonic, .Monotonic) orelse waiting;
117 state = self.state.cmpxchgStrong(state, waiting, .Monotonic, .Monotonic) orelse waiting;
119118 }
120119
121120 // Wait until the ResetEvent is set since the state is waiting.
......@@ -252,7 +251,7 @@ test "ResetEvent - broadcast" {
252251 const num_threads = 10;
253252 const Barrier = struct {
254253 event: ResetEvent = .{},
255 counter: Atomic(usize) = Atomic(usize).init(num_threads),
254 counter: std.atomic.Value(usize) = std.atomic.Value(usize).init(num_threads),
256255
257256 fn wait(self: *@This()) void {
258257 if (self.counter.fetchSub(1, .AcqRel) == 1) {
lib/std/Thread/RwLock.zig+1-1
......@@ -307,7 +307,7 @@ test "RwLock - concurrent access" {
307307
308308 rwl: RwLock = .{},
309309 writes: usize = 0,
310 reads: std.atomic.Atomic(usize) = std.atomic.Atomic(usize).init(0),
310 reads: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
311311
312312 term1: usize = 0,
313313 term2: usize = 0,
lib/std/Thread/WaitGroup.zig+1-2
......@@ -1,12 +1,11 @@
11const std = @import("std");
2const Atomic = std.atomic.Atomic;
32const assert = std.debug.assert;
43const WaitGroup = @This();
54
65const is_waiting: usize = 1 << 0;
76const one_pending: usize = 1 << 1;
87
9state: Atomic(usize) = Atomic(usize).init(0),
8state: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
109event: std.Thread.ResetEvent = .{},
1110
1211pub fn start(self: *WaitGroup) void {
lib/std/atomic.zig+369-31
......@@ -1,40 +1,374 @@
1const std = @import("std.zig");
2const builtin = @import("builtin");
1/// This is a thin wrapper around a primitive value to prevent accidental data races.
2pub fn Value(comptime T: type) type {
3 return extern struct {
4 /// Care must be taken to avoid data races when interacting with this field directly.
5 raw: T,
6
7 const Self = @This();
8
9 pub fn init(value: T) Self {
10 return .{ .raw = value };
11 }
12
13 /// Perform an atomic fence which uses the atomic value as a hint for
14 /// the modification order. Use this when you want to imply a fence on
15 /// an atomic variable without necessarily performing a memory access.
16 pub inline fn fence(self: *Self, comptime order: AtomicOrder) void {
17 // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.
18 if (builtin.sanitize_thread) {
19 const tsan = struct {
20 extern "c" fn __tsan_acquire(addr: *anyopaque) void;
21 extern "c" fn __tsan_release(addr: *anyopaque) void;
22 };
23
24 const addr: *anyopaque = self;
25 return switch (order) {
26 .Unordered, .Monotonic => @compileError(@tagName(order) ++ " only applies to atomic loads and stores"),
27 .Acquire => tsan.__tsan_acquire(addr),
28 .Release => tsan.__tsan_release(addr),
29 .AcqRel, .SeqCst => {
30 tsan.__tsan_acquire(addr);
31 tsan.__tsan_release(addr);
32 },
33 };
34 }
35
36 return @fence(order);
37 }
38
39 pub inline fn load(self: *const Self, comptime order: AtomicOrder) T {
40 return @atomicLoad(T, &self.raw, order);
41 }
42
43 pub inline fn store(self: *Self, value: T, comptime order: AtomicOrder) void {
44 @atomicStore(T, &self.raw, value, order);
45 }
46
47 pub inline fn swap(self: *Self, operand: T, comptime order: AtomicOrder) T {
48 return @atomicRmw(T, &self.raw, .Xchg, operand, order);
49 }
50
51 pub inline fn cmpxchgWeak(
52 self: *Self,
53 expected_value: T,
54 new_value: T,
55 comptime success_order: AtomicOrder,
56 comptime fail_order: AtomicOrder,
57 ) ?T {
58 return @cmpxchgWeak(T, &self.raw, expected_value, new_value, success_order, fail_order);
59 }
60
61 pub inline fn cmpxchgStrong(
62 self: *Self,
63 expected_value: T,
64 new_value: T,
65 comptime success_order: AtomicOrder,
66 comptime fail_order: AtomicOrder,
67 ) ?T {
68 return @cmpxchgStrong(T, &self.raw, expected_value, new_value, success_order, fail_order);
69 }
70
71 pub inline fn fetchAdd(self: *Self, operand: T, comptime order: AtomicOrder) T {
72 return @atomicRmw(T, &self.raw, .Add, operand, order);
73 }
74
75 pub inline fn fetchSub(self: *Self, operand: T, comptime order: AtomicOrder) T {
76 return @atomicRmw(T, &self.raw, .Sub, operand, order);
77 }
78
79 pub inline fn fetchMin(self: *Self, operand: T, comptime order: AtomicOrder) T {
80 return @atomicRmw(T, &self.raw, .Min, operand, order);
81 }
82
83 pub inline fn fetchMax(self: *Self, operand: T, comptime order: AtomicOrder) T {
84 return @atomicRmw(T, &self.raw, .Max, operand, order);
85 }
86
87 pub inline fn fetchAnd(self: *Self, operand: T, comptime order: AtomicOrder) T {
88 return @atomicRmw(T, &self.raw, .And, operand, order);
89 }
90
91 pub inline fn fetchNand(self: *Self, operand: T, comptime order: AtomicOrder) T {
92 return @atomicRmw(T, &self.raw, .Nand, operand, order);
93 }
94
95 pub inline fn fetchXor(self: *Self, operand: T, comptime order: AtomicOrder) T {
96 return @atomicRmw(T, &self.raw, .Xor, operand, order);
97 }
398
4pub const Ordering = std.builtin.AtomicOrder;
99 pub inline fn fetchOr(self: *Self, operand: T, comptime order: AtomicOrder) T {
100 return @atomicRmw(T, &self.raw, .Or, operand, order);
101 }
5102
6pub const Stack = @import("atomic/stack.zig").Stack;
7pub const Queue = @import("atomic/queue.zig").Queue;
8pub const Atomic = @import("atomic/Atomic.zig").Atomic;
103 pub inline fn rmw(
104 self: *Self,
105 comptime op: std.builtin.AtomicRmwOp,
106 operand: T,
107 comptime order: AtomicOrder,
108 ) T {
109 return @atomicRmw(T, &self.raw, op, operand, order);
110 }
9111
10test {
11 _ = @import("atomic/stack.zig");
12 _ = @import("atomic/queue.zig");
13 _ = @import("atomic/Atomic.zig");
112 const Bit = std.math.Log2Int(T);
113
114 /// Marked `inline` so that if `bit` is comptime-known, the instruction
115 /// can be lowered to a more efficient machine code instruction if
116 /// possible.
117 pub inline fn bitSet(self: *Self, bit: Bit, comptime order: AtomicOrder) u1 {
118 const mask = @as(T, 1) << bit;
119 const value = self.fetchOr(mask, order);
120 return @intFromBool(value & mask != 0);
121 }
122
123 /// Marked `inline` so that if `bit` is comptime-known, the instruction
124 /// can be lowered to a more efficient machine code instruction if
125 /// possible.
126 pub inline fn bitReset(self: *Self, bit: Bit, comptime order: AtomicOrder) u1 {
127 const mask = @as(T, 1) << bit;
128 const value = self.fetchAnd(~mask, order);
129 return @intFromBool(value & mask != 0);
130 }
131
132 /// Marked `inline` so that if `bit` is comptime-known, the instruction
133 /// can be lowered to a more efficient machine code instruction if
134 /// possible.
135 pub inline fn bitToggle(self: *Self, bit: Bit, comptime order: AtomicOrder) u1 {
136 const mask = @as(T, 1) << bit;
137 const value = self.fetchXor(mask, order);
138 return @intFromBool(value & mask != 0);
139 }
140 };
14141}
15142
16pub inline fn fence(comptime ordering: Ordering) void {
17 switch (ordering) {
18 .Acquire, .Release, .AcqRel, .SeqCst => {
19 @fence(ordering);
20 },
21 else => {
22 @compileLog(ordering, " only applies to a given memory location");
23 },
143test Value {
144 const RefCount = struct {
145 count: Value(usize),
146 dropFn: *const fn (*RefCount) void,
147
148 const RefCount = @This();
149
150 fn ref(rc: *RefCount) void {
151 // No ordering necessary; just updating a counter.
152 _ = rc.count.fetchAdd(1, .Monotonic);
153 }
154
155 fn unref(rc: *RefCount) void {
156 // Release ensures code before unref() happens-before the
157 // count is decremented as dropFn could be called by then.
158 if (rc.count.fetchSub(1, .Release) == 1) {
159 // Acquire ensures count decrement and code before
160 // previous unrefs()s happens-before we call dropFn
161 // below.
162 // Another alternative is to use .AcqRel on the
163 // fetchSub count decrement but it's extra barrier in
164 // possibly hot path.
165 rc.count.fence(.Acquire);
166 (rc.dropFn)(rc);
167 }
168 }
169
170 fn noop(rc: *RefCount) void {
171 _ = rc;
172 }
173 };
174
175 var ref_count: RefCount = .{
176 .count = Value(usize).init(0),
177 .dropFn = RefCount.noop,
178 };
179 ref_count.ref();
180 ref_count.unref();
181}
182
183test "Value.swap" {
184 var x = Value(usize).init(5);
185 try testing.expectEqual(@as(usize, 5), x.swap(10, .SeqCst));
186 try testing.expectEqual(@as(usize, 10), x.load(.SeqCst));
187
188 const E = enum(usize) { a, b, c };
189 var y = Value(E).init(.c);
190 try testing.expectEqual(E.c, y.swap(.a, .SeqCst));
191 try testing.expectEqual(E.a, y.load(.SeqCst));
192
193 var z = Value(f32).init(5.0);
194 try testing.expectEqual(@as(f32, 5.0), z.swap(10.0, .SeqCst));
195 try testing.expectEqual(@as(f32, 10.0), z.load(.SeqCst));
196
197 var a = Value(bool).init(false);
198 try testing.expectEqual(false, a.swap(true, .SeqCst));
199 try testing.expectEqual(true, a.load(.SeqCst));
200
201 var b = Value(?*u8).init(null);
202 try testing.expectEqual(@as(?*u8, null), b.swap(@as(?*u8, @ptrFromInt(@alignOf(u8))), .SeqCst));
203 try testing.expectEqual(@as(?*u8, @ptrFromInt(@alignOf(u8))), b.load(.SeqCst));
204}
205
206test "Value.store" {
207 var x = Value(usize).init(5);
208 x.store(10, .SeqCst);
209 try testing.expectEqual(@as(usize, 10), x.load(.SeqCst));
210}
211
212test "Value.cmpxchgWeak" {
213 var x = Value(usize).init(0);
214
215 try testing.expectEqual(@as(?usize, 0), x.cmpxchgWeak(1, 0, .SeqCst, .SeqCst));
216 try testing.expectEqual(@as(usize, 0), x.load(.SeqCst));
217
218 while (x.cmpxchgWeak(0, 1, .SeqCst, .SeqCst)) |_| {}
219 try testing.expectEqual(@as(usize, 1), x.load(.SeqCst));
220
221 while (x.cmpxchgWeak(1, 0, .SeqCst, .SeqCst)) |_| {}
222 try testing.expectEqual(@as(usize, 0), x.load(.SeqCst));
223}
224
225test "Value.cmpxchgStrong" {
226 var x = Value(usize).init(0);
227 try testing.expectEqual(@as(?usize, 0), x.cmpxchgStrong(1, 0, .SeqCst, .SeqCst));
228 try testing.expectEqual(@as(usize, 0), x.load(.SeqCst));
229 try testing.expectEqual(@as(?usize, null), x.cmpxchgStrong(0, 1, .SeqCst, .SeqCst));
230 try testing.expectEqual(@as(usize, 1), x.load(.SeqCst));
231 try testing.expectEqual(@as(?usize, null), x.cmpxchgStrong(1, 0, .SeqCst, .SeqCst));
232 try testing.expectEqual(@as(usize, 0), x.load(.SeqCst));
233}
234
235test "Value.fetchAdd" {
236 var x = Value(usize).init(5);
237 try testing.expectEqual(@as(usize, 5), x.fetchAdd(5, .SeqCst));
238 try testing.expectEqual(@as(usize, 10), x.load(.SeqCst));
239 try testing.expectEqual(@as(usize, 10), x.fetchAdd(std.math.maxInt(usize), .SeqCst));
240 try testing.expectEqual(@as(usize, 9), x.load(.SeqCst));
241}
242
243test "Value.fetchSub" {
244 var x = Value(usize).init(5);
245 try testing.expectEqual(@as(usize, 5), x.fetchSub(5, .SeqCst));
246 try testing.expectEqual(@as(usize, 0), x.load(.SeqCst));
247 try testing.expectEqual(@as(usize, 0), x.fetchSub(1, .SeqCst));
248 try testing.expectEqual(@as(usize, std.math.maxInt(usize)), x.load(.SeqCst));
249}
250
251test "Value.fetchMin" {
252 var x = Value(usize).init(5);
253 try testing.expectEqual(@as(usize, 5), x.fetchMin(0, .SeqCst));
254 try testing.expectEqual(@as(usize, 0), x.load(.SeqCst));
255 try testing.expectEqual(@as(usize, 0), x.fetchMin(10, .SeqCst));
256 try testing.expectEqual(@as(usize, 0), x.load(.SeqCst));
257}
258
259test "Value.fetchMax" {
260 var x = Value(usize).init(5);
261 try testing.expectEqual(@as(usize, 5), x.fetchMax(10, .SeqCst));
262 try testing.expectEqual(@as(usize, 10), x.load(.SeqCst));
263 try testing.expectEqual(@as(usize, 10), x.fetchMax(5, .SeqCst));
264 try testing.expectEqual(@as(usize, 10), x.load(.SeqCst));
265}
266
267test "Value.fetchAnd" {
268 var x = Value(usize).init(0b11);
269 try testing.expectEqual(@as(usize, 0b11), x.fetchAnd(0b10, .SeqCst));
270 try testing.expectEqual(@as(usize, 0b10), x.load(.SeqCst));
271 try testing.expectEqual(@as(usize, 0b10), x.fetchAnd(0b00, .SeqCst));
272 try testing.expectEqual(@as(usize, 0b00), x.load(.SeqCst));
273}
274
275test "Value.fetchNand" {
276 var x = Value(usize).init(0b11);
277 try testing.expectEqual(@as(usize, 0b11), x.fetchNand(0b10, .SeqCst));
278 try testing.expectEqual(~@as(usize, 0b10), x.load(.SeqCst));
279 try testing.expectEqual(~@as(usize, 0b10), x.fetchNand(0b00, .SeqCst));
280 try testing.expectEqual(~@as(usize, 0b00), x.load(.SeqCst));
281}
282
283test "Value.fetchOr" {
284 var x = Value(usize).init(0b11);
285 try testing.expectEqual(@as(usize, 0b11), x.fetchOr(0b100, .SeqCst));
286 try testing.expectEqual(@as(usize, 0b111), x.load(.SeqCst));
287 try testing.expectEqual(@as(usize, 0b111), x.fetchOr(0b010, .SeqCst));
288 try testing.expectEqual(@as(usize, 0b111), x.load(.SeqCst));
289}
290
291test "Value.fetchXor" {
292 var x = Value(usize).init(0b11);
293 try testing.expectEqual(@as(usize, 0b11), x.fetchXor(0b10, .SeqCst));
294 try testing.expectEqual(@as(usize, 0b01), x.load(.SeqCst));
295 try testing.expectEqual(@as(usize, 0b01), x.fetchXor(0b01, .SeqCst));
296 try testing.expectEqual(@as(usize, 0b00), x.load(.SeqCst));
297}
298
299test "Value.bitSet" {
300 var x = Value(usize).init(0);
301
302 for (0..@bitSizeOf(usize)) |bit_index| {
303 const bit = @as(std.math.Log2Int(usize), @intCast(bit_index));
304 const mask = @as(usize, 1) << bit;
305
306 // setting the bit should change the bit
307 try testing.expect(x.load(.SeqCst) & mask == 0);
308 try testing.expectEqual(@as(u1, 0), x.bitSet(bit, .SeqCst));
309 try testing.expect(x.load(.SeqCst) & mask != 0);
310
311 // setting it again shouldn't change the bit
312 try testing.expectEqual(@as(u1, 1), x.bitSet(bit, .SeqCst));
313 try testing.expect(x.load(.SeqCst) & mask != 0);
314
315 // all the previous bits should have not changed (still be set)
316 for (0..bit_index) |prev_bit_index| {
317 const prev_bit = @as(std.math.Log2Int(usize), @intCast(prev_bit_index));
318 const prev_mask = @as(usize, 1) << prev_bit;
319 try testing.expect(x.load(.SeqCst) & prev_mask != 0);
320 }
24321 }
25322}
26323
27pub inline fn compilerFence(comptime ordering: Ordering) void {
28 switch (ordering) {
29 .Acquire, .Release, .AcqRel, .SeqCst => asm volatile ("" ::: "memory"),
30 else => @compileLog(ordering, " only applies to a given memory location"),
324test "Value.bitReset" {
325 var x = Value(usize).init(0);
326
327 for (0..@bitSizeOf(usize)) |bit_index| {
328 const bit = @as(std.math.Log2Int(usize), @intCast(bit_index));
329 const mask = @as(usize, 1) << bit;
330 x.raw |= mask;
331
332 // unsetting the bit should change the bit
333 try testing.expect(x.load(.SeqCst) & mask != 0);
334 try testing.expectEqual(@as(u1, 1), x.bitReset(bit, .SeqCst));
335 try testing.expect(x.load(.SeqCst) & mask == 0);
336
337 // unsetting it again shouldn't change the bit
338 try testing.expectEqual(@as(u1, 0), x.bitReset(bit, .SeqCst));
339 try testing.expect(x.load(.SeqCst) & mask == 0);
340
341 // all the previous bits should have not changed (still be reset)
342 for (0..bit_index) |prev_bit_index| {
343 const prev_bit = @as(std.math.Log2Int(usize), @intCast(prev_bit_index));
344 const prev_mask = @as(usize, 1) << prev_bit;
345 try testing.expect(x.load(.SeqCst) & prev_mask == 0);
346 }
31347 }
32348}
33349
34test "fence/compilerFence" {
35 inline for (.{ .Acquire, .Release, .AcqRel, .SeqCst }) |ordering| {
36 compilerFence(ordering);
37 fence(ordering);
350test "Value.bitToggle" {
351 var x = Value(usize).init(0);
352
353 for (0..@bitSizeOf(usize)) |bit_index| {
354 const bit = @as(std.math.Log2Int(usize), @intCast(bit_index));
355 const mask = @as(usize, 1) << bit;
356
357 // toggling the bit should change the bit
358 try testing.expect(x.load(.SeqCst) & mask == 0);
359 try testing.expectEqual(@as(u1, 0), x.bitToggle(bit, .SeqCst));
360 try testing.expect(x.load(.SeqCst) & mask != 0);
361
362 // toggling it again *should* change the bit
363 try testing.expectEqual(@as(u1, 1), x.bitToggle(bit, .SeqCst));
364 try testing.expect(x.load(.SeqCst) & mask == 0);
365
366 // all the previous bits should have not changed (still be toggled back)
367 for (0..bit_index) |prev_bit_index| {
368 const prev_bit = @as(std.math.Log2Int(usize), @intCast(prev_bit_index));
369 const prev_mask = @as(usize, 1) << prev_bit;
370 try testing.expect(x.load(.SeqCst) & prev_mask == 0);
371 }
38372 }
39373}
40374
......@@ -74,9 +408,8 @@ pub inline fn spinLoopHint() void {
74408 }
75409}
76410
77test "spinLoopHint" {
78 var i: usize = 10;
79 while (i > 0) : (i -= 1) {
411test spinLoopHint {
412 for (0..10) |_| {
80413 spinLoopHint();
81414 }
82415}
......@@ -85,8 +418,8 @@ test "spinLoopHint" {
85418/// Add this much padding or align to this boundary to avoid atomically-updated
86419/// memory from forcing cache invalidations on near, but non-atomic, memory.
87420///
88// https://en.wikipedia.org/wiki/False_sharing
89// https://github.com/golang/go/search?q=CacheLinePadSize
421/// https://en.wikipedia.org/wiki/False_sharing
422/// https://github.com/golang/go/search?q=CacheLinePadSize
90423pub const cache_line = switch (builtin.cpu.arch) {
91424 // x86_64: Starting from Intel's Sandy Bridge, the spatial prefetcher pulls in pairs of 64-byte cache lines at a time.
92425 // - https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf
......@@ -118,3 +451,8 @@ pub const cache_line = switch (builtin.cpu.arch) {
118451 // - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_wasm.go#L7
119452 else => 64,
120453};
454
455const std = @import("std.zig");
456const builtin = @import("builtin");
457const AtomicOrder = std.builtin.AtomicOrder;
458const testing = std.testing;
lib/std/atomic/Atomic.zig deleted-619
......@@ -1,619 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3
4const testing = std.testing;
5const Ordering = std.atomic.Ordering;
6
7pub fn Atomic(comptime T: type) type {
8 return extern struct {
9 value: T,
10
11 const Self = @This();
12
13 pub fn init(value: T) Self {
14 return .{ .value = value };
15 }
16
17 /// Perform an atomic fence which uses the atomic value as a hint for the modification order.
18 /// Use this when you want to imply a fence on an atomic variable without necessarily performing a memory access.
19 ///
20 /// Example:
21 /// ```
22 /// const RefCount = struct {
23 /// count: Atomic(usize),
24 /// dropFn: *const fn (*RefCount) void,
25 ///
26 /// fn ref(self: *RefCount) void {
27 /// _ = self.count.fetchAdd(1, .Monotonic); // no ordering necessary, just updating a counter
28 /// }
29 ///
30 /// fn unref(self: *RefCount) void {
31 /// // Release ensures code before unref() happens-before the count is decremented as dropFn could be called by then.
32 /// if (self.count.fetchSub(1, .Release)) {
33 /// // Acquire ensures count decrement and code before previous unrefs()s happens-before we call dropFn below.
34 /// // NOTE: another alternative is to use .AcqRel on the fetchSub count decrement but it's extra barrier in possibly hot path.
35 /// self.count.fence(.Acquire);
36 /// (self.dropFn)(self);
37 /// }
38 /// }
39 /// };
40 /// ```
41 pub inline fn fence(self: *Self, comptime ordering: Ordering) void {
42 // LLVM's ThreadSanitizer doesn't support the normal fences so we specialize for it.
43 if (builtin.sanitize_thread) {
44 const tsan = struct {
45 extern "c" fn __tsan_acquire(addr: *anyopaque) void;
46 extern "c" fn __tsan_release(addr: *anyopaque) void;
47 };
48
49 const addr: *anyopaque = self;
50 return switch (ordering) {
51 .Unordered, .Monotonic => @compileError(@tagName(ordering) ++ " only applies to atomic loads and stores"),
52 .Acquire => tsan.__tsan_acquire(addr),
53 .Release => tsan.__tsan_release(addr),
54 .AcqRel, .SeqCst => {
55 tsan.__tsan_acquire(addr);
56 tsan.__tsan_release(addr);
57 },
58 };
59 }
60
61 return std.atomic.fence(ordering);
62 }
63
64 /// Non-atomically load from the atomic value without synchronization.
65 /// Care must be taken to avoid data-races when interacting with other atomic operations.
66 pub inline fn loadUnchecked(self: Self) T {
67 return self.value;
68 }
69
70 /// Non-atomically store to the atomic value without synchronization.
71 /// Care must be taken to avoid data-races when interacting with other atomic operations.
72 pub inline fn storeUnchecked(self: *Self, value: T) void {
73 self.value = value;
74 }
75
76 pub inline fn load(self: *const Self, comptime ordering: Ordering) T {
77 return switch (ordering) {
78 .AcqRel => @compileError(@tagName(ordering) ++ " implies " ++ @tagName(Ordering.Release) ++ " which is only allowed on atomic stores"),
79 .Release => @compileError(@tagName(ordering) ++ " is only allowed on atomic stores"),
80 else => @atomicLoad(T, &self.value, ordering),
81 };
82 }
83
84 pub inline fn store(self: *Self, value: T, comptime ordering: Ordering) void {
85 switch (ordering) {
86 .AcqRel => @compileError(@tagName(ordering) ++ " implies " ++ @tagName(Ordering.Acquire) ++ " which is only allowed on atomic loads"),
87 .Acquire => @compileError(@tagName(ordering) ++ " is only allowed on atomic loads"),
88 else => @atomicStore(T, &self.value, value, ordering),
89 }
90 }
91
92 pub inline fn swap(self: *Self, value: T, comptime ordering: Ordering) T {
93 return self.rmw(.Xchg, value, ordering);
94 }
95
96 pub inline fn compareAndSwap(
97 self: *Self,
98 compare: T,
99 exchange: T,
100 comptime success: Ordering,
101 comptime failure: Ordering,
102 ) ?T {
103 return self.cmpxchg(true, compare, exchange, success, failure);
104 }
105
106 pub inline fn tryCompareAndSwap(
107 self: *Self,
108 compare: T,
109 exchange: T,
110 comptime success: Ordering,
111 comptime failure: Ordering,
112 ) ?T {
113 return self.cmpxchg(false, compare, exchange, success, failure);
114 }
115
116 inline fn cmpxchg(
117 self: *Self,
118 comptime is_strong: bool,
119 compare: T,
120 exchange: T,
121 comptime success: Ordering,
122 comptime failure: Ordering,
123 ) ?T {
124 if (success == .Unordered or failure == .Unordered) {
125 @compileError(@tagName(Ordering.Unordered) ++ " is only allowed on atomic loads and stores");
126 }
127
128 const success_is_stronger = switch (failure) {
129 .SeqCst => success == .SeqCst,
130 .AcqRel => @compileError(@tagName(failure) ++ " implies " ++ @tagName(Ordering.Release) ++ " which is only allowed on success"),
131 .Acquire => success == .SeqCst or success == .AcqRel or success == .Acquire,
132 .Release => @compileError(@tagName(failure) ++ " is only allowed on success"),
133 .Monotonic => true,
134 .Unordered => unreachable,
135 };
136
137 if (!success_is_stronger) {
138 @compileError(@tagName(success) ++ " must be stronger than " ++ @tagName(failure));
139 }
140
141 return switch (is_strong) {
142 true => @cmpxchgStrong(T, &self.value, compare, exchange, success, failure),
143 false => @cmpxchgWeak(T, &self.value, compare, exchange, success, failure),
144 };
145 }
146
147 inline fn rmw(
148 self: *Self,
149 comptime op: std.builtin.AtomicRmwOp,
150 value: T,
151 comptime ordering: Ordering,
152 ) T {
153 return @atomicRmw(T, &self.value, op, value, ordering);
154 }
155
156 pub inline fn fetchAdd(self: *Self, value: T, comptime ordering: Ordering) T {
157 return self.rmw(.Add, value, ordering);
158 }
159
160 pub inline fn fetchSub(self: *Self, value: T, comptime ordering: Ordering) T {
161 return self.rmw(.Sub, value, ordering);
162 }
163
164 pub inline fn fetchMin(self: *Self, value: T, comptime ordering: Ordering) T {
165 return self.rmw(.Min, value, ordering);
166 }
167
168 pub inline fn fetchMax(self: *Self, value: T, comptime ordering: Ordering) T {
169 return self.rmw(.Max, value, ordering);
170 }
171
172 pub inline fn fetchAnd(self: *Self, value: T, comptime ordering: Ordering) T {
173 return self.rmw(.And, value, ordering);
174 }
175
176 pub inline fn fetchNand(self: *Self, value: T, comptime ordering: Ordering) T {
177 return self.rmw(.Nand, value, ordering);
178 }
179
180 pub inline fn fetchOr(self: *Self, value: T, comptime ordering: Ordering) T {
181 return self.rmw(.Or, value, ordering);
182 }
183
184 pub inline fn fetchXor(self: *Self, value: T, comptime ordering: Ordering) T {
185 return self.rmw(.Xor, value, ordering);
186 }
187
188 const Bit = std.math.Log2Int(T);
189 const BitRmwOp = enum {
190 Set,
191 Reset,
192 Toggle,
193 };
194
195 pub inline fn bitSet(self: *Self, bit: Bit, comptime ordering: Ordering) u1 {
196 return bitRmw(self, .Set, bit, ordering);
197 }
198
199 pub inline fn bitReset(self: *Self, bit: Bit, comptime ordering: Ordering) u1 {
200 return bitRmw(self, .Reset, bit, ordering);
201 }
202
203 pub inline fn bitToggle(self: *Self, bit: Bit, comptime ordering: Ordering) u1 {
204 return bitRmw(self, .Toggle, bit, ordering);
205 }
206
207 inline fn bitRmw(self: *Self, comptime op: BitRmwOp, bit: Bit, comptime ordering: Ordering) u1 {
208 // x86 supports dedicated bitwise instructions
209 if (comptime builtin.target.cpu.arch.isX86() and @sizeOf(T) >= 2 and @sizeOf(T) <= 8) {
210 // TODO: this causes std lib test failures when enabled
211 if (false) {
212 return x86BitRmw(self, op, bit, ordering);
213 }
214 }
215
216 const mask = @as(T, 1) << bit;
217 const value = switch (op) {
218 .Set => self.fetchOr(mask, ordering),
219 .Reset => self.fetchAnd(~mask, ordering),
220 .Toggle => self.fetchXor(mask, ordering),
221 };
222
223 return @intFromBool(value & mask != 0);
224 }
225
226 inline fn x86BitRmw(self: *Self, comptime op: BitRmwOp, bit: Bit, comptime ordering: Ordering) u1 {
227 const old_bit: u8 = switch (@sizeOf(T)) {
228 2 => switch (op) {
229 .Set => asm volatile ("lock btsw %[bit], %[ptr]"
230 // LLVM doesn't support u1 flag register return values
231 : [result] "={@ccc}" (-> u8),
232 : [ptr] "*m" (&self.value),
233 [bit] "X" (@as(T, bit)),
234 : "cc", "memory"
235 ),
236 .Reset => asm volatile ("lock btrw %[bit], %[ptr]"
237 // LLVM doesn't support u1 flag register return values
238 : [result] "={@ccc}" (-> u8),
239 : [ptr] "*m" (&self.value),
240 [bit] "X" (@as(T, bit)),
241 : "cc", "memory"
242 ),
243 .Toggle => asm volatile ("lock btcw %[bit], %[ptr]"
244 // LLVM doesn't support u1 flag register return values
245 : [result] "={@ccc}" (-> u8),
246 : [ptr] "*m" (&self.value),
247 [bit] "X" (@as(T, bit)),
248 : "cc", "memory"
249 ),
250 },
251 4 => switch (op) {
252 .Set => asm volatile ("lock btsl %[bit], %[ptr]"
253 // LLVM doesn't support u1 flag register return values
254 : [result] "={@ccc}" (-> u8),
255 : [ptr] "*m" (&self.value),
256 [bit] "X" (@as(T, bit)),
257 : "cc", "memory"
258 ),
259 .Reset => asm volatile ("lock btrl %[bit], %[ptr]"
260 // LLVM doesn't support u1 flag register return values
261 : [result] "={@ccc}" (-> u8),
262 : [ptr] "*m" (&self.value),
263 [bit] "X" (@as(T, bit)),
264 : "cc", "memory"
265 ),
266 .Toggle => asm volatile ("lock btcl %[bit], %[ptr]"
267 // LLVM doesn't support u1 flag register return values
268 : [result] "={@ccc}" (-> u8),
269 : [ptr] "*m" (&self.value),
270 [bit] "X" (@as(T, bit)),
271 : "cc", "memory"
272 ),
273 },
274 8 => switch (op) {
275 .Set => asm volatile ("lock btsq %[bit], %[ptr]"
276 // LLVM doesn't support u1 flag register return values
277 : [result] "={@ccc}" (-> u8),
278 : [ptr] "*m" (&self.value),
279 [bit] "X" (@as(T, bit)),
280 : "cc", "memory"
281 ),
282 .Reset => asm volatile ("lock btrq %[bit], %[ptr]"
283 // LLVM doesn't support u1 flag register return values
284 : [result] "={@ccc}" (-> u8),
285 : [ptr] "*m" (&self.value),
286 [bit] "X" (@as(T, bit)),
287 : "cc", "memory"
288 ),
289 .Toggle => asm volatile ("lock btcq %[bit], %[ptr]"
290 // LLVM doesn't support u1 flag register return values
291 : [result] "={@ccc}" (-> u8),
292 : [ptr] "*m" (&self.value),
293 [bit] "X" (@as(T, bit)),
294 : "cc", "memory"
295 ),
296 },
297 else => @compileError("Invalid atomic type " ++ @typeName(T)),
298 };
299
300 // TODO: emit appropriate tsan fence if compiling with tsan
301 _ = ordering;
302
303 return @intCast(old_bit);
304 }
305 };
306}
307
308test "Atomic.fence" {
309 inline for (.{ .Acquire, .Release, .AcqRel, .SeqCst }) |ordering| {
310 var x = Atomic(usize).init(0);
311 x.fence(ordering);
312 }
313}
314
315fn atomicIntTypes() []const type {
316 comptime var bytes = 1;
317 comptime var types: []const type = &[_]type{};
318 inline while (bytes <= @sizeOf(usize)) : (bytes *= 2) {
319 types = types ++ &[_]type{std.meta.Int(.unsigned, bytes * 8)};
320 }
321 return types;
322}
323
324test "Atomic.loadUnchecked" {
325 inline for (atomicIntTypes()) |Int| {
326 var x = Atomic(Int).init(5);
327 try testing.expectEqual(x.loadUnchecked(), 5);
328 }
329}
330
331test "Atomic.storeUnchecked" {
332 inline for (atomicIntTypes()) |Int| {
333 _ = Int;
334 var x = Atomic(usize).init(5);
335 x.storeUnchecked(10);
336 try testing.expectEqual(x.loadUnchecked(), 10);
337 }
338}
339
340test "Atomic.load" {
341 inline for (atomicIntTypes()) |Int| {
342 inline for (.{ .Unordered, .Monotonic, .Acquire, .SeqCst }) |ordering| {
343 var x = Atomic(Int).init(5);
344 try testing.expectEqual(x.load(ordering), 5);
345 }
346 }
347}
348
349test "Atomic.store" {
350 inline for (atomicIntTypes()) |Int| {
351 inline for (.{ .Unordered, .Monotonic, .Release, .SeqCst }) |ordering| {
352 _ = Int;
353 var x = Atomic(usize).init(5);
354 x.store(10, ordering);
355 try testing.expectEqual(x.load(.SeqCst), 10);
356 }
357 }
358}
359
360const atomic_rmw_orderings = [_]Ordering{
361 .Monotonic,
362 .Acquire,
363 .Release,
364 .AcqRel,
365 .SeqCst,
366};
367
368test "Atomic.swap" {
369 inline for (atomic_rmw_orderings) |ordering| {
370 var x = Atomic(usize).init(5);
371 try testing.expectEqual(x.swap(10, ordering), 5);
372 try testing.expectEqual(x.load(.SeqCst), 10);
373
374 var y = Atomic(enum(usize) { a, b, c }).init(.c);
375 try testing.expectEqual(y.swap(.a, ordering), .c);
376 try testing.expectEqual(y.load(.SeqCst), .a);
377
378 var z = Atomic(f32).init(5.0);
379 try testing.expectEqual(z.swap(10.0, ordering), 5.0);
380 try testing.expectEqual(z.load(.SeqCst), 10.0);
381
382 var a = Atomic(bool).init(false);
383 try testing.expectEqual(a.swap(true, ordering), false);
384 try testing.expectEqual(a.load(.SeqCst), true);
385
386 var b = Atomic(?*u8).init(null);
387 try testing.expectEqual(b.swap(@as(?*u8, @ptrFromInt(@alignOf(u8))), ordering), null);
388 try testing.expectEqual(b.load(.SeqCst), @as(?*u8, @ptrFromInt(@alignOf(u8))));
389 }
390}
391
392const atomic_cmpxchg_orderings = [_][2]Ordering{
393 .{ .Monotonic, .Monotonic },
394 .{ .Acquire, .Monotonic },
395 .{ .Acquire, .Acquire },
396 .{ .Release, .Monotonic },
397 // Although accepted by LLVM, acquire failure implies AcqRel success
398 // .{ .Release, .Acquire },
399 .{ .AcqRel, .Monotonic },
400 .{ .AcqRel, .Acquire },
401 .{ .SeqCst, .Monotonic },
402 .{ .SeqCst, .Acquire },
403 .{ .SeqCst, .SeqCst },
404};
405
406test "Atomic.compareAndSwap" {
407 inline for (atomicIntTypes()) |Int| {
408 inline for (atomic_cmpxchg_orderings) |ordering| {
409 var x = Atomic(Int).init(0);
410 try testing.expectEqual(x.compareAndSwap(1, 0, ordering[0], ordering[1]), 0);
411 try testing.expectEqual(x.load(.SeqCst), 0);
412 try testing.expectEqual(x.compareAndSwap(0, 1, ordering[0], ordering[1]), null);
413 try testing.expectEqual(x.load(.SeqCst), 1);
414 try testing.expectEqual(x.compareAndSwap(1, 0, ordering[0], ordering[1]), null);
415 try testing.expectEqual(x.load(.SeqCst), 0);
416 }
417 }
418}
419
420test "Atomic.tryCompareAndSwap" {
421 inline for (atomicIntTypes()) |Int| {
422 inline for (atomic_cmpxchg_orderings) |ordering| {
423 var x = Atomic(Int).init(0);
424
425 try testing.expectEqual(x.tryCompareAndSwap(1, 0, ordering[0], ordering[1]), 0);
426 try testing.expectEqual(x.load(.SeqCst), 0);
427
428 while (x.tryCompareAndSwap(0, 1, ordering[0], ordering[1])) |_| {}
429 try testing.expectEqual(x.load(.SeqCst), 1);
430
431 while (x.tryCompareAndSwap(1, 0, ordering[0], ordering[1])) |_| {}
432 try testing.expectEqual(x.load(.SeqCst), 0);
433 }
434 }
435}
436
437test "Atomic.fetchAdd" {
438 inline for (atomicIntTypes()) |Int| {
439 inline for (atomic_rmw_orderings) |ordering| {
440 var x = Atomic(Int).init(5);
441 try testing.expectEqual(x.fetchAdd(5, ordering), 5);
442 try testing.expectEqual(x.load(.SeqCst), 10);
443 try testing.expectEqual(x.fetchAdd(std.math.maxInt(Int), ordering), 10);
444 try testing.expectEqual(x.load(.SeqCst), 9);
445 }
446 }
447}
448
449test "Atomic.fetchSub" {
450 inline for (atomicIntTypes()) |Int| {
451 inline for (atomic_rmw_orderings) |ordering| {
452 var x = Atomic(Int).init(5);
453 try testing.expectEqual(x.fetchSub(5, ordering), 5);
454 try testing.expectEqual(x.load(.SeqCst), 0);
455 try testing.expectEqual(x.fetchSub(1, ordering), 0);
456 try testing.expectEqual(x.load(.SeqCst), std.math.maxInt(Int));
457 }
458 }
459}
460
461test "Atomic.fetchMin" {
462 inline for (atomicIntTypes()) |Int| {
463 inline for (atomic_rmw_orderings) |ordering| {
464 var x = Atomic(Int).init(5);
465 try testing.expectEqual(x.fetchMin(0, ordering), 5);
466 try testing.expectEqual(x.load(.SeqCst), 0);
467 try testing.expectEqual(x.fetchMin(10, ordering), 0);
468 try testing.expectEqual(x.load(.SeqCst), 0);
469 }
470 }
471}
472
473test "Atomic.fetchMax" {
474 inline for (atomicIntTypes()) |Int| {
475 inline for (atomic_rmw_orderings) |ordering| {
476 var x = Atomic(Int).init(5);
477 try testing.expectEqual(x.fetchMax(10, ordering), 5);
478 try testing.expectEqual(x.load(.SeqCst), 10);
479 try testing.expectEqual(x.fetchMax(5, ordering), 10);
480 try testing.expectEqual(x.load(.SeqCst), 10);
481 }
482 }
483}
484
485test "Atomic.fetchAnd" {
486 inline for (atomicIntTypes()) |Int| {
487 inline for (atomic_rmw_orderings) |ordering| {
488 var x = Atomic(Int).init(0b11);
489 try testing.expectEqual(x.fetchAnd(0b10, ordering), 0b11);
490 try testing.expectEqual(x.load(.SeqCst), 0b10);
491 try testing.expectEqual(x.fetchAnd(0b00, ordering), 0b10);
492 try testing.expectEqual(x.load(.SeqCst), 0b00);
493 }
494 }
495}
496
497test "Atomic.fetchNand" {
498 inline for (atomicIntTypes()) |Int| {
499 inline for (atomic_rmw_orderings) |ordering| {
500 var x = Atomic(Int).init(0b11);
501 try testing.expectEqual(x.fetchNand(0b10, ordering), 0b11);
502 try testing.expectEqual(x.load(.SeqCst), ~@as(Int, 0b10));
503 try testing.expectEqual(x.fetchNand(0b00, ordering), ~@as(Int, 0b10));
504 try testing.expectEqual(x.load(.SeqCst), ~@as(Int, 0b00));
505 }
506 }
507}
508
509test "Atomic.fetchOr" {
510 inline for (atomicIntTypes()) |Int| {
511 inline for (atomic_rmw_orderings) |ordering| {
512 var x = Atomic(Int).init(0b11);
513 try testing.expectEqual(x.fetchOr(0b100, ordering), 0b11);
514 try testing.expectEqual(x.load(.SeqCst), 0b111);
515 try testing.expectEqual(x.fetchOr(0b010, ordering), 0b111);
516 try testing.expectEqual(x.load(.SeqCst), 0b111);
517 }
518 }
519}
520
521test "Atomic.fetchXor" {
522 inline for (atomicIntTypes()) |Int| {
523 inline for (atomic_rmw_orderings) |ordering| {
524 var x = Atomic(Int).init(0b11);
525 try testing.expectEqual(x.fetchXor(0b10, ordering), 0b11);
526 try testing.expectEqual(x.load(.SeqCst), 0b01);
527 try testing.expectEqual(x.fetchXor(0b01, ordering), 0b01);
528 try testing.expectEqual(x.load(.SeqCst), 0b00);
529 }
530 }
531}
532
533test "Atomic.bitSet" {
534 inline for (atomicIntTypes()) |Int| {
535 inline for (atomic_rmw_orderings) |ordering| {
536 var x = Atomic(Int).init(0);
537
538 for (0..@bitSizeOf(Int)) |bit_index| {
539 const bit = @as(std.math.Log2Int(Int), @intCast(bit_index));
540 const mask = @as(Int, 1) << bit;
541
542 // setting the bit should change the bit
543 try testing.expect(x.load(.SeqCst) & mask == 0);
544 try testing.expectEqual(x.bitSet(bit, ordering), 0);
545 try testing.expect(x.load(.SeqCst) & mask != 0);
546
547 // setting it again shouldn't change the bit
548 try testing.expectEqual(x.bitSet(bit, ordering), 1);
549 try testing.expect(x.load(.SeqCst) & mask != 0);
550
551 // all the previous bits should have not changed (still be set)
552 for (0..bit_index) |prev_bit_index| {
553 const prev_bit = @as(std.math.Log2Int(Int), @intCast(prev_bit_index));
554 const prev_mask = @as(Int, 1) << prev_bit;
555 try testing.expect(x.load(.SeqCst) & prev_mask != 0);
556 }
557 }
558 }
559 }
560}
561
562test "Atomic.bitReset" {
563 inline for (atomicIntTypes()) |Int| {
564 inline for (atomic_rmw_orderings) |ordering| {
565 var x = Atomic(Int).init(0);
566
567 for (0..@bitSizeOf(Int)) |bit_index| {
568 const bit = @as(std.math.Log2Int(Int), @intCast(bit_index));
569 const mask = @as(Int, 1) << bit;
570 x.storeUnchecked(x.loadUnchecked() | mask);
571
572 // unsetting the bit should change the bit
573 try testing.expect(x.load(.SeqCst) & mask != 0);
574 try testing.expectEqual(x.bitReset(bit, ordering), 1);
575 try testing.expect(x.load(.SeqCst) & mask == 0);
576
577 // unsetting it again shouldn't change the bit
578 try testing.expectEqual(x.bitReset(bit, ordering), 0);
579 try testing.expect(x.load(.SeqCst) & mask == 0);
580
581 // all the previous bits should have not changed (still be reset)
582 for (0..bit_index) |prev_bit_index| {
583 const prev_bit = @as(std.math.Log2Int(Int), @intCast(prev_bit_index));
584 const prev_mask = @as(Int, 1) << prev_bit;
585 try testing.expect(x.load(.SeqCst) & prev_mask == 0);
586 }
587 }
588 }
589 }
590}
591
592test "Atomic.bitToggle" {
593 inline for (atomicIntTypes()) |Int| {
594 inline for (atomic_rmw_orderings) |ordering| {
595 var x = Atomic(Int).init(0);
596
597 for (0..@bitSizeOf(Int)) |bit_index| {
598 const bit = @as(std.math.Log2Int(Int), @intCast(bit_index));
599 const mask = @as(Int, 1) << bit;
600
601 // toggling the bit should change the bit
602 try testing.expect(x.load(.SeqCst) & mask == 0);
603 try testing.expectEqual(x.bitToggle(bit, ordering), 0);
604 try testing.expect(x.load(.SeqCst) & mask != 0);
605
606 // toggling it again *should* change the bit
607 try testing.expectEqual(x.bitToggle(bit, ordering), 1);
608 try testing.expect(x.load(.SeqCst) & mask == 0);
609
610 // all the previous bits should have not changed (still be toggled back)
611 for (0..bit_index) |prev_bit_index| {
612 const prev_bit = @as(std.math.Log2Int(Int), @intCast(prev_bit_index));
613 const prev_mask = @as(Int, 1) << prev_bit;
614 try testing.expect(x.load(.SeqCst) & prev_mask == 0);
615 }
616 }
617 }
618 }
619}
lib/std/atomic/queue.zig deleted-413
......@@ -1,413 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const expect = std.testing.expect;
5
6/// Many producer, many consumer, non-allocating, thread-safe.
7/// Uses a mutex to protect access.
8/// The queue does not manage ownership and the user is responsible to
9/// manage the storage of the nodes.
10pub fn Queue(comptime T: type) type {
11 return struct {
12 head: ?*Node,
13 tail: ?*Node,
14 mutex: std.Thread.Mutex,
15
16 pub const Self = @This();
17 pub const Node = std.DoublyLinkedList(T).Node;
18
19 /// Initializes a new queue. The queue does not provide a `deinit()`
20 /// function, so the user must take care of cleaning up the queue elements.
21 pub fn init() Self {
22 return Self{
23 .head = null,
24 .tail = null,
25 .mutex = std.Thread.Mutex{},
26 };
27 }
28
29 /// Appends `node` to the queue.
30 /// The lifetime of `node` must be longer than the lifetime of the queue.
31 pub fn put(self: *Self, node: *Node) void {
32 node.next = null;
33
34 self.mutex.lock();
35 defer self.mutex.unlock();
36
37 node.prev = self.tail;
38 self.tail = node;
39 if (node.prev) |prev_tail| {
40 prev_tail.next = node;
41 } else {
42 assert(self.head == null);
43 self.head = node;
44 }
45 }
46
47 /// Gets a previously inserted node or returns `null` if there is none.
48 /// It is safe to `get()` a node from the queue while another thread tries
49 /// to `remove()` the same node at the same time.
50 pub fn get(self: *Self) ?*Node {
51 self.mutex.lock();
52 defer self.mutex.unlock();
53
54 const head = self.head orelse return null;
55 self.head = head.next;
56 if (head.next) |new_head| {
57 new_head.prev = null;
58 } else {
59 self.tail = null;
60 }
61 // This way, a get() and a remove() are thread-safe with each other.
62 head.prev = null;
63 head.next = null;
64 return head;
65 }
66
67 /// Prepends `node` to the front of the queue.
68 /// The lifetime of `node` must be longer than the lifetime of the queue.
69 pub fn unget(self: *Self, node: *Node) void {
70 node.prev = null;
71
72 self.mutex.lock();
73 defer self.mutex.unlock();
74
75 const opt_head = self.head;
76 self.head = node;
77 if (opt_head) |old_head| {
78 node.next = old_head;
79 } else {
80 assert(self.tail == null);
81 self.tail = node;
82 }
83 }
84
85 /// Removes a node from the queue, returns whether node was actually removed.
86 /// It is safe to `remove()` a node from the queue while another thread tries
87 /// to `get()` the same node at the same time.
88 pub fn remove(self: *Self, node: *Node) bool {
89 self.mutex.lock();
90 defer self.mutex.unlock();
91
92 if (node.prev == null and node.next == null and self.head != node) {
93 return false;
94 }
95
96 if (node.prev) |prev| {
97 prev.next = node.next;
98 } else {
99 self.head = node.next;
100 }
101 if (node.next) |next| {
102 next.prev = node.prev;
103 } else {
104 self.tail = node.prev;
105 }
106 node.prev = null;
107 node.next = null;
108 return true;
109 }
110
111 /// Returns `true` if the queue is currently empty.
112 /// Note that in a multi-consumer environment a return value of `false`
113 /// does not mean that `get` will yield a non-`null` value!
114 pub fn isEmpty(self: *Self) bool {
115 self.mutex.lock();
116 defer self.mutex.unlock();
117 return self.head == null;
118 }
119
120 /// Dumps the contents of the queue to `stderr`.
121 pub fn dump(self: *Self) void {
122 self.dumpToStream(std.io.getStdErr().writer()) catch return;
123 }
124
125 /// Dumps the contents of the queue to `stream`.
126 /// Up to 4 elements from the head are dumped and the tail of the queue is
127 /// dumped as well.
128 pub fn dumpToStream(self: *Self, stream: anytype) !void {
129 const S = struct {
130 fn dumpRecursive(
131 s: anytype,
132 optional_node: ?*Node,
133 indent: usize,
134 comptime depth: comptime_int,
135 ) !void {
136 try s.writeByteNTimes(' ', indent);
137 if (optional_node) |node| {
138 try s.print("0x{x}={}\n", .{ @intFromPtr(node), node.data });
139 if (depth == 0) {
140 try s.print("(max depth)\n", .{});
141 return;
142 }
143 try dumpRecursive(s, node.next, indent + 1, depth - 1);
144 } else {
145 try s.print("(null)\n", .{});
146 }
147 }
148 };
149 self.mutex.lock();
150 defer self.mutex.unlock();
151
152 try stream.print("head: ", .{});
153 try S.dumpRecursive(stream, self.head, 0, 4);
154 try stream.print("tail: ", .{});
155 try S.dumpRecursive(stream, self.tail, 0, 4);
156 }
157 };
158}
159
160const Context = struct {
161 allocator: std.mem.Allocator,
162 queue: *Queue(i32),
163 put_sum: isize,
164 get_sum: isize,
165 get_count: usize,
166 puts_done: bool,
167};
168
169// TODO add lazy evaluated build options and then put puts_per_thread behind
170// some option such as: "AggressiveMultithreadedFuzzTest". In the AppVeyor
171// CI we would use a less aggressive setting since at 1 core, while we still
172// want this test to pass, we need a smaller value since there is so much thrashing
173// we would also use a less aggressive setting when running in valgrind
174const puts_per_thread = 500;
175const put_thread_count = 3;
176
177test "std.atomic.Queue" {
178 const plenty_of_memory = try std.heap.page_allocator.alloc(u8, 300 * 1024);
179 defer std.heap.page_allocator.free(plenty_of_memory);
180
181 var fixed_buffer_allocator = std.heap.FixedBufferAllocator.init(plenty_of_memory);
182 const a = fixed_buffer_allocator.threadSafeAllocator();
183
184 var queue = Queue(i32).init();
185 var context = Context{
186 .allocator = a,
187 .queue = &queue,
188 .put_sum = 0,
189 .get_sum = 0,
190 .puts_done = false,
191 .get_count = 0,
192 };
193
194 if (builtin.single_threaded) {
195 try expect(context.queue.isEmpty());
196 {
197 var i: usize = 0;
198 while (i < put_thread_count) : (i += 1) {
199 try expect(startPuts(&context) == 0);
200 }
201 }
202 try expect(!context.queue.isEmpty());
203 context.puts_done = true;
204 {
205 var i: usize = 0;
206 while (i < put_thread_count) : (i += 1) {
207 try expect(startGets(&context) == 0);
208 }
209 }
210 try expect(context.queue.isEmpty());
211 } else {
212 try expect(context.queue.isEmpty());
213
214 var putters: [put_thread_count]std.Thread = undefined;
215 for (&putters) |*t| {
216 t.* = try std.Thread.spawn(.{}, startPuts, .{&context});
217 }
218 var getters: [put_thread_count]std.Thread = undefined;
219 for (&getters) |*t| {
220 t.* = try std.Thread.spawn(.{}, startGets, .{&context});
221 }
222
223 for (putters) |t|
224 t.join();
225 @atomicStore(bool, &context.puts_done, true, .SeqCst);
226 for (getters) |t|
227 t.join();
228
229 try expect(context.queue.isEmpty());
230 }
231
232 if (context.put_sum != context.get_sum) {
233 std.debug.panic("failure\nput_sum:{} != get_sum:{}", .{ context.put_sum, context.get_sum });
234 }
235
236 if (context.get_count != puts_per_thread * put_thread_count) {
237 std.debug.panic("failure\nget_count:{} != puts_per_thread:{} * put_thread_count:{}", .{
238 context.get_count,
239 @as(u32, puts_per_thread),
240 @as(u32, put_thread_count),
241 });
242 }
243}
244
245fn startPuts(ctx: *Context) u8 {
246 var put_count: usize = puts_per_thread;
247 var prng = std.rand.DefaultPrng.init(0xdeadbeef);
248 const random = prng.random();
249 while (put_count != 0) : (put_count -= 1) {
250 std.time.sleep(1); // let the os scheduler be our fuzz
251 const x = @as(i32, @bitCast(random.int(u32)));
252 const node = ctx.allocator.create(Queue(i32).Node) catch unreachable;
253 node.* = .{
254 .prev = undefined,
255 .next = undefined,
256 .data = x,
257 };
258 ctx.queue.put(node);
259 _ = @atomicRmw(isize, &ctx.put_sum, .Add, x, .SeqCst);
260 }
261 return 0;
262}
263
264fn startGets(ctx: *Context) u8 {
265 while (true) {
266 const last = @atomicLoad(bool, &ctx.puts_done, .SeqCst);
267
268 while (ctx.queue.get()) |node| {
269 std.time.sleep(1); // let the os scheduler be our fuzz
270 _ = @atomicRmw(isize, &ctx.get_sum, .Add, node.data, .SeqCst);
271 _ = @atomicRmw(usize, &ctx.get_count, .Add, 1, .SeqCst);
272 }
273
274 if (last) return 0;
275 }
276}
277
278test "std.atomic.Queue single-threaded" {
279 var queue = Queue(i32).init();
280 try expect(queue.isEmpty());
281
282 var node_0 = Queue(i32).Node{
283 .data = 0,
284 .next = undefined,
285 .prev = undefined,
286 };
287 queue.put(&node_0);
288 try expect(!queue.isEmpty());
289
290 var node_1 = Queue(i32).Node{
291 .data = 1,
292 .next = undefined,
293 .prev = undefined,
294 };
295 queue.put(&node_1);
296 try expect(!queue.isEmpty());
297
298 try expect(queue.get().?.data == 0);
299 try expect(!queue.isEmpty());
300
301 var node_2 = Queue(i32).Node{
302 .data = 2,
303 .next = undefined,
304 .prev = undefined,
305 };
306 queue.put(&node_2);
307 try expect(!queue.isEmpty());
308
309 var node_3 = Queue(i32).Node{
310 .data = 3,
311 .next = undefined,
312 .prev = undefined,
313 };
314 queue.put(&node_3);
315 try expect(!queue.isEmpty());
316
317 try expect(queue.get().?.data == 1);
318 try expect(!queue.isEmpty());
319
320 try expect(queue.get().?.data == 2);
321 try expect(!queue.isEmpty());
322
323 var node_4 = Queue(i32).Node{
324 .data = 4,
325 .next = undefined,
326 .prev = undefined,
327 };
328 queue.put(&node_4);
329 try expect(!queue.isEmpty());
330
331 try expect(queue.get().?.data == 3);
332 node_3.next = null;
333 try expect(!queue.isEmpty());
334
335 queue.unget(&node_3);
336 try expect(queue.get().?.data == 3);
337 try expect(!queue.isEmpty());
338
339 try expect(queue.get().?.data == 4);
340 try expect(queue.isEmpty());
341
342 try expect(queue.get() == null);
343 try expect(queue.isEmpty());
344
345 // unget an empty queue
346 queue.unget(&node_4);
347 try expect(queue.tail == &node_4);
348 try expect(queue.head == &node_4);
349
350 try expect(queue.get().?.data == 4);
351
352 try expect(queue.get() == null);
353 try expect(queue.isEmpty());
354}
355
356test "std.atomic.Queue dump" {
357 const mem = std.mem;
358 var buffer: [1024]u8 = undefined;
359 var expected_buffer: [1024]u8 = undefined;
360 var fbs = std.io.fixedBufferStream(&buffer);
361
362 var queue = Queue(i32).init();
363
364 // Test empty stream
365 fbs.reset();
366 try queue.dumpToStream(fbs.writer());
367 try expect(mem.eql(u8, buffer[0..fbs.pos],
368 \\head: (null)
369 \\tail: (null)
370 \\
371 ));
372
373 // Test a stream with one element
374 var node_0 = Queue(i32).Node{
375 .data = 1,
376 .next = undefined,
377 .prev = undefined,
378 };
379 queue.put(&node_0);
380
381 fbs.reset();
382 try queue.dumpToStream(fbs.writer());
383
384 var expected = try std.fmt.bufPrint(expected_buffer[0..],
385 \\head: 0x{x}=1
386 \\ (null)
387 \\tail: 0x{x}=1
388 \\ (null)
389 \\
390 , .{ @intFromPtr(queue.head), @intFromPtr(queue.tail) });
391 try expect(mem.eql(u8, buffer[0..fbs.pos], expected));
392
393 // Test a stream with two elements
394 var node_1 = Queue(i32).Node{
395 .data = 2,
396 .next = undefined,
397 .prev = undefined,
398 };
399 queue.put(&node_1);
400
401 fbs.reset();
402 try queue.dumpToStream(fbs.writer());
403
404 expected = try std.fmt.bufPrint(expected_buffer[0..],
405 \\head: 0x{x}=1
406 \\ 0x{x}=2
407 \\ (null)
408 \\tail: 0x{x}=2
409 \\ (null)
410 \\
411 , .{ @intFromPtr(queue.head), @intFromPtr(queue.head.?.next), @intFromPtr(queue.tail) });
412 try expect(mem.eql(u8, buffer[0..fbs.pos], expected));
413}
lib/std/atomic/stack.zig deleted-178
......@@ -1,178 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const expect = std.testing.expect;
5
6/// Many reader, many writer, non-allocating, thread-safe.
7/// Uses a spinlock to protect `push()` and `pop()`.
8/// When building in single threaded mode, this is a simple linked list.
9pub fn Stack(comptime T: type) type {
10 return struct {
11 root: ?*Node,
12 lock: @TypeOf(lock_init),
13
14 const lock_init = if (builtin.single_threaded) {} else false;
15
16 pub const Self = @This();
17
18 pub const Node = struct {
19 next: ?*Node,
20 data: T,
21 };
22
23 pub fn init() Self {
24 return Self{
25 .root = null,
26 .lock = lock_init,
27 };
28 }
29
30 /// push operation, but only if you are the first item in the stack. if you did not succeed in
31 /// being the first item in the stack, returns the other item that was there.
32 pub fn pushFirst(self: *Self, node: *Node) ?*Node {
33 node.next = null;
34 return @cmpxchgStrong(?*Node, &self.root, null, node, .SeqCst, .SeqCst);
35 }
36
37 pub fn push(self: *Self, node: *Node) void {
38 if (builtin.single_threaded) {
39 node.next = self.root;
40 self.root = node;
41 } else {
42 while (@atomicRmw(bool, &self.lock, .Xchg, true, .SeqCst)) {}
43 defer assert(@atomicRmw(bool, &self.lock, .Xchg, false, .SeqCst));
44
45 node.next = self.root;
46 self.root = node;
47 }
48 }
49
50 pub fn pop(self: *Self) ?*Node {
51 if (builtin.single_threaded) {
52 const root = self.root orelse return null;
53 self.root = root.next;
54 return root;
55 } else {
56 while (@atomicRmw(bool, &self.lock, .Xchg, true, .SeqCst)) {}
57 defer assert(@atomicRmw(bool, &self.lock, .Xchg, false, .SeqCst));
58
59 const root = self.root orelse return null;
60 self.root = root.next;
61 return root;
62 }
63 }
64
65 pub fn isEmpty(self: *Self) bool {
66 return @atomicLoad(?*Node, &self.root, .SeqCst) == null;
67 }
68 };
69}
70
71const Context = struct {
72 allocator: std.mem.Allocator,
73 stack: *Stack(i32),
74 put_sum: isize,
75 get_sum: isize,
76 get_count: usize,
77 puts_done: bool,
78};
79// TODO add lazy evaluated build options and then put puts_per_thread behind
80// some option such as: "AggressiveMultithreadedFuzzTest". In the AppVeyor
81// CI we would use a less aggressive setting since at 1 core, while we still
82// want this test to pass, we need a smaller value since there is so much thrashing
83// we would also use a less aggressive setting when running in valgrind
84const puts_per_thread = 500;
85const put_thread_count = 3;
86
87test "std.atomic.stack" {
88 const plenty_of_memory = try std.heap.page_allocator.alloc(u8, 300 * 1024);
89 defer std.heap.page_allocator.free(plenty_of_memory);
90
91 var fixed_buffer_allocator = std.heap.FixedBufferAllocator.init(plenty_of_memory);
92 const a = fixed_buffer_allocator.threadSafeAllocator();
93
94 var stack = Stack(i32).init();
95 var context = Context{
96 .allocator = a,
97 .stack = &stack,
98 .put_sum = 0,
99 .get_sum = 0,
100 .puts_done = false,
101 .get_count = 0,
102 };
103
104 if (builtin.single_threaded) {
105 {
106 var i: usize = 0;
107 while (i < put_thread_count) : (i += 1) {
108 try expect(startPuts(&context) == 0);
109 }
110 }
111 context.puts_done = true;
112 {
113 var i: usize = 0;
114 while (i < put_thread_count) : (i += 1) {
115 try expect(startGets(&context) == 0);
116 }
117 }
118 } else {
119 var putters: [put_thread_count]std.Thread = undefined;
120 for (&putters) |*t| {
121 t.* = try std.Thread.spawn(.{}, startPuts, .{&context});
122 }
123 var getters: [put_thread_count]std.Thread = undefined;
124 for (&getters) |*t| {
125 t.* = try std.Thread.spawn(.{}, startGets, .{&context});
126 }
127
128 for (putters) |t|
129 t.join();
130 @atomicStore(bool, &context.puts_done, true, .SeqCst);
131 for (getters) |t|
132 t.join();
133 }
134
135 if (context.put_sum != context.get_sum) {
136 std.debug.panic("failure\nput_sum:{} != get_sum:{}", .{ context.put_sum, context.get_sum });
137 }
138
139 if (context.get_count != puts_per_thread * put_thread_count) {
140 std.debug.panic("failure\nget_count:{} != puts_per_thread:{} * put_thread_count:{}", .{
141 context.get_count,
142 @as(u32, puts_per_thread),
143 @as(u32, put_thread_count),
144 });
145 }
146}
147
148fn startPuts(ctx: *Context) u8 {
149 var put_count: usize = puts_per_thread;
150 var prng = std.rand.DefaultPrng.init(0xdeadbeef);
151 const random = prng.random();
152 while (put_count != 0) : (put_count -= 1) {
153 std.time.sleep(1); // let the os scheduler be our fuzz
154 const x = @as(i32, @bitCast(random.int(u32)));
155 const node = ctx.allocator.create(Stack(i32).Node) catch unreachable;
156 node.* = Stack(i32).Node{
157 .next = undefined,
158 .data = x,
159 };
160 ctx.stack.push(node);
161 _ = @atomicRmw(isize, &ctx.put_sum, .Add, x, .SeqCst);
162 }
163 return 0;
164}
165
166fn startGets(ctx: *Context) u8 {
167 while (true) {
168 const last = @atomicLoad(bool, &ctx.puts_done, .SeqCst);
169
170 while (ctx.stack.pop()) |node| {
171 std.time.sleep(1); // let the os scheduler be our fuzz
172 _ = @atomicRmw(isize, &ctx.get_sum, .Add, node.data, .SeqCst);
173 _ = @atomicRmw(usize, &ctx.get_count, .Add, 1, .SeqCst);
174 }
175
176 if (last) return 0;
177 }
178}
lib/std/child_process.zig+1-1
......@@ -1286,7 +1286,7 @@ fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const w
12861286 wr.* = wr_h;
12871287}
12881288
1289var pipe_name_counter = std.atomic.Atomic(u32).init(1);
1289var pipe_name_counter = std.atomic.Value(u32).init(1);
12901290
12911291fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
12921292 var tmp_bufw: [128]u16 = undefined;
lib/std/debug.zig+2-2
......@@ -375,7 +375,7 @@ pub fn panicExtra(
375375
376376/// Non-zero whenever the program triggered a panic.
377377/// The counter is incremented/decremented atomically.
378var panicking = std.atomic.Atomic(u8).init(0);
378var panicking = std.atomic.Value(u8).init(0);
379379
380380// Locked to avoid interleaving panic messages from multiple threads.
381381var panic_mutex = std.Thread.Mutex{};
......@@ -448,7 +448,7 @@ fn waitForOtherThreadToFinishPanicking() void {
448448 if (builtin.single_threaded) unreachable;
449449
450450 // Sleep forever without hammering the CPU
451 var futex = std.atomic.Atomic(u32).init(0);
451 var futex = std.atomic.Value(u32).init(0);
452452 while (true) std.Thread.Futex.wait(&futex, 0);
453453 unreachable;
454454 }
lib/std/event/loop.zig+2-3
......@@ -7,7 +7,6 @@ const os = std.os;
77const windows = os.windows;
88const maxInt = std.math.maxInt;
99const Thread = std.Thread;
10const Atomic = std.atomic.Atomic;
1110
1211const is_windows = builtin.os.tag == .windows;
1312
......@@ -854,7 +853,7 @@ pub const Loop = struct {
854853 waiters: Waiters,
855854 thread: std.Thread,
856855 event: std.Thread.ResetEvent,
857 is_running: Atomic(bool),
856 is_running: std.atomic.Value(bool),
858857
859858 /// Initialize the delay queue by spawning the timer thread
860859 /// and starting any timer resources.
......@@ -866,7 +865,7 @@ pub const Loop = struct {
866865 },
867866 .thread = undefined,
868867 .event = .{},
869 .is_running = Atomic(bool).init(true),
868 .is_running = std.atomic.Value(bool).init(true),
870869 };
871870
872871 // Must be after init so that it can read the other state, such as `is_running`.
lib/std/os.zig+1-1
......@@ -6461,7 +6461,7 @@ pub const CopyFileRangeError = error{
64616461 CorruptedData,
64626462} || PReadError || PWriteError || UnexpectedError;
64636463
6464var has_copy_file_range_syscall = std.atomic.Atomic(bool).init(true);
6464var has_copy_file_range_syscall = std.atomic.Value(bool).init(true);
64656465
64666466/// Transfer data between file descriptors at specified offsets.
64676467/// Returns the number of bytes written, which can less than requested.
src/crash_report.zig+2-2
......@@ -322,7 +322,7 @@ const PanicSwitch = struct {
322322 /// Updated atomically before taking the panic_mutex.
323323 /// In recoverable cases, the program will not abort
324324 /// until all panicking threads have dumped their traces.
325 var panicking = std.atomic.Atomic(u8).init(0);
325 var panicking = std.atomic.Value(u8).init(0);
326326
327327 // Locked to avoid interleaving panic messages from multiple threads.
328328 var panic_mutex = std.Thread.Mutex{};
......@@ -477,7 +477,7 @@ const PanicSwitch = struct {
477477 // and call abort()
478478
479479 // Sleep forever without hammering the CPU
480 var futex = std.atomic.Atomic(u32).init(0);
480 var futex = std.atomic.Value(u32).init(0);
481481 while (true) std.Thread.Futex.wait(&futex, 0);
482482
483483 // This should be unreachable, recurse into recoverAbort.