authorgravatar for 45520026+kprotty@users.noreply.github.comprotty <45520026+kprotty@users.noreply.github.com> 2021-05-31 11:11:30-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-05-31 11:11:30-05:00
logeb6975f0889db4c2c29d47bb959bc39bd0a9b167
tree688143d7828e2717ed2246c9a2482d8f22433976
parent57cf9f7ea6d018714cf4afa711799c67ff730f12
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.sync.atomic: extended atomic helper functions (#8866)

- deprecates `std.Thread.spinLoopHint` and moves it to `std.atomic.spinLoopHint` - added an Atomic(T) generic wrapper type which replaces atomic.Bool and atomic.Int - in Atomic(T), selectively expose member functions depending on T and include bitwise atomic methods when T is an Integer - added fence() and compilerFence() to std.atomic

14 files changed, 624 insertions(+), 193 deletions(-)

CMakeLists.txt+1-2
......@@ -337,8 +337,7 @@ set(ZIG_STAGE2_SOURCES
337337 "${CMAKE_SOURCE_DIR}/lib/std/array_list.zig"
338338 "${CMAKE_SOURCE_DIR}/lib/std/ascii.zig"
339339 "${CMAKE_SOURCE_DIR}/lib/std/atomic.zig"
340 "${CMAKE_SOURCE_DIR}/lib/std/atomic/bool.zig"
341 "${CMAKE_SOURCE_DIR}/lib/std/atomic/int.zig"
340 "${CMAKE_SOURCE_DIR}/lib/std/atomic/Atomic.zig"
342341 "${CMAKE_SOURCE_DIR}/lib/std/atomic/queue.zig"
343342 "${CMAKE_SOURCE_DIR}/lib/std/atomic/stack.zig"
344343 "${CMAKE_SOURCE_DIR}/lib/std/base64.zig"
lib/std/Thread.zig+8-29
......@@ -67,33 +67,7 @@ else switch (std.Target.current.os.tag) {
6767 else => struct {},
6868};
6969
70/// Signals the processor that it is inside a busy-wait spin-loop ("spin lock").
71pub inline fn spinLoopHint() void {
72 switch (std.Target.current.cpu.arch) {
73 .i386, .x86_64 => {
74 asm volatile ("pause" ::: "memory");
75 },
76 .arm, .armeb, .thumb, .thumbeb => {
77 // `yield` was introduced in v6k but are also available on v6m.
78 const can_yield = comptime std.Target.arm.featureSetHasAny(std.Target.current.cpu.features, .{ .has_v6k, .has_v6m });
79 if (can_yield) asm volatile ("yield" ::: "memory")
80 // Fallback.
81 else asm volatile ("" ::: "memory");
82 },
83 .aarch64, .aarch64_be, .aarch64_32 => {
84 asm volatile ("isb" ::: "memory");
85 },
86 .powerpc64, .powerpc64le => {
87 // No-op that serves as `yield` hint.
88 asm volatile ("or 27, 27, 27" ::: "memory");
89 },
90 else => {
91 // Do nothing but prevent the compiler from optimizing away the
92 // spinning loop.
93 asm volatile ("" ::: "memory");
94 },
95 }
96}
70pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint");
9771
9872/// Returns the ID of the calling thread.
9973/// Makes a syscall every time the function is called.
......@@ -597,8 +571,13 @@ pub fn getCurrentThreadId() u64 {
597571 }
598572}
599573
600test {
574test "std.Thread" {
601575 if (!builtin.single_threaded) {
602 std.testing.refAllDecls(@This());
576 _ = AutoResetEvent;
577 _ = ResetEvent;
578 _ = StaticResetEvent;
579 _ = Mutex;
580 _ = Semaphore;
581 _ = Condition;
603582 }
604583}
lib/std/Thread/Condition.zig+1-1
......@@ -115,7 +115,7 @@ pub const AtomicCondition = struct {
115115 else => unreachable,
116116 }
117117 },
118 else => spinLoopHint(),
118 else => std.atomic.spinLoopHint(),
119119 }
120120 }
121121 }
lib/std/Thread/Mutex.zig+2-2
......@@ -126,7 +126,7 @@ pub const AtomicMutex = struct {
126126
127127 var iter = std.math.min(32, spin + 1);
128128 while (iter > 0) : (iter -= 1)
129 std.Thread.spinLoopHint();
129 std.atomic.spinLoopHint();
130130 }
131131
132132 new_state = .waiting;
......@@ -149,7 +149,7 @@ pub const AtomicMutex = struct {
149149 else => unreachable,
150150 }
151151 },
152 else => std.Thread.spinLoopHint(),
152 else => std.atomic.spinLoopHint(),
153153 }
154154 }
155155 }
lib/std/Thread/StaticResetEvent.zig+2-2
......@@ -182,7 +182,7 @@ pub const AtomicEvent = struct {
182182 timer = time.Timer.start() catch return error.TimedOut;
183183
184184 while (@atomicLoad(u32, waiters, .Acquire) != WAKE) {
185 std.os.sched_yield() catch std.Thread.spinLoopHint();
185 std.os.sched_yield() catch std.atomic.spinLoopHint();
186186 if (timeout) |timeout_ns| {
187187 if (timer.read() >= timeout_ns)
188188 return error.TimedOut;
......@@ -293,7 +293,7 @@ pub const AtomicEvent = struct {
293293 return @intToPtr(?windows.HANDLE, handle);
294294 },
295295 LOADING => {
296 std.os.sched_yield() catch std.Thread.spinLoopHint();
296 std.os.sched_yield() catch std.atomic.spinLoopHint();
297297 handle = @atomicLoad(usize, &event_handle, .Monotonic);
298298 },
299299 else => {
lib/std/atomic.zig+72-4
......@@ -3,14 +3,82 @@
33// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
44// The MIT license requires this copyright notice to be included in all copies
55// and substantial portions of the software.
6
7const std = @import("std.zig");
8const target = std.Target.current;
9
10pub const Ordering = std.builtin.AtomicOrder;
11
612pub const Stack = @import("atomic/stack.zig").Stack;
713pub const Queue = @import("atomic/queue.zig").Queue;
8pub const Bool = @import("atomic/bool.zig").Bool;
9pub const Int = @import("atomic/int.zig").Int;
14pub const Atomic = @import("atomic/Atomic.zig").Atomic;
1015
1116test "std.atomic" {
1217 _ = @import("atomic/stack.zig");
1318 _ = @import("atomic/queue.zig");
14 _ = @import("atomic/bool.zig");
15 _ = @import("atomic/int.zig");
19 _ = @import("atomic/Atomic.zig");
20}
21
22pub fn fence(comptime ordering: Ordering) callconv(.Inline) void {
23 switch (ordering) {
24 .Acquire, .Release, .AcqRel, .SeqCst => {
25 @fence(ordering);
26 },
27 else => {
28 @compileLog(ordering, " only applies to a given memory location");
29 },
30 }
31}
32
33pub fn compilerFence(comptime ordering: Ordering) callconv(.Inline) void {
34 switch (ordering) {
35 .Acquire, .Release, .AcqRel, .SeqCst => asm volatile ("" ::: "memory"),
36 else => @compileLog(ordering, " only applies to a given memory location"),
37 }
38}
39
40test "fence/compilerFence" {
41 inline for (.{ .Acquire, .Release, .AcqRel, .SeqCst }) |ordering| {
42 compilerFence(ordering);
43 fence(ordering);
44 }
45}
46
47/// Signals to the processor that the caller is inside a busy-wait spin-loop.
48pub fn spinLoopHint() callconv(.Inline) void {
49 const hint_instruction = switch (target.cpu.arch) {
50 // No-op instruction that can hint to save (or share with a hardware-thread) pipelining/power resources
51 // https://software.intel.com/content/www/us/en/develop/articles/benefitting-power-and-performance-sleep-loops.html
52 .i386, .x86_64 => "pause",
53
54 // No-op instruction that serves as a hardware-thread resource yield hint.
55 // https://stackoverflow.com/a/7588941
56 .powerpc64, .powerpc64le => "or 27, 27, 27",
57
58 // `isb` appears more reliable for releasing execution resources than `yield` on common aarch64 CPUs.
59 // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8258604
60 // https://bugs.mysql.com/bug.php?id=100664
61 .aarch64, .aarch64_be, .aarch64_32 => "isb",
62
63 // `yield` was introduced in v6k but is also available on v6m.
64 // https://www.keil.com/support/man/docs/armasm/armasm_dom1361289926796.htm
65 .arm, .armeb, .thumb, .thumbeb => blk: {
66 const can_yield = comptime std.Target.arm.featureSetHasAny(target.cpu.features, .{ .has_v6k, .has_v6m });
67 const instruction = if (can_yield) "yield" else "";
68 break :blk instruction;
69 },
70
71 else => "",
72 };
73
74 // Memory barrier to prevent the compiler from optimizing away the spin-loop
75 // even if no hint_instruction was provided.
76 asm volatile (hint_instruction ::: "memory");
77}
78
79test "spinLoopHint" {
80 var i: usize = 10;
81 while (i > 0) : (i -= 1) {
82 spinLoopHint();
83 }
1684}
lib/std/atomic/Atomic.zig created+522
......@@ -0,0 +1,522 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("../std.zig");
8
9const testing = std.testing;
10const target = std.Target.current;
11const Ordering = std.atomic.Ordering;
12
13pub fn Atomic(comptime T: type) type {
14 return extern struct {
15 value: T,
16
17 const Self = @This();
18
19 pub fn init(value: T) Self {
20 return .{ .value = value };
21 }
22
23 /// Non-atomically load from the atomic value without synchronization.
24 /// Care must be taken to avoid data-races when interacting with other atomic operations.
25 pub fn loadUnchecked(self: Self) T {
26 return self.value;
27 }
28
29 /// Non-atomically store to the atomic value without synchronization.
30 /// Care must be taken to avoid data-races when interacting with other atomic operations.
31 pub fn storeUnchecked(self: *Self, value: T) void {
32 self.value = value;
33 }
34
35 pub fn load(self: *const Self, comptime ordering: Ordering) T {
36 return switch (ordering) {
37 .AcqRel => @compileError(@tagName(ordering) ++ " implies " ++ @tagName(Ordering.Release) ++ " which is only allowed on atomic stores"),
38 .Release => @compileError(@tagName(ordering) ++ " is only allowed on atomic stores"),
39 else => @atomicLoad(T, &self.value, ordering),
40 };
41 }
42
43 pub fn store(self: *Self, value: T, comptime ordering: Ordering) void {
44 return switch (ordering) {
45 .AcqRel => @compileError(@tagName(ordering) ++ " implies " ++ @tagName(Ordering.Acquire) ++ " which is only allowed on atomic loads"),
46 .Acquire => @compileError(@tagName(ordering) ++ " is only allowed on atomic loads"),
47 else => @atomicStore(T, &self.value, value, ordering),
48 };
49 }
50
51 pub fn swap(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T {
52 return self.rmw(.Xchg, value, ordering);
53 }
54
55 pub fn compareAndSwap(
56 self: *Self,
57 compare: T,
58 exchange: T,
59 comptime success: Ordering,
60 comptime failure: Ordering,
61 ) callconv(.Inline) ?T {
62 return self.cmpxchg(true, compare, exchange, success, failure);
63 }
64
65 pub fn tryCompareAndSwap(
66 self: *Self,
67 compare: T,
68 exchange: T,
69 comptime success: Ordering,
70 comptime failure: Ordering,
71 ) callconv(.Inline) ?T {
72 return self.cmpxchg(false, compare, exchange, success, failure);
73 }
74
75 fn cmpxchg(
76 self: *Self,
77 comptime is_strong: bool,
78 compare: T,
79 exchange: T,
80 comptime success: Ordering,
81 comptime failure: Ordering,
82 ) callconv(.Inline) ?T {
83 if (success == .Unordered or failure == .Unordered) {
84 @compileError(@tagName(Ordering.Unordered) ++ " is only allowed on atomic loads and stores");
85 }
86
87 comptime var success_is_stronger = switch (failure) {
88 .SeqCst => success == .SeqCst,
89 .AcqRel => @compileError(@tagName(failure) ++ " implies " ++ @tagName(Ordering.Release) ++ " which is only allowed on success"),
90 .Acquire => success == .SeqCst or success == .AcqRel or success == .Acquire,
91 .Release => @compileError(@tagName(failure) ++ " is only allowed on success"),
92 .Monotonic => true,
93 .Unordered => unreachable,
94 };
95
96 if (!success_is_stronger) {
97 @compileError(@tagName(success) ++ " must be stronger than " ++ @tagName(failure));
98 }
99
100 return switch (is_strong) {
101 true => @cmpxchgStrong(T, &self.value, compare, exchange, success, failure),
102 false => @cmpxchgWeak(T, &self.value, compare, exchange, success, failure),
103 };
104 }
105
106 fn rmw(
107 self: *Self,
108 comptime op: std.builtin.AtomicRmwOp,
109 value: T,
110 comptime ordering: Ordering,
111 ) callconv(.Inline) T {
112 return @atomicRmw(T, &self.value, op, value, ordering);
113 }
114
115 fn exportWhen(comptime condition: bool, comptime functions: type) type {
116 return if (condition) functions else struct {};
117 }
118
119 pub usingnamespace exportWhen(std.meta.trait.isNumber(T), struct {
120 pub fn fetchAdd(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T {
121 return self.rmw(.Add, value, ordering);
122 }
123
124 pub fn fetchSub(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T {
125 return self.rmw(.Sub, value, ordering);
126 }
127
128 pub fn fetchMin(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T {
129 return self.rmw(.Min, value, ordering);
130 }
131
132 pub fn fetchMax(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T {
133 return self.rmw(.Max, value, ordering);
134 }
135 });
136
137 pub usingnamespace exportWhen(std.meta.trait.isIntegral(T), struct {
138 pub fn fetchAnd(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T {
139 return self.rmw(.And, value, ordering);
140 }
141
142 pub fn fetchNand(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T {
143 return self.rmw(.Nand, value, ordering);
144 }
145
146 pub fn fetchOr(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T {
147 return self.rmw(.Or, value, ordering);
148 }
149
150 pub fn fetchXor(self: *Self, value: T, comptime ordering: Ordering) callconv(.Inline) T {
151 return self.rmw(.Xor, value, ordering);
152 }
153
154 const Bit = std.math.Log2Int(T);
155 const BitRmwOp = enum {
156 Set,
157 Reset,
158 Toggle,
159 };
160
161 pub fn bitSet(self: *Self, bit: Bit, comptime ordering: Ordering) callconv(.Inline) u1 {
162 return bitRmw(self, .Set, bit, ordering);
163 }
164
165 pub fn bitReset(self: *Self, bit: Bit, comptime ordering: Ordering) callconv(.Inline) u1 {
166 return bitRmw(self, .Reset, bit, ordering);
167 }
168
169 pub fn bitToggle(self: *Self, bit: Bit, comptime ordering: Ordering) callconv(.Inline) u1 {
170 return bitRmw(self, .Toggle, bit, ordering);
171 }
172
173 fn bitRmw(
174 self: *Self,
175 comptime op: BitRmwOp,
176 bit: Bit,
177 comptime ordering: Ordering,
178 ) callconv(.Inline) u1 {
179 // x86 supports dedicated bitwise instructions
180 if (comptime target.cpu.arch.isX86() and @sizeOf(T) >= 2 and @sizeOf(T) <= 8) {
181 const instruction = switch (op) {
182 .Set => "lock bts",
183 .Reset => "lock btr",
184 .Toggle => "lock btc",
185 };
186
187 const suffix = switch (@sizeOf(T)) {
188 2 => "w",
189 4 => "l",
190 8 => "q",
191 else => @compileError("Invalid atomic type " ++ @typeName(T)),
192 };
193
194 const old_bit = asm volatile (instruction ++ suffix ++ " %[bit], %[ptr]"
195 : [result] "={@ccc}" (-> u8) // LLVM doesn't support u1 flag register return values
196 : [ptr] "*p" (&self.value),
197 [bit] "X" (@as(T, bit))
198 : "cc", "memory"
199 );
200
201 return @intCast(u1, old_bit);
202 }
203
204 const mask = @as(T, 1) << bit;
205 const value = switch (op) {
206 .Set => self.fetchOr(mask, ordering),
207 .Reset => self.fetchAnd(~mask, ordering),
208 .Toggle => self.fetchXor(mask, ordering),
209 };
210
211 return @boolToInt(value & mask != 0);
212 }
213 });
214 };
215}
216
217fn atomicIntTypes() []const type {
218 comptime var bytes = 1;
219 comptime var types: []const type = &[_]type{};
220 inline while (bytes <= @sizeOf(usize)) : (bytes *= 2) {
221 types = types ++ &[_]type{std.meta.Int(.unsigned, bytes * 8)};
222 }
223 return types;
224}
225
226test "Atomic.loadUnchecked" {
227 inline for (atomicIntTypes()) |Int| {
228 var x = Atomic(Int).init(5);
229 try testing.expectEqual(x.loadUnchecked(), 5);
230 }
231}
232
233test "Atomic.storeUnchecked" {
234 inline for (atomicIntTypes()) |Int| {
235 var x = Atomic(usize).init(5);
236 x.storeUnchecked(10);
237 try testing.expectEqual(x.loadUnchecked(), 10);
238 }
239}
240
241test "Atomic.load" {
242 inline for (atomicIntTypes()) |Int| {
243 inline for (.{ .Unordered, .Monotonic, .Acquire, .SeqCst }) |ordering| {
244 var x = Atomic(Int).init(5);
245 try testing.expectEqual(x.load(ordering), 5);
246 }
247 }
248}
249
250test "Atomic.store" {
251 inline for (atomicIntTypes()) |Int| {
252 inline for (.{ .Unordered, .Monotonic, .Release, .SeqCst }) |ordering| {
253 var x = Atomic(usize).init(5);
254 x.store(10, ordering);
255 try testing.expectEqual(x.load(.SeqCst), 10);
256 }
257 }
258}
259
260const atomic_rmw_orderings = [_]Ordering{
261 .Monotonic,
262 .Acquire,
263 .Release,
264 .AcqRel,
265 .SeqCst,
266};
267
268test "Atomic.swap" {
269 inline for (atomic_rmw_orderings) |ordering| {
270 var x = Atomic(usize).init(5);
271 try testing.expectEqual(x.swap(10, ordering), 5);
272 try testing.expectEqual(x.load(.SeqCst), 10);
273
274 var y = Atomic(enum(usize) { a, b, c }).init(.c);
275 try testing.expectEqual(y.swap(.a, ordering), .c);
276 try testing.expectEqual(y.load(.SeqCst), .a);
277
278 var z = Atomic(f32).init(5.0);
279 try testing.expectEqual(z.swap(10.0, ordering), 5.0);
280 try testing.expectEqual(z.load(.SeqCst), 10.0);
281
282 var a = Atomic(bool).init(false);
283 try testing.expectEqual(a.swap(true, ordering), false);
284 try testing.expectEqual(a.load(.SeqCst), true);
285
286 var b = Atomic(?*u8).init(null);
287 try testing.expectEqual(b.swap(@intToPtr(?*u8, @alignOf(u8)), ordering), null);
288 try testing.expectEqual(b.load(.SeqCst), @intToPtr(?*u8, @alignOf(u8)));
289 }
290}
291
292const atomic_cmpxchg_orderings = [_][2]Ordering{
293 .{ .Monotonic, .Monotonic },
294 .{ .Acquire, .Monotonic },
295 .{ .Acquire, .Acquire },
296 .{ .Release, .Monotonic },
297 // Although accepted by LLVM, acquire failure implies AcqRel success
298 // .{ .Release, .Acquire },
299 .{ .AcqRel, .Monotonic },
300 .{ .AcqRel, .Acquire },
301 .{ .SeqCst, .Monotonic },
302 .{ .SeqCst, .Acquire },
303 .{ .SeqCst, .SeqCst },
304};
305
306test "Atomic.compareAndSwap" {
307 inline for (atomicIntTypes()) |Int| {
308 inline for (atomic_cmpxchg_orderings) |ordering| {
309 var x = Atomic(Int).init(0);
310 try testing.expectEqual(x.compareAndSwap(1, 0, ordering[0], ordering[1]), 0);
311 try testing.expectEqual(x.load(.SeqCst), 0);
312 try testing.expectEqual(x.compareAndSwap(0, 1, ordering[0], ordering[1]), null);
313 try testing.expectEqual(x.load(.SeqCst), 1);
314 try testing.expectEqual(x.compareAndSwap(1, 0, ordering[0], ordering[1]), null);
315 try testing.expectEqual(x.load(.SeqCst), 0);
316 }
317 }
318}
319
320test "Atomic.tryCompareAndSwap" {
321 inline for (atomicIntTypes()) |Int| {
322 inline for (atomic_cmpxchg_orderings) |ordering| {
323 var x = Atomic(Int).init(0);
324
325 try testing.expectEqual(x.tryCompareAndSwap(1, 0, ordering[0], ordering[1]), 0);
326 try testing.expectEqual(x.load(.SeqCst), 0);
327
328 while (x.tryCompareAndSwap(0, 1, ordering[0], ordering[1])) |_| {}
329 try testing.expectEqual(x.load(.SeqCst), 1);
330
331 while (x.tryCompareAndSwap(1, 0, ordering[0], ordering[1])) |_| {}
332 try testing.expectEqual(x.load(.SeqCst), 0);
333 }
334 }
335}
336
337test "Atomic.fetchAdd" {
338 inline for (atomicIntTypes()) |Int| {
339 inline for (atomic_rmw_orderings) |ordering| {
340 var x = Atomic(Int).init(5);
341 try testing.expectEqual(x.fetchAdd(5, ordering), 5);
342 try testing.expectEqual(x.load(.SeqCst), 10);
343 try testing.expectEqual(x.fetchAdd(std.math.maxInt(Int), ordering), 10);
344 try testing.expectEqual(x.load(.SeqCst), 9);
345 }
346 }
347}
348
349test "Atomic.fetchSub" {
350 inline for (atomicIntTypes()) |Int| {
351 inline for (atomic_rmw_orderings) |ordering| {
352 var x = Atomic(Int).init(5);
353 try testing.expectEqual(x.fetchSub(5, ordering), 5);
354 try testing.expectEqual(x.load(.SeqCst), 0);
355 try testing.expectEqual(x.fetchSub(1, ordering), 0);
356 try testing.expectEqual(x.load(.SeqCst), std.math.maxInt(Int));
357 }
358 }
359}
360
361test "Atomic.fetchMin" {
362 inline for (atomicIntTypes()) |Int| {
363 inline for (atomic_rmw_orderings) |ordering| {
364 var x = Atomic(Int).init(5);
365 try testing.expectEqual(x.fetchMin(0, ordering), 5);
366 try testing.expectEqual(x.load(.SeqCst), 0);
367 try testing.expectEqual(x.fetchMin(10, ordering), 0);
368 try testing.expectEqual(x.load(.SeqCst), 0);
369 }
370 }
371}
372
373test "Atomic.fetchMax" {
374 inline for (atomicIntTypes()) |Int| {
375 inline for (atomic_rmw_orderings) |ordering| {
376 var x = Atomic(Int).init(5);
377 try testing.expectEqual(x.fetchMax(10, ordering), 5);
378 try testing.expectEqual(x.load(.SeqCst), 10);
379 try testing.expectEqual(x.fetchMax(5, ordering), 10);
380 try testing.expectEqual(x.load(.SeqCst), 10);
381 }
382 }
383}
384
385test "Atomic.fetchAnd" {
386 inline for (atomicIntTypes()) |Int| {
387 inline for (atomic_rmw_orderings) |ordering| {
388 var x = Atomic(Int).init(0b11);
389 try testing.expectEqual(x.fetchAnd(0b10, ordering), 0b11);
390 try testing.expectEqual(x.load(.SeqCst), 0b10);
391 try testing.expectEqual(x.fetchAnd(0b00, ordering), 0b10);
392 try testing.expectEqual(x.load(.SeqCst), 0b00);
393 }
394 }
395}
396
397test "Atomic.fetchNand" {
398 inline for (atomicIntTypes()) |Int| {
399 inline for (atomic_rmw_orderings) |ordering| {
400 var x = Atomic(Int).init(0b11);
401 try testing.expectEqual(x.fetchNand(0b10, ordering), 0b11);
402 try testing.expectEqual(x.load(.SeqCst), ~@as(Int, 0b10));
403 try testing.expectEqual(x.fetchNand(0b00, ordering), ~@as(Int, 0b10));
404 try testing.expectEqual(x.load(.SeqCst), ~@as(Int, 0b00));
405 }
406 }
407}
408
409test "Atomic.fetchOr" {
410 inline for (atomicIntTypes()) |Int| {
411 inline for (atomic_rmw_orderings) |ordering| {
412 var x = Atomic(Int).init(0b11);
413 try testing.expectEqual(x.fetchOr(0b100, ordering), 0b11);
414 try testing.expectEqual(x.load(.SeqCst), 0b111);
415 try testing.expectEqual(x.fetchOr(0b010, ordering), 0b111);
416 try testing.expectEqual(x.load(.SeqCst), 0b111);
417 }
418 }
419}
420
421test "Atomic.fetchXor" {
422 inline for (atomicIntTypes()) |Int| {
423 inline for (atomic_rmw_orderings) |ordering| {
424 var x = Atomic(Int).init(0b11);
425 try testing.expectEqual(x.fetchXor(0b10, ordering), 0b11);
426 try testing.expectEqual(x.load(.SeqCst), 0b01);
427 try testing.expectEqual(x.fetchXor(0b01, ordering), 0b01);
428 try testing.expectEqual(x.load(.SeqCst), 0b00);
429 }
430 }
431}
432
433test "Atomic.bitSet" {
434 inline for (atomicIntTypes()) |Int| {
435 inline for (atomic_rmw_orderings) |ordering| {
436 var x = Atomic(Int).init(0);
437 const bit_array = @as([std.meta.bitCount(Int)]void, undefined);
438
439 for (bit_array) |_, bit_index| {
440 const bit = @intCast(std.math.Log2Int(Int), bit_index);
441 const mask = @as(Int, 1) << bit;
442
443 // setting the bit should change the bit
444 try testing.expect(x.load(.SeqCst) & mask == 0);
445 try testing.expectEqual(x.bitSet(bit, ordering), 0);
446 try testing.expect(x.load(.SeqCst) & mask != 0);
447
448 // setting it again shouldn't change the bit
449 try testing.expectEqual(x.bitSet(bit, ordering), 1);
450 try testing.expect(x.load(.SeqCst) & mask != 0);
451
452 // all the previous bits should have not changed (still be set)
453 for (bit_array[0..bit_index]) |_, prev_bit_index| {
454 const prev_bit = @intCast(std.math.Log2Int(Int), prev_bit_index);
455 const prev_mask = @as(Int, 1) << prev_bit;
456 try testing.expect(x.load(.SeqCst) & prev_mask != 0);
457 }
458 }
459 }
460 }
461}
462
463test "Atomic.bitReset" {
464 inline for (atomicIntTypes()) |Int| {
465 inline for (atomic_rmw_orderings) |ordering| {
466 var x = Atomic(Int).init(0);
467 const bit_array = @as([std.meta.bitCount(Int)]void, undefined);
468
469 for (bit_array) |_, bit_index| {
470 const bit = @intCast(std.math.Log2Int(Int), bit_index);
471 const mask = @as(Int, 1) << bit;
472 x.storeUnchecked(x.loadUnchecked() | mask);
473
474 // unsetting the bit should change the bit
475 try testing.expect(x.load(.SeqCst) & mask != 0);
476 try testing.expectEqual(x.bitReset(bit, ordering), 1);
477 try testing.expect(x.load(.SeqCst) & mask == 0);
478
479 // unsetting it again shouldn't change the bit
480 try testing.expectEqual(x.bitReset(bit, ordering), 0);
481 try testing.expect(x.load(.SeqCst) & mask == 0);
482
483 // all the previous bits should have not changed (still be reset)
484 for (bit_array[0..bit_index]) |_, prev_bit_index| {
485 const prev_bit = @intCast(std.math.Log2Int(Int), prev_bit_index);
486 const prev_mask = @as(Int, 1) << prev_bit;
487 try testing.expect(x.load(.SeqCst) & prev_mask == 0);
488 }
489 }
490 }
491 }
492}
493
494test "Atomic.bitToggle" {
495 inline for (atomicIntTypes()) |Int| {
496 inline for (atomic_rmw_orderings) |ordering| {
497 var x = Atomic(Int).init(0);
498 const bit_array = @as([std.meta.bitCount(Int)]void, undefined);
499
500 for (bit_array) |_, bit_index| {
501 const bit = @intCast(std.math.Log2Int(Int), bit_index);
502 const mask = @as(Int, 1) << bit;
503
504 // toggling the bit should change the bit
505 try testing.expect(x.load(.SeqCst) & mask == 0);
506 try testing.expectEqual(x.bitToggle(bit, ordering), 0);
507 try testing.expect(x.load(.SeqCst) & mask != 0);
508
509 // toggling it again *should* change the bit
510 try testing.expectEqual(x.bitToggle(bit, ordering), 1);
511 try testing.expect(x.load(.SeqCst) & mask == 0);
512
513 // all the previous bits should have not changed (still be toggled back)
514 for (bit_array[0..bit_index]) |_, prev_bit_index| {
515 const prev_bit = @intCast(std.math.Log2Int(Int), prev_bit_index);
516 const prev_mask = @as(Int, 1) << prev_bit;
517 try testing.expect(x.load(.SeqCst) & prev_mask == 0);
518 }
519 }
520 }
521 }
522}
lib/std/atomic/bool.zig deleted-55
......@@ -1,55 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("std");
8const builtin = std.builtin;
9const testing = std.testing;
10
11/// Thread-safe, lock-free boolean
12pub const Bool = extern struct {
13 unprotected_value: bool,
14
15 pub const Self = @This();
16
17 pub fn init(init_val: bool) Self {
18 return Self{ .unprotected_value = init_val };
19 }
20
21 // xchg is only valid rmw operation for a bool
22 /// Atomically modifies memory and then returns the previous value.
23 pub fn xchg(self: *Self, operand: bool, comptime ordering: std.builtin.AtomicOrder) bool {
24 switch (ordering) {
25 .Monotonic, .Acquire, .Release, .AcqRel, .SeqCst => {},
26 else => @compileError("Invalid ordering '" ++ @tagName(ordering) ++ "' for a RMW operation"),
27 }
28 return @atomicRmw(bool, &self.unprotected_value, .Xchg, operand, ordering);
29 }
30
31 pub fn load(self: *const Self, comptime ordering: std.builtin.AtomicOrder) bool {
32 switch (ordering) {
33 .Unordered, .Monotonic, .Acquire, .SeqCst => {},
34 else => @compileError("Invalid ordering '" ++ @tagName(ordering) ++ "' for a load operation"),
35 }
36 return @atomicLoad(bool, &self.unprotected_value, ordering);
37 }
38
39 pub fn store(self: *Self, value: bool, comptime ordering: std.builtin.AtomicOrder) void {
40 switch (ordering) {
41 .Unordered, .Monotonic, .Release, .SeqCst => {},
42 else => @compileError("Invalid ordering '" ++ @tagName(ordering) ++ "' for a store operation"),
43 }
44 @atomicStore(bool, &self.unprotected_value, value, ordering);
45 }
46};
47
48test "std.atomic.Bool" {
49 var a = Bool.init(false);
50 try testing.expectEqual(false, a.xchg(false, .SeqCst));
51 try testing.expectEqual(false, a.load(.SeqCst));
52 a.store(true, .SeqCst);
53 try testing.expectEqual(true, a.xchg(false, .SeqCst));
54 try testing.expectEqual(false, a.load(.SeqCst));
55}
lib/std/atomic/int.zig deleted-92
......@@ -1,92 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("std");
8const builtin = std.builtin;
9const testing = std.testing;
10
11/// Thread-safe, lock-free integer
12pub fn Int(comptime T: type) type {
13 if (!std.meta.trait.isIntegral(T))
14 @compileError("Expected integral type, got '" ++ @typeName(T) ++ "'");
15
16 return extern struct {
17 unprotected_value: T,
18
19 pub const Self = @This();
20
21 pub fn init(init_val: T) Self {
22 return Self{ .unprotected_value = init_val };
23 }
24
25 /// Read, Modify, Write
26 pub fn rmw(self: *Self, comptime op: builtin.AtomicRmwOp, operand: T, comptime ordering: builtin.AtomicOrder) T {
27 switch (ordering) {
28 .Monotonic, .Acquire, .Release, .AcqRel, .SeqCst => {},
29 else => @compileError("Invalid ordering '" ++ @tagName(ordering) ++ "' for a RMW operation"),
30 }
31 return @atomicRmw(T, &self.unprotected_value, op, operand, ordering);
32 }
33
34 pub fn load(self: *const Self, comptime ordering: builtin.AtomicOrder) T {
35 switch (ordering) {
36 .Unordered, .Monotonic, .Acquire, .SeqCst => {},
37 else => @compileError("Invalid ordering '" ++ @tagName(ordering) ++ "' for a load operation"),
38 }
39 return @atomicLoad(T, &self.unprotected_value, ordering);
40 }
41
42 pub fn store(self: *Self, value: T, comptime ordering: builtin.AtomicOrder) void {
43 switch (ordering) {
44 .Unordered, .Monotonic, .Release, .SeqCst => {},
45 else => @compileError("Invalid ordering '" ++ @tagName(ordering) ++ "' for a store operation"),
46 }
47 @atomicStore(T, &self.unprotected_value, value, ordering);
48 }
49
50 /// Twos complement wraparound increment
51 /// Returns previous value
52 pub fn incr(self: *Self) T {
53 return self.rmw(.Add, 1, .SeqCst);
54 }
55
56 /// Twos complement wraparound decrement
57 /// Returns previous value
58 pub fn decr(self: *Self) T {
59 return self.rmw(.Sub, 1, .SeqCst);
60 }
61
62 pub fn get(self: *const Self) T {
63 return self.load(.SeqCst);
64 }
65
66 pub fn set(self: *Self, new_value: T) void {
67 self.store(new_value, .SeqCst);
68 }
69
70 pub fn xchg(self: *Self, new_value: T) T {
71 return self.rmw(.Xchg, new_value, .SeqCst);
72 }
73
74 /// Twos complement wraparound add
75 /// Returns previous value
76 pub fn fetchAdd(self: *Self, op: T) T {
77 return self.rmw(.Add, op, .SeqCst);
78 }
79 };
80}
81
82test "std.atomic.Int" {
83 var a = Int(u8).init(0);
84 try testing.expectEqual(@as(u8, 0), a.incr());
85 try testing.expectEqual(@as(u8, 1), a.load(.SeqCst));
86 a.store(42, .SeqCst);
87 try testing.expectEqual(@as(u8, 42), a.decr());
88 try testing.expectEqual(@as(u8, 41), a.xchg(100));
89 try testing.expectEqual(@as(u8, 100), a.fetchAdd(5));
90 try testing.expectEqual(@as(u8, 105), a.get());
91 a.set(200);
92}
lib/std/json.zig+4-1
......@@ -2111,7 +2111,10 @@ test "parse into struct with duplicate field" {
21112111 const ballast = try testing.allocator.alloc(u64, 1);
21122112 defer testing.allocator.free(ballast);
21132113
2114 const options_first = ParseOptions{ .allocator = testing.allocator, .duplicate_field_behavior = .UseFirst };
2114 const options_first = ParseOptions{
2115 .allocator = testing.allocator,
2116 .duplicate_field_behavior = .UseFirst,
2117 };
21152118
21162119 const options_last = ParseOptions{
21172120 .allocator = testing.allocator,
lib/std/os.zig+1-1
......@@ -5534,7 +5534,7 @@ pub const CopyFileRangeError = error{
55345534
55355535var has_copy_file_range_syscall = init: {
55365536 const kernel_has_syscall = std.Target.current.os.isAtLeast(.linux, .{ .major = 4, .minor = 5 }) orelse true;
5537 break :init std.atomic.Bool.init(kernel_has_syscall);
5537 break :init std.atomic.Atomic(bool).init(kernel_has_syscall);
55385538};
55395539
55405540/// Transfer data between file descriptors at specified offsets.
lib/std/packed_int_array.zig+3-3
......@@ -379,9 +379,9 @@ test "PackedIntArray" {
379379}
380380
381381test "PackedIntIo" {
382 const bytes = [_]u8 { 0b01101_000, 0b01011_110, 0b00011_101 };
383 try testing.expectEqual(@as(u15, 0x2bcd), PackedIntIo(u15, .Little).get(&bytes, 0, 3));
384 try testing.expectEqual(@as(u16, 0xabcd), PackedIntIo(u16, .Little).get(&bytes, 0, 3));
382 const bytes = [_]u8{ 0b01101_000, 0b01011_110, 0b00011_101 };
383 try testing.expectEqual(@as(u15, 0x2bcd), PackedIntIo(u15, .Little).get(&bytes, 0, 3));
384 try testing.expectEqual(@as(u16, 0xabcd), PackedIntIo(u16, .Little).get(&bytes, 0, 3));
385385 try testing.expectEqual(@as(u17, 0x1abcd), PackedIntIo(u17, .Little).get(&bytes, 0, 3));
386386 try testing.expectEqual(@as(u18, 0x3abcd), PackedIntIo(u18, .Little).get(&bytes, 0, 3));
387387}
lib/std/target.zig+7
......@@ -767,6 +767,13 @@ pub const Target = struct {
767767 spirv32,
768768 spirv64,
769769
770 pub fn isX86(arch: Arch) bool {
771 return switch (arch) {
772 .i386, .x86_64 => true,
773 else => false,
774 };
775 }
776
770777 pub fn isARM(arch: Arch) bool {
771778 return switch (arch) {
772779 .arm, .armeb => true,
src/BuiltinFn.zig+1-1
......@@ -400,7 +400,7 @@ pub const list = list: {
400400 "@fence",
401401 .{
402402 .tag = .fence,
403 .param_count = 0,
403 .param_count = 1,
404404 },
405405 },
406406 .{