authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-11-12 17:45:29+00:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2019-11-12 17:45:29+00:00
loge32b4829f4f91ee412ead7a3851f6e271d9fb07e
tree2f04d9d06b0ee3973c1f83afdf6018fcac4d99b7
parent710ccacfa3307fc642f4bac71f894e3d8a18764a
parent5194fc57d1c206d71654b4f3e43bfcb300bf43c5
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #3670 from Vexu/atomics-enum

Support atomic operations with enums

6 files changed, 99 insertions(+), 58 deletions(-)

lib/std/event/future.zig+11-10
......@@ -12,12 +12,13 @@ pub fn Future(comptime T: type) type {
1212 return struct {
1313 lock: Lock,
1414 data: T,
15 available: Available,
1516
16 /// TODO make this an enum
17 /// 0 - not started
18 /// 1 - started
19 /// 2 - finished
20 available: u8,
17 const Available = enum(u8) {
18 NotStarted,
19 Started,
20 Finished,
21 };
2122
2223 const Self = @This();
2324 const Queue = std.atomic.Queue(anyframe);
......@@ -34,7 +35,7 @@ pub fn Future(comptime T: type) type {
3435 /// available.
3536 /// Thread-safe.
3637 pub async fn get(self: *Self) *T {
37 if (@atomicLoad(u8, &self.available, .SeqCst) == 2) {
38 if (@atomicLoad(Available, &self.available, .SeqCst) == .Finished) {
3839 return &self.data;
3940 }
4041 const held = self.lock.acquire();
......@@ -46,7 +47,7 @@ pub fn Future(comptime T: type) type {
4647 /// Gets the data without waiting for it. If it's available, a pointer is
4748 /// returned. Otherwise, null is returned.
4849 pub fn getOrNull(self: *Self) ?*T {
49 if (@atomicLoad(u8, &self.available, .SeqCst) == 2) {
50 if (@atomicLoad(Available, &self.available, .SeqCst) == .Finished) {
5051 return &self.data;
5152 } else {
5253 return null;
......@@ -59,7 +60,7 @@ pub fn Future(comptime T: type) type {
5960 /// It's not required to call start() before resolve() but it can be useful since
6061 /// this method is thread-safe.
6162 pub async fn start(self: *Self) ?*T {
62 const state = @cmpxchgStrong(u8, &self.available, 0, 1, .SeqCst, .SeqCst) orelse return null;
63 const state = @cmpxchgStrong(Available, &self.available, .NotStarted, .Started, .SeqCst, .SeqCst) orelse return null;
6364 switch (state) {
6465 1 => {
6566 const held = self.lock.acquire();
......@@ -74,8 +75,8 @@ pub fn Future(comptime T: type) type {
7475 /// Make the data become available. May be called only once.
7576 /// Before calling this, modify the `data` property.
7677 pub fn resolve(self: *Self) void {
77 const prev = @atomicRmw(u8, &self.available, .Xchg, 2, .SeqCst);
78 assert(prev == 0 or prev == 1); // resolve() called twice
78 const prev = @atomicRmw(Available, &self.available, .Xchg, .Finished, .SeqCst);
79 assert(prev != .Finished); // resolve() called twice
7980 Lock.Held.release(Lock.Held{ .lock = &self.lock });
8081 }
8182 };
lib/std/event/rwlock.zig+16-16
......@@ -13,17 +13,17 @@ const Loop = std.event.Loop;
1313/// When a write lock is held, it will not be released until the writer queue is empty.
1414/// TODO: make this API also work in blocking I/O mode
1515pub const RwLock = struct {
16 shared_state: u8, // TODO make this an enum
16 shared_state: State,
1717 writer_queue: Queue,
1818 reader_queue: Queue,
1919 writer_queue_empty_bit: u8, // TODO make this a bool
2020 reader_queue_empty_bit: u8, // TODO make this a bool
2121 reader_lock_count: usize,
2222
23 const State = struct {
24 const Unlocked = 0;
25 const WriteLock = 1;
26 const ReadLock = 2;
23 const State = enum(u8) {
24 Unlocked,
25 WriteLock,
26 ReadLock,
2727 };
2828
2929 const Queue = std.atomic.Queue(anyframe);
......@@ -41,7 +41,7 @@ pub const RwLock = struct {
4141 }
4242
4343 _ = @atomicRmw(u8, &self.lock.reader_queue_empty_bit, .Xchg, 1, .SeqCst);
44 if (@cmpxchgStrong(u8, &self.lock.shared_state, State.ReadLock, State.Unlocked, .SeqCst, .SeqCst) != null) {
44 if (@cmpxchgStrong(State, &self.lock.shared_state, .ReadLock, .Unlocked, .SeqCst, .SeqCst) != null) {
4545 // Didn't unlock. Someone else's problem.
4646 return;
4747 }
......@@ -64,7 +64,7 @@ pub const RwLock = struct {
6464 // We need to release the write lock. Check if any readers are waiting to grab the lock.
6565 if (@atomicLoad(u8, &self.lock.reader_queue_empty_bit, .SeqCst) == 0) {
6666 // Switch to a read lock.
67 _ = @atomicRmw(u8, &self.lock.shared_state, .Xchg, State.ReadLock, .SeqCst);
67 _ = @atomicRmw(State, &self.lock.shared_state, .Xchg, .ReadLock, .SeqCst);
6868 while (self.lock.reader_queue.get()) |node| {
6969 global_event_loop.onNextTick(node);
7070 }
......@@ -72,7 +72,7 @@ pub const RwLock = struct {
7272 }
7373
7474 _ = @atomicRmw(u8, &self.lock.writer_queue_empty_bit, .Xchg, 1, .SeqCst);
75 _ = @atomicRmw(u8, &self.lock.shared_state, .Xchg, State.Unlocked, .SeqCst);
75 _ = @atomicRmw(State, &self.lock.shared_state, .Xchg, State.Unlocked, .SeqCst);
7676
7777 self.lock.commonPostUnlock();
7878 }
......@@ -80,7 +80,7 @@ pub const RwLock = struct {
8080
8181 pub fn init() RwLock {
8282 return RwLock{
83 .shared_state = State.Unlocked,
83 .shared_state = .Unlocked,
8484 .writer_queue = Queue.init(),
8585 .writer_queue_empty_bit = 1,
8686 .reader_queue = Queue.init(),
......@@ -92,7 +92,7 @@ pub const RwLock = struct {
9292 /// Must be called when not locked. Not thread safe.
9393 /// All calls to acquire() and release() must complete before calling deinit().
9494 pub fn deinit(self: *RwLock) void {
95 assert(self.shared_state == State.Unlocked);
95 assert(self.shared_state == .Unlocked);
9696 while (self.writer_queue.get()) |node| resume node.data;
9797 while (self.reader_queue.get()) |node| resume node.data;
9898 }
......@@ -116,7 +116,7 @@ pub const RwLock = struct {
116116 _ = @atomicRmw(u8, &self.reader_queue_empty_bit, .Xchg, 0, .SeqCst);
117117
118118 // Here we don't care if we are the one to do the locking or if it was already locked for reading.
119 const have_read_lock = if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.ReadLock, .SeqCst, .SeqCst)) |old_state| old_state == State.ReadLock else true;
119 const have_read_lock = if (@cmpxchgStrong(State, &self.shared_state, .Unlocked, .ReadLock, .SeqCst, .SeqCst)) |old_state| old_state == .ReadLock else true;
120120 if (have_read_lock) {
121121 // Give out all the read locks.
122122 if (self.reader_queue.get()) |first_node| {
......@@ -147,7 +147,7 @@ pub const RwLock = struct {
147147 _ = @atomicRmw(u8, &self.writer_queue_empty_bit, .Xchg, 0, .SeqCst);
148148
149149 // Here we must be the one to acquire the write lock. It cannot already be locked.
150 if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.WriteLock, .SeqCst, .SeqCst) == null) {
150 if (@cmpxchgStrong(State, &self.shared_state, .Unlocked, .WriteLock, .SeqCst, .SeqCst) == null) {
151151 // We now have a write lock.
152152 if (self.writer_queue.get()) |node| {
153153 // Whether this node is us or someone else, we tail resume it.
......@@ -166,7 +166,7 @@ pub const RwLock = struct {
166166 // But if there's a writer_queue item or a reader_queue item,
167167 // we are the actor which must loop and attempt to grab the lock again.
168168 if (@atomicLoad(u8, &self.writer_queue_empty_bit, .SeqCst) == 0) {
169 if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.WriteLock, .SeqCst, .SeqCst) != null) {
169 if (@cmpxchgStrong(State, &self.shared_state, .Unlocked, .WriteLock, .SeqCst, .SeqCst) != null) {
170170 // We did not obtain the lock. Great, the queues are someone else's problem.
171171 return;
172172 }
......@@ -177,12 +177,12 @@ pub const RwLock = struct {
177177 }
178178 // Release the lock again.
179179 _ = @atomicRmw(u8, &self.writer_queue_empty_bit, .Xchg, 1, .SeqCst);
180 _ = @atomicRmw(u8, &self.shared_state, .Xchg, State.Unlocked, .SeqCst);
180 _ = @atomicRmw(State, &self.shared_state, .Xchg, .Unlocked, .SeqCst);
181181 continue;
182182 }
183183
184184 if (@atomicLoad(u8, &self.reader_queue_empty_bit, .SeqCst) == 0) {
185 if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.ReadLock, .SeqCst, .SeqCst) != null) {
185 if (@cmpxchgStrong(State, &self.shared_state, .Unlocked, .ReadLock, .SeqCst, .SeqCst) != null) {
186186 // We did not obtain the lock. Great, the queues are someone else's problem.
187187 return;
188188 }
......@@ -196,7 +196,7 @@ pub const RwLock = struct {
196196 }
197197 // Release the lock again.
198198 _ = @atomicRmw(u8, &self.reader_queue_empty_bit, .Xchg, 1, .SeqCst);
199 if (@cmpxchgStrong(u8, &self.shared_state, State.ReadLock, State.Unlocked, .SeqCst, .SeqCst) != null) {
199 if (@cmpxchgStrong(State, &self.shared_state, .ReadLock, .Unlocked, .SeqCst, .SeqCst) != null) {
200200 // Didn't unlock. Someone else's problem.
201201 return;
202202 }
lib/std/lazy_init.zig+13-11
......@@ -1,24 +1,26 @@
11const std = @import("std.zig");
2const builtin = @import("builtin");
32const assert = std.debug.assert;
43const testing = std.testing;
5const AtomicRmwOp = builtin.AtomicRmwOp;
6const AtomicOrder = builtin.AtomicOrder;
74
85/// Thread-safe initialization of global data.
96/// TODO use a mutex instead of a spinlock
107pub fn lazyInit(comptime T: type) LazyInit(T) {
118 return LazyInit(T){
129 .data = undefined,
13 .state = 0,
1410 };
1511}
1612
1713fn LazyInit(comptime T: type) type {
1814 return struct {
19 state: u8, // TODO make this an enum
15 state: State = .NotResolved,
2016 data: Data,
2117
18 const State = enum(u8) {
19 NotResolved,
20 Resolving,
21 Resolved,
22 };
23
2224 const Self = @This();
2325
2426 // TODO this isn't working for void, investigate and then remove this special case
......@@ -30,14 +32,14 @@ fn LazyInit(comptime T: type) type {
3032 /// perform the initialization and then call resolve().
3133 pub fn get(self: *Self) ?Ptr {
3234 while (true) {
33 var state = @cmpxchgWeak(u8, &self.state, 0, 1, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return null;
35 var state = @cmpxchgWeak(State, &self.state, .NotResolved, .Resolving, .SeqCst, .SeqCst) orelse return null;
3436 switch (state) {
35 0 => continue,
36 1 => {
37 .NotResolved => continue,
38 .Resolving => {
3739 // TODO mutex instead of a spinlock
3840 continue;
3941 },
40 2 => {
42 .Resolved => {
4143 if (@sizeOf(T) == 0) {
4244 return @as(T, undefined);
4345 } else {
......@@ -50,8 +52,8 @@ fn LazyInit(comptime T: type) type {
5052 }
5153
5254 pub fn resolve(self: *Self) void {
53 const prev = @atomicRmw(u8, &self.state, AtomicRmwOp.Xchg, 2, AtomicOrder.SeqCst);
54 assert(prev == 1); // resolve() called twice
55 const prev = @atomicRmw(State, &self.state, .Xchg, .Resolved, .SeqCst);
56 assert(prev != .Resolved); // resolve() called twice
5557 }
5658 };
5759}
lib/std/mutex.zig+22-20
......@@ -39,12 +39,14 @@ pub const Mutex = if (builtin.single_threaded)
3939 }
4040else
4141 struct {
42 state: u32, // TODO: make this an enum
42 state: State, // TODO: make this an enum
4343 parker: ThreadParker,
4444
45 const Unlocked = 0;
46 const Sleeping = 1;
47 const Locked = 2;
45 const State = enum(u32) {
46 Unlocked,
47 Sleeping,
48 Locked,
49 };
4850
4951 /// number of iterations to spin yielding the cpu
5052 const SPIN_CPU = 4;
......@@ -57,7 +59,7 @@ else
5759
5860 pub fn init() Mutex {
5961 return Mutex{
60 .state = Unlocked,
62 .state = .Unlocked,
6163 .parker = ThreadParker.init(),
6264 };
6365 }
......@@ -70,10 +72,10 @@ else
7072 mutex: *Mutex,
7173
7274 pub fn release(self: Held) void {
73 switch (@atomicRmw(u32, &self.mutex.state, .Xchg, Unlocked, .Release)) {
74 Locked => {},
75 Sleeping => self.mutex.parker.unpark(&self.mutex.state),
76 Unlocked => unreachable, // unlocking an unlocked mutex
75 switch (@atomicRmw(State, &self.mutex.state, .Xchg, .Unlocked, .Release)) {
76 .Locked => {},
77 .Sleeping => self.mutex.parker.unpark(@ptrCast(*const u32, &self.mutex.state)),
78 .Unlocked => unreachable, // unlocking an unlocked mutex
7779 else => unreachable, // should never be anything else
7880 }
7981 }
......@@ -83,34 +85,34 @@ else
8385 // Try and speculatively grab the lock.
8486 // If it fails, the state is either Locked or Sleeping
8587 // depending on if theres a thread stuck sleeping below.
86 var state = @atomicRmw(u32, &self.state, .Xchg, Locked, .Acquire);
87 if (state == Unlocked)
88 var state = @atomicRmw(State, &self.state, .Xchg, .Locked, .Acquire);
89 if (state == .Unlocked)
8890 return Held{ .mutex = self };
8991
9092 while (true) {
9193 // try and acquire the lock using cpu spinning on failure
9294 var spin: usize = 0;
9395 while (spin < SPIN_CPU) : (spin += 1) {
94 var value = @atomicLoad(u32, &self.state, .Monotonic);
95 while (value == Unlocked)
96 value = @cmpxchgWeak(u32, &self.state, Unlocked, state, .Acquire, .Monotonic) orelse return Held{ .mutex = self };
96 var value = @atomicLoad(State, &self.state, .Monotonic);
97 while (value == .Unlocked)
98 value = @cmpxchgWeak(State, &self.state, .Unlocked, state, .Acquire, .Monotonic) orelse return Held{ .mutex = self };
9799 SpinLock.yield(SPIN_CPU_COUNT);
98100 }
99101
100102 // try and acquire the lock using thread rescheduling on failure
101103 spin = 0;
102104 while (spin < SPIN_THREAD) : (spin += 1) {
103 var value = @atomicLoad(u32, &self.state, .Monotonic);
104 while (value == Unlocked)
105 value = @cmpxchgWeak(u32, &self.state, Unlocked, state, .Acquire, .Monotonic) orelse return Held{ .mutex = self };
105 var value = @atomicLoad(State, &self.state, .Monotonic);
106 while (value == .Unlocked)
107 value = @cmpxchgWeak(State, &self.state, .Unlocked, state, .Acquire, .Monotonic) orelse return Held{ .mutex = self };
106108 std.os.sched_yield() catch std.time.sleep(1);
107109 }
108110
109111 // failed to acquire the lock, go to sleep until woken up by `Held.release()`
110 if (@atomicRmw(u32, &self.state, .Xchg, Sleeping, .Acquire) == Unlocked)
112 if (@atomicRmw(State, &self.state, .Xchg, .Sleeping, .Acquire) == .Unlocked)
111113 return Held{ .mutex = self };
112 state = Sleeping;
113 self.parker.park(&self.state, Sleeping);
114 state = .Sleeping;
115 self.parker.park(@ptrCast(*const u32, &self.state), @enumToInt(State.Sleeping));
114116 }
115117 }
116118 };
src/ir.cpp+21-1
......@@ -25621,9 +25621,29 @@ static ZigType *ir_resolve_atomic_operand_type(IrAnalyze *ira, IrInstruction *op
2562125621 buf_sprintf("%" PRIu32 "-bit integer type is not a power of 2", operand_type->data.integral.bit_count));
2562225622 return ira->codegen->builtin_types.entry_invalid;
2562325623 }
25624 } else if (operand_type->id == ZigTypeIdEnum) {
25625 ZigType *int_type = operand_type->data.enumeration.tag_int_type;
25626 if (int_type->data.integral.bit_count < 8) {
25627 ir_add_error(ira, op,
25628 buf_sprintf("expected enum tag type 8 bits or larger, found %" PRIu32 "-bit tag type",
25629 int_type->data.integral.bit_count));
25630 return ira->codegen->builtin_types.entry_invalid;
25631 }
25632 uint32_t max_atomic_bits = target_arch_largest_atomic_bits(ira->codegen->zig_target->arch);
25633 if (int_type->data.integral.bit_count > max_atomic_bits) {
25634 ir_add_error(ira, op,
25635 buf_sprintf("expected %" PRIu32 "-bit enum tag type or smaller, found %" PRIu32 "-bit tag type",
25636 max_atomic_bits, int_type->data.integral.bit_count));
25637 return ira->codegen->builtin_types.entry_invalid;
25638 }
25639 if (!is_power_of_2(int_type->data.integral.bit_count)) {
25640 ir_add_error(ira, op,
25641 buf_sprintf("%" PRIu32 "-bit enum tag type is not a power of 2", int_type->data.integral.bit_count));
25642 return ira->codegen->builtin_types.entry_invalid;
25643 }
2562425644 } else if (get_codegen_ptr_type(operand_type) == nullptr) {
2562525645 ir_add_error(ira, op,
25626 buf_sprintf("expected integer or pointer type, found '%s'", buf_ptr(&operand_type->name)));
25646 buf_sprintf("expected integer, enum or pointer type, found '%s'", buf_ptr(&operand_type->name)));
2562725647 return ira->codegen->builtin_types.entry_invalid;
2562825648 }
2562925649
test/stage1/behavior/atomics.zig+16
......@@ -107,3 +107,19 @@ test "cmpxchg on a global variable" {
107107 _ = @cmpxchgWeak(u32, &a_global_variable, 1234, 42, .Acquire, .Monotonic);
108108 expectEqual(@as(u32, 42), a_global_variable);
109109}
110
111test "atomic load and rmw with enum" {
112 const Value = enum(u8) {
113 a,
114 b,
115 c,
116 };
117 var x = Value.a;
118
119 expect(@atomicLoad(Value, &x, .SeqCst) != .b);
120
121 _ = @atomicRmw(Value, &x, .Xchg, .c, .SeqCst);
122 expect(@atomicLoad(Value, &x, .SeqCst) == .c);
123 expect(@atomicLoad(Value, &x, .SeqCst) != .a);
124 expect(@atomicLoad(Value, &x, .SeqCst) != .b);
125}