| author | |
| committer | |
| log | e32b4829f4f91ee412ead7a3851f6e271d9fb07e |
| tree | 2f04d9d06b0ee3973c1f83afdf6018fcac4d99b7 |
| parent | 710ccacfa3307fc642f4bac71f894e3d8a18764a |
| parent | 5194fc57d1c206d71654b4f3e43bfcb300bf43c5 |
| signature |
Support atomic operations with enums6 files changed, 99 insertions(+), 58 deletions(-)
lib/std/event/future.zig+11-10| ... | @@ -12,12 +12,13 @@ pub fn Future(comptime T: type) type { | ... | @@ -12,12 +12,13 @@ pub fn Future(comptime T: type) type { |
| 12 | return struct { | 12 | return struct { |
| 13 | lock: Lock, | 13 | lock: Lock, |
| 14 | data: T, | 14 | data: T, |
| 15 | available: Available, | ||
| 15 | 16 | ||
| 16 | /// TODO make this an enum | 17 | const Available = enum(u8) { |
| 17 | /// 0 - not started | 18 | NotStarted, |
| 18 | /// 1 - started | 19 | Started, |
| 19 | /// 2 - finished | 20 | Finished, |
| 20 | available: u8, | 21 | }; |
| 21 | 22 | ||
| 22 | const Self = @This(); | 23 | const Self = @This(); |
| 23 | const Queue = std.atomic.Queue(anyframe); | 24 | const Queue = std.atomic.Queue(anyframe); |
| ... | @@ -34,7 +35,7 @@ pub fn Future(comptime T: type) type { | ... | @@ -34,7 +35,7 @@ pub fn Future(comptime T: type) type { |
| 34 | /// available. | 35 | /// available. |
| 35 | /// Thread-safe. | 36 | /// Thread-safe. |
| 36 | pub async fn get(self: *Self) *T { | 37 | pub async fn get(self: *Self) *T { |
| 37 | if (@atomicLoad(u8, &self.available, .SeqCst) == 2) { | 38 | if (@atomicLoad(Available, &self.available, .SeqCst) == .Finished) { |
| 38 | return &self.data; | 39 | return &self.data; |
| 39 | } | 40 | } |
| 40 | const held = self.lock.acquire(); | 41 | const held = self.lock.acquire(); |
| ... | @@ -46,7 +47,7 @@ pub fn Future(comptime T: type) type { | ... | @@ -46,7 +47,7 @@ pub fn Future(comptime T: type) type { |
| 46 | /// Gets the data without waiting for it. If it's available, a pointer is | 47 | /// Gets the data without waiting for it. If it's available, a pointer is |
| 47 | /// returned. Otherwise, null is returned. | 48 | /// returned. Otherwise, null is returned. |
| 48 | pub fn getOrNull(self: *Self) ?*T { | 49 | pub fn getOrNull(self: *Self) ?*T { |
| 49 | if (@atomicLoad(u8, &self.available, .SeqCst) == 2) { | 50 | if (@atomicLoad(Available, &self.available, .SeqCst) == .Finished) { |
| 50 | return &self.data; | 51 | return &self.data; |
| 51 | } else { | 52 | } else { |
| 52 | return null; | 53 | return null; |
| ... | @@ -59,7 +60,7 @@ pub fn Future(comptime T: type) type { | ... | @@ -59,7 +60,7 @@ pub fn Future(comptime T: type) type { |
| 59 | /// It's not required to call start() before resolve() but it can be useful since | 60 | /// It's not required to call start() before resolve() but it can be useful since |
| 60 | /// this method is thread-safe. | 61 | /// this method is thread-safe. |
| 61 | pub async fn start(self: *Self) ?*T { | 62 | 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; |
| 63 | switch (state) { | 64 | switch (state) { |
| 64 | 1 => { | 65 | 1 => { |
| 65 | const held = self.lock.acquire(); | 66 | const held = self.lock.acquire(); |
| ... | @@ -74,8 +75,8 @@ pub fn Future(comptime T: type) type { | ... | @@ -74,8 +75,8 @@ pub fn Future(comptime T: type) type { |
| 74 | /// Make the data become available. May be called only once. | 75 | /// Make the data become available. May be called only once. |
| 75 | /// Before calling this, modify the `data` property. | 76 | /// Before calling this, modify the `data` property. |
| 76 | pub fn resolve(self: *Self) void { | 77 | pub fn resolve(self: *Self) void { |
| 77 | const prev = @atomicRmw(u8, &self.available, .Xchg, 2, .SeqCst); | 78 | const prev = @atomicRmw(Available, &self.available, .Xchg, .Finished, .SeqCst); |
| 78 | assert(prev == 0 or prev == 1); // resolve() called twice | 79 | assert(prev != .Finished); // resolve() called twice |
| 79 | Lock.Held.release(Lock.Held{ .lock = &self.lock }); | 80 | Lock.Held.release(Lock.Held{ .lock = &self.lock }); |
| 80 | } | 81 | } |
| 81 | }; | 82 | }; |
lib/std/event/rwlock.zig+16-16| ... | @@ -13,17 +13,17 @@ const Loop = std.event.Loop; | ... | @@ -13,17 +13,17 @@ const Loop = std.event.Loop; |
| 13 | /// When a write lock is held, it will not be released until the writer queue is empty. | 13 | /// When a write lock is held, it will not be released until the writer queue is empty. |
| 14 | /// TODO: make this API also work in blocking I/O mode | 14 | /// TODO: make this API also work in blocking I/O mode |
| 15 | pub const RwLock = struct { | 15 | pub const RwLock = struct { |
| 16 | shared_state: u8, // TODO make this an enum | 16 | shared_state: State, |
| 17 | writer_queue: Queue, | 17 | writer_queue: Queue, |
| 18 | reader_queue: Queue, | 18 | reader_queue: Queue, |
| 19 | writer_queue_empty_bit: u8, // TODO make this a bool | 19 | writer_queue_empty_bit: u8, // TODO make this a bool |
| 20 | reader_queue_empty_bit: u8, // TODO make this a bool | 20 | reader_queue_empty_bit: u8, // TODO make this a bool |
| 21 | reader_lock_count: usize, | 21 | reader_lock_count: usize, |
| 22 | 22 | ||
| 23 | const State = struct { | 23 | const State = enum(u8) { |
| 24 | const Unlocked = 0; | 24 | Unlocked, |
| 25 | const WriteLock = 1; | 25 | WriteLock, |
| 26 | const ReadLock = 2; | 26 | ReadLock, |
| 27 | }; | 27 | }; |
| 28 | 28 | ||
| 29 | const Queue = std.atomic.Queue(anyframe); | 29 | const Queue = std.atomic.Queue(anyframe); |
| ... | @@ -41,7 +41,7 @@ pub const RwLock = struct { | ... | @@ -41,7 +41,7 @@ pub const RwLock = struct { |
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | _ = @atomicRmw(u8, &self.lock.reader_queue_empty_bit, .Xchg, 1, .SeqCst); | 43 | _ = @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) { |
| 45 | // Didn't unlock. Someone else's problem. | 45 | // Didn't unlock. Someone else's problem. |
| 46 | return; | 46 | return; |
| 47 | } | 47 | } |
| ... | @@ -64,7 +64,7 @@ pub const RwLock = struct { | ... | @@ -64,7 +64,7 @@ pub const RwLock = struct { |
| 64 | // We need to release the write lock. Check if any readers are waiting to grab the lock. | 64 | // We need to release the write lock. Check if any readers are waiting to grab the lock. |
| 65 | if (@atomicLoad(u8, &self.lock.reader_queue_empty_bit, .SeqCst) == 0) { | 65 | if (@atomicLoad(u8, &self.lock.reader_queue_empty_bit, .SeqCst) == 0) { |
| 66 | // Switch to a read lock. | 66 | // 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); |
| 68 | while (self.lock.reader_queue.get()) |node| { | 68 | while (self.lock.reader_queue.get()) |node| { |
| 69 | global_event_loop.onNextTick(node); | 69 | global_event_loop.onNextTick(node); |
| 70 | } | 70 | } |
| ... | @@ -72,7 +72,7 @@ pub const RwLock = struct { | ... | @@ -72,7 +72,7 @@ pub const RwLock = struct { |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | _ = @atomicRmw(u8, &self.lock.writer_queue_empty_bit, .Xchg, 1, .SeqCst); | 74 | _ = @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); |
| 76 | 76 | ||
| 77 | self.lock.commonPostUnlock(); | 77 | self.lock.commonPostUnlock(); |
| 78 | } | 78 | } |
| ... | @@ -80,7 +80,7 @@ pub const RwLock = struct { | ... | @@ -80,7 +80,7 @@ pub const RwLock = struct { |
| 80 | 80 | ||
| 81 | pub fn init() RwLock { | 81 | pub fn init() RwLock { |
| 82 | return RwLock{ | 82 | return RwLock{ |
| 83 | .shared_state = State.Unlocked, | 83 | .shared_state = .Unlocked, |
| 84 | .writer_queue = Queue.init(), | 84 | .writer_queue = Queue.init(), |
| 85 | .writer_queue_empty_bit = 1, | 85 | .writer_queue_empty_bit = 1, |
| 86 | .reader_queue = Queue.init(), | 86 | .reader_queue = Queue.init(), |
| ... | @@ -92,7 +92,7 @@ pub const RwLock = struct { | ... | @@ -92,7 +92,7 @@ pub const RwLock = struct { |
| 92 | /// Must be called when not locked. Not thread safe. | 92 | /// Must be called when not locked. Not thread safe. |
| 93 | /// All calls to acquire() and release() must complete before calling deinit(). | 93 | /// All calls to acquire() and release() must complete before calling deinit(). |
| 94 | pub fn deinit(self: *RwLock) void { | 94 | pub fn deinit(self: *RwLock) void { |
| 95 | assert(self.shared_state == State.Unlocked); | 95 | assert(self.shared_state == .Unlocked); |
| 96 | while (self.writer_queue.get()) |node| resume node.data; | 96 | while (self.writer_queue.get()) |node| resume node.data; |
| 97 | while (self.reader_queue.get()) |node| resume node.data; | 97 | while (self.reader_queue.get()) |node| resume node.data; |
| 98 | } | 98 | } |
| ... | @@ -116,7 +116,7 @@ pub const RwLock = struct { | ... | @@ -116,7 +116,7 @@ pub const RwLock = struct { |
| 116 | _ = @atomicRmw(u8, &self.reader_queue_empty_bit, .Xchg, 0, .SeqCst); | 116 | _ = @atomicRmw(u8, &self.reader_queue_empty_bit, .Xchg, 0, .SeqCst); |
| 117 | 117 | ||
| 118 | // Here we don't care if we are the one to do the locking or if it was already locked for reading. | 118 | // 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; |
| 120 | if (have_read_lock) { | 120 | if (have_read_lock) { |
| 121 | // Give out all the read locks. | 121 | // Give out all the read locks. |
| 122 | if (self.reader_queue.get()) |first_node| { | 122 | if (self.reader_queue.get()) |first_node| { |
| ... | @@ -147,7 +147,7 @@ pub const RwLock = struct { | ... | @@ -147,7 +147,7 @@ pub const RwLock = struct { |
| 147 | _ = @atomicRmw(u8, &self.writer_queue_empty_bit, .Xchg, 0, .SeqCst); | 147 | _ = @atomicRmw(u8, &self.writer_queue_empty_bit, .Xchg, 0, .SeqCst); |
| 148 | 148 | ||
| 149 | // Here we must be the one to acquire the write lock. It cannot already be locked. | 149 | // 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) { |
| 151 | // We now have a write lock. | 151 | // We now have a write lock. |
| 152 | if (self.writer_queue.get()) |node| { | 152 | if (self.writer_queue.get()) |node| { |
| 153 | // Whether this node is us or someone else, we tail resume it. | 153 | // Whether this node is us or someone else, we tail resume it. |
| ... | @@ -166,7 +166,7 @@ pub const RwLock = struct { | ... | @@ -166,7 +166,7 @@ pub const RwLock = struct { |
| 166 | // But if there's a writer_queue item or a reader_queue item, | 166 | // But if there's a writer_queue item or a reader_queue item, |
| 167 | // we are the actor which must loop and attempt to grab the lock again. | 167 | // we are the actor which must loop and attempt to grab the lock again. |
| 168 | if (@atomicLoad(u8, &self.writer_queue_empty_bit, .SeqCst) == 0) { | 168 | 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) { |
| 170 | // We did not obtain the lock. Great, the queues are someone else's problem. | 170 | // We did not obtain the lock. Great, the queues are someone else's problem. |
| 171 | return; | 171 | return; |
| 172 | } | 172 | } |
| ... | @@ -177,12 +177,12 @@ pub const RwLock = struct { | ... | @@ -177,12 +177,12 @@ pub const RwLock = struct { |
| 177 | } | 177 | } |
| 178 | // Release the lock again. | 178 | // Release the lock again. |
| 179 | _ = @atomicRmw(u8, &self.writer_queue_empty_bit, .Xchg, 1, .SeqCst); | 179 | _ = @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); |
| 181 | continue; | 181 | continue; |
| 182 | } | 182 | } |
| 183 | 183 | ||
| 184 | if (@atomicLoad(u8, &self.reader_queue_empty_bit, .SeqCst) == 0) { | 184 | 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) { |
| 186 | // We did not obtain the lock. Great, the queues are someone else's problem. | 186 | // We did not obtain the lock. Great, the queues are someone else's problem. |
| 187 | return; | 187 | return; |
| 188 | } | 188 | } |
| ... | @@ -196,7 +196,7 @@ pub const RwLock = struct { | ... | @@ -196,7 +196,7 @@ pub const RwLock = struct { |
| 196 | } | 196 | } |
| 197 | // Release the lock again. | 197 | // Release the lock again. |
| 198 | _ = @atomicRmw(u8, &self.reader_queue_empty_bit, .Xchg, 1, .SeqCst); | 198 | _ = @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) { |
| 200 | // Didn't unlock. Someone else's problem. | 200 | // Didn't unlock. Someone else's problem. |
| 201 | return; | 201 | return; |
| 202 | } | 202 | } |
lib/std/lazy_init.zig+13-11| ... | @@ -1,24 +1,26 @@ | ... | @@ -1,24 +1,26 @@ |
| 1 | const std = @import("std.zig"); | 1 | const std = @import("std.zig"); |
| 2 | const builtin = @import("builtin"); | ||
| 3 | const assert = std.debug.assert; | 2 | const assert = std.debug.assert; |
| 4 | const testing = std.testing; | 3 | const testing = std.testing; |
| 5 | const AtomicRmwOp = builtin.AtomicRmwOp; | ||
| 6 | const AtomicOrder = builtin.AtomicOrder; | ||
| 7 | 4 | ||
| 8 | /// Thread-safe initialization of global data. | 5 | /// Thread-safe initialization of global data. |
| 9 | /// TODO use a mutex instead of a spinlock | 6 | /// TODO use a mutex instead of a spinlock |
| 10 | pub fn lazyInit(comptime T: type) LazyInit(T) { | 7 | pub fn lazyInit(comptime T: type) LazyInit(T) { |
| 11 | return LazyInit(T){ | 8 | return LazyInit(T){ |
| 12 | .data = undefined, | 9 | .data = undefined, |
| 13 | .state = 0, | ||
| 14 | }; | 10 | }; |
| 15 | } | 11 | } |
| 16 | 12 | ||
| 17 | fn LazyInit(comptime T: type) type { | 13 | fn LazyInit(comptime T: type) type { |
| 18 | return struct { | 14 | return struct { |
| 19 | state: u8, // TODO make this an enum | 15 | state: State = .NotResolved, |
| 20 | data: Data, | 16 | data: Data, |
| 21 | 17 | ||
| 18 | const State = enum(u8) { | ||
| 19 | NotResolved, | ||
| 20 | Resolving, | ||
| 21 | Resolved, | ||
| 22 | }; | ||
| 23 | |||
| 22 | const Self = @This(); | 24 | const Self = @This(); |
| 23 | 25 | ||
| 24 | // TODO this isn't working for void, investigate and then remove this special case | 26 | // TODO this isn't working for void, investigate and then remove this special case |
| ... | @@ -30,14 +32,14 @@ fn LazyInit(comptime T: type) type { | ... | @@ -30,14 +32,14 @@ fn LazyInit(comptime T: type) type { |
| 30 | /// perform the initialization and then call resolve(). | 32 | /// perform the initialization and then call resolve(). |
| 31 | pub fn get(self: *Self) ?Ptr { | 33 | pub fn get(self: *Self) ?Ptr { |
| 32 | while (true) { | 34 | 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; |
| 34 | switch (state) { | 36 | switch (state) { |
| 35 | 0 => continue, | 37 | .NotResolved => continue, |
| 36 | 1 => { | 38 | .Resolving => { |
| 37 | // TODO mutex instead of a spinlock | 39 | // TODO mutex instead of a spinlock |
| 38 | continue; | 40 | continue; |
| 39 | }, | 41 | }, |
| 40 | 2 => { | 42 | .Resolved => { |
| 41 | if (@sizeOf(T) == 0) { | 43 | if (@sizeOf(T) == 0) { |
| 42 | return @as(T, undefined); | 44 | return @as(T, undefined); |
| 43 | } else { | 45 | } else { |
| ... | @@ -50,8 +52,8 @@ fn LazyInit(comptime T: type) type { | ... | @@ -50,8 +52,8 @@ fn LazyInit(comptime T: type) type { |
| 50 | } | 52 | } |
| 51 | 53 | ||
| 52 | pub fn resolve(self: *Self) void { | 54 | pub fn resolve(self: *Self) void { |
| 53 | const prev = @atomicRmw(u8, &self.state, AtomicRmwOp.Xchg, 2, AtomicOrder.SeqCst); | 55 | const prev = @atomicRmw(State, &self.state, .Xchg, .Resolved, .SeqCst); |
| 54 | assert(prev == 1); // resolve() called twice | 56 | assert(prev != .Resolved); // resolve() called twice |
| 55 | } | 57 | } |
| 56 | }; | 58 | }; |
| 57 | } | 59 | } |
lib/std/mutex.zig+22-20| ... | @@ -39,12 +39,14 @@ pub const Mutex = if (builtin.single_threaded) | ... | @@ -39,12 +39,14 @@ pub const Mutex = if (builtin.single_threaded) |
| 39 | } | 39 | } |
| 40 | else | 40 | else |
| 41 | struct { | 41 | struct { |
| 42 | state: u32, // TODO: make this an enum | 42 | state: State, // TODO: make this an enum |
| 43 | parker: ThreadParker, | 43 | parker: ThreadParker, |
| 44 | 44 | ||
| 45 | const Unlocked = 0; | 45 | const State = enum(u32) { |
| 46 | const Sleeping = 1; | 46 | Unlocked, |
| 47 | const Locked = 2; | 47 | Sleeping, |
| 48 | Locked, | ||
| 49 | }; | ||
| 48 | 50 | ||
| 49 | /// number of iterations to spin yielding the cpu | 51 | /// number of iterations to spin yielding the cpu |
| 50 | const SPIN_CPU = 4; | 52 | const SPIN_CPU = 4; |
| ... | @@ -57,7 +59,7 @@ else | ... | @@ -57,7 +59,7 @@ else |
| 57 | 59 | ||
| 58 | pub fn init() Mutex { | 60 | pub fn init() Mutex { |
| 59 | return Mutex{ | 61 | return Mutex{ |
| 60 | .state = Unlocked, | 62 | .state = .Unlocked, |
| 61 | .parker = ThreadParker.init(), | 63 | .parker = ThreadParker.init(), |
| 62 | }; | 64 | }; |
| 63 | } | 65 | } |
| ... | @@ -70,10 +72,10 @@ else | ... | @@ -70,10 +72,10 @@ else |
| 70 | mutex: *Mutex, | 72 | mutex: *Mutex, |
| 71 | 73 | ||
| 72 | pub fn release(self: Held) void { | 74 | pub fn release(self: Held) void { |
| 73 | switch (@atomicRmw(u32, &self.mutex.state, .Xchg, Unlocked, .Release)) { | 75 | switch (@atomicRmw(State, &self.mutex.state, .Xchg, .Unlocked, .Release)) { |
| 74 | Locked => {}, | 76 | .Locked => {}, |
| 75 | Sleeping => self.mutex.parker.unpark(&self.mutex.state), | 77 | .Sleeping => self.mutex.parker.unpark(@ptrCast(*const u32, &self.mutex.state)), |
| 76 | Unlocked => unreachable, // unlocking an unlocked mutex | 78 | .Unlocked => unreachable, // unlocking an unlocked mutex |
| 77 | else => unreachable, // should never be anything else | 79 | else => unreachable, // should never be anything else |
| 78 | } | 80 | } |
| 79 | } | 81 | } |
| ... | @@ -83,34 +85,34 @@ else | ... | @@ -83,34 +85,34 @@ else |
| 83 | // Try and speculatively grab the lock. | 85 | // Try and speculatively grab the lock. |
| 84 | // If it fails, the state is either Locked or Sleeping | 86 | // If it fails, the state is either Locked or Sleeping |
| 85 | // depending on if theres a thread stuck sleeping below. | 87 | // depending on if theres a thread stuck sleeping below. |
| 86 | var state = @atomicRmw(u32, &self.state, .Xchg, Locked, .Acquire); | 88 | var state = @atomicRmw(State, &self.state, .Xchg, .Locked, .Acquire); |
| 87 | if (state == Unlocked) | 89 | if (state == .Unlocked) |
| 88 | return Held{ .mutex = self }; | 90 | return Held{ .mutex = self }; |
| 89 | 91 | ||
| 90 | while (true) { | 92 | while (true) { |
| 91 | // try and acquire the lock using cpu spinning on failure | 93 | // try and acquire the lock using cpu spinning on failure |
| 92 | var spin: usize = 0; | 94 | var spin: usize = 0; |
| 93 | while (spin < SPIN_CPU) : (spin += 1) { | 95 | while (spin < SPIN_CPU) : (spin += 1) { |
| 94 | var value = @atomicLoad(u32, &self.state, .Monotonic); | 96 | var value = @atomicLoad(State, &self.state, .Monotonic); |
| 95 | while (value == Unlocked) | 97 | while (value == .Unlocked) |
| 96 | value = @cmpxchgWeak(u32, &self.state, Unlocked, state, .Acquire, .Monotonic) orelse return Held{ .mutex = self }; | 98 | value = @cmpxchgWeak(State, &self.state, .Unlocked, state, .Acquire, .Monotonic) orelse return Held{ .mutex = self }; |
| 97 | SpinLock.yield(SPIN_CPU_COUNT); | 99 | SpinLock.yield(SPIN_CPU_COUNT); |
| 98 | } | 100 | } |
| 99 | 101 | ||
| 100 | // try and acquire the lock using thread rescheduling on failure | 102 | // try and acquire the lock using thread rescheduling on failure |
| 101 | spin = 0; | 103 | spin = 0; |
| 102 | while (spin < SPIN_THREAD) : (spin += 1) { | 104 | while (spin < SPIN_THREAD) : (spin += 1) { |
| 103 | var value = @atomicLoad(u32, &self.state, .Monotonic); | 105 | var value = @atomicLoad(State, &self.state, .Monotonic); |
| 104 | while (value == Unlocked) | 106 | while (value == .Unlocked) |
| 105 | value = @cmpxchgWeak(u32, &self.state, Unlocked, state, .Acquire, .Monotonic) orelse return Held{ .mutex = self }; | 107 | value = @cmpxchgWeak(State, &self.state, .Unlocked, state, .Acquire, .Monotonic) orelse return Held{ .mutex = self }; |
| 106 | std.os.sched_yield() catch std.time.sleep(1); | 108 | std.os.sched_yield() catch std.time.sleep(1); |
| 107 | } | 109 | } |
| 108 | 110 | ||
| 109 | // failed to acquire the lock, go to sleep until woken up by `Held.release()` | 111 | // 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) |
| 111 | return Held{ .mutex = self }; | 113 | return Held{ .mutex = self }; |
| 112 | state = Sleeping; | 114 | state = .Sleeping; |
| 113 | self.parker.park(&self.state, Sleeping); | 115 | self.parker.park(@ptrCast(*const u32, &self.state), @enumToInt(State.Sleeping)); |
| 114 | } | 116 | } |
| 115 | } | 117 | } |
| 116 | }; | 118 | }; |
src/ir.cpp+21-1| ... | @@ -25621,9 +25621,29 @@ static ZigType *ir_resolve_atomic_operand_type(IrAnalyze *ira, IrInstruction *op | ... | @@ -25621,9 +25621,29 @@ static ZigType *ir_resolve_atomic_operand_type(IrAnalyze *ira, IrInstruction *op |
| 25621 | buf_sprintf("%" PRIu32 "-bit integer type is not a power of 2", operand_type->data.integral.bit_count)); | 25621 | buf_sprintf("%" PRIu32 "-bit integer type is not a power of 2", operand_type->data.integral.bit_count)); |
| 25622 | return ira->codegen->builtin_types.entry_invalid; | 25622 | return ira->codegen->builtin_types.entry_invalid; |
| 25623 | } | 25623 | } |
| 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 | } | ||
| 25624 | } else if (get_codegen_ptr_type(operand_type) == nullptr) { | 25644 | } else if (get_codegen_ptr_type(operand_type) == nullptr) { |
| 25625 | ir_add_error(ira, op, | 25645 | 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))); |
| 25627 | return ira->codegen->builtin_types.entry_invalid; | 25647 | return ira->codegen->builtin_types.entry_invalid; |
| 25628 | } | 25648 | } |
| 25629 | 25649 |
test/stage1/behavior/atomics.zig+16| ... | @@ -107,3 +107,19 @@ test "cmpxchg on a global variable" { | ... | @@ -107,3 +107,19 @@ test "cmpxchg on a global variable" { |
| 107 | _ = @cmpxchgWeak(u32, &a_global_variable, 1234, 42, .Acquire, .Monotonic); | 107 | _ = @cmpxchgWeak(u32, &a_global_variable, 1234, 42, .Acquire, .Monotonic); |
| 108 | expectEqual(@as(u32, 42), a_global_variable); | 108 | expectEqual(@as(u32, 42), a_global_variable); |
| 109 | } | 109 | } |
| 110 | |||
| 111 | test "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 | } |