authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-07 23:26:58-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-07 23:26:58-07:00
log0347df82e8c821906ef0d07ec65fe4b3884c0212
tree20076f52a0024e2252eac24683e39d3ee25d4d28
parentcc17f84cccc540143f3fd19fe32218478d4a0c6f

improvements & fixes for general purpose allocator integration

* std.Mutex API is improved to not have init() deinit(). This API is designed to support static initialization and does not require any resource cleanup. This also happens to work around some kind of stage1 behavior that wasn't letting the new allocator mutex code get compiled. * the general purpose allocator now returns a bool from deinit() which tells if there were any leaks. This value is used by the test runner to fail the tests if there are any. * self-hosted compiler is updated to use the general purpose allocator when not linking against libc.

10 files changed, 142 insertions(+), 145 deletions(-)

lib/std/atomic/queue.zig+1-1
...@@ -22,7 +22,7 @@ pub fn Queue(comptime T: type) type {...@@ -22,7 +22,7 @@ pub fn Queue(comptime T: type) type {
22 return Self{22 return Self{
23 .head = null,23 .head = null,
24 .tail = null,24 .tail = null,
25 .mutex = std.Mutex.init(),25 .mutex = std.Mutex{},
26 };26 };
27 }27 }
2828
lib/std/debug.zig+2-2
...@@ -47,7 +47,7 @@ pub const LineInfo = struct {...@@ -47,7 +47,7 @@ pub const LineInfo = struct {
47 }47 }
48};48};
4949
50var stderr_mutex = std.Mutex.init();50var stderr_mutex = std.Mutex{};
5151
52/// Deprecated. Use `std.log` functions for logging or `std.debug.print` for52/// Deprecated. Use `std.log` functions for logging or `std.debug.print` for
53/// "printf debugging".53/// "printf debugging".
...@@ -232,7 +232,7 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn {...@@ -232,7 +232,7 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn {
232var panicking: u8 = 0;232var panicking: u8 = 0;
233233
234// Locked to avoid interleaving panic messages from multiple threads.234// Locked to avoid interleaving panic messages from multiple threads.
235var panic_mutex = std.Mutex.init();235var panic_mutex = std.Mutex{};
236236
237/// Counts how many times the panic handler is invoked by this thread.237/// Counts how many times the panic handler is invoked by this thread.
238/// This is used to catch and handle panics triggered by the panic handler.238/// This is used to catch and handle panics triggered by the panic handler.
lib/std/heap.zig+7-1
...@@ -464,7 +464,13 @@ pub const HeapAllocator = switch (builtin.os.tag) {...@@ -464,7 +464,13 @@ pub const HeapAllocator = switch (builtin.os.tag) {
464 return buf;464 return buf;
465 }465 }
466466
467 fn resize(allocator: *Allocator, buf: []u8, new_size: usize, len_align: u29) error{OutOfMemory}!usize {467 fn resize(
468 allocator: *Allocator,
469 buf: []u8,
470 buf_align: u29,
471 new_size: usize,
472 len_align: u29,
473 ) error{OutOfMemory}!usize {
468 const self = @fieldParentPtr(HeapAllocator, "allocator", allocator);474 const self = @fieldParentPtr(HeapAllocator, "allocator", allocator);
469 if (new_size == 0) {475 if (new_size == 0) {
470 os.windows.HeapFree(self.heap_handle.?, 0, @intToPtr(*c_void, getRecordPtr(buf).*));476 os.windows.HeapFree(self.heap_handle.?, 0, @intToPtr(*c_void, getRecordPtr(buf).*));
lib/std/heap/general_purpose_allocator.zig+26-19
...@@ -140,7 +140,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -140,7 +140,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
140 const total_requested_bytes_init = if (config.enable_memory_limit) @as(usize, 0) else {};140 const total_requested_bytes_init = if (config.enable_memory_limit) @as(usize, 0) else {};
141 const requested_memory_limit_init = if (config.enable_memory_limit) @as(usize, math.maxInt(usize)) else {};141 const requested_memory_limit_init = if (config.enable_memory_limit) @as(usize, math.maxInt(usize)) else {};
142142
143 const mutex_init = if (config.thread_safe) std.Mutex.init() else std.Mutex.Dummy.init();143 const mutex_init = if (config.thread_safe) std.Mutex{} else std.mutex.Dummy{};
144144
145 const stack_n = config.stack_trace_frames;145 const stack_n = config.stack_trace_frames;
146 const one_trace_size = @sizeOf(usize) * stack_n;146 const one_trace_size = @sizeOf(usize) * stack_n;
...@@ -250,7 +250,8 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -250,7 +250,8 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
250 bucket: *BucketHeader,250 bucket: *BucketHeader,
251 size_class: usize,251 size_class: usize,
252 used_bits_count: usize,252 used_bits_count: usize,
253 ) void {253 ) bool {
254 var leaks = false;
254 var used_bits_byte: usize = 0;255 var used_bits_byte: usize = 0;
255 while (used_bits_byte < used_bits_count) : (used_bits_byte += 1) {256 while (used_bits_byte < used_bits_count) : (used_bits_byte += 1) {
256 const used_byte = bucket.usedBits(used_bits_byte).*;257 const used_byte = bucket.usedBits(used_bits_byte).*;
...@@ -268,22 +269,26 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -268,22 +269,26 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
268 .alloc,269 .alloc,
269 );270 );
270 std.debug.dumpStackTrace(stack_trace);271 std.debug.dumpStackTrace(stack_trace);
272 leaks = true;
271 }273 }
272 if (bit_index == math.maxInt(u3))274 if (bit_index == math.maxInt(u3))
273 break;275 break;
274 }276 }
275 }277 }
276 }278 }
279 return leaks;
277 }280 }
278281
279 pub fn deinit(self: *Self) void {282 /// Returns whether there were leaks.
283 pub fn deinit(self: *Self) bool {
284 var leaks = false;
280 for (self.buckets) |optional_bucket, bucket_i| {285 for (self.buckets) |optional_bucket, bucket_i| {
281 const first_bucket = optional_bucket orelse continue;286 const first_bucket = optional_bucket orelse continue;
282 const size_class = @as(usize, 1) << @intCast(u6, bucket_i);287 const size_class = @as(usize, 1) << @intCast(u6, bucket_i);
283 const used_bits_count = usedBitsCount(size_class);288 const used_bits_count = usedBitsCount(size_class);
284 var bucket = first_bucket;289 var bucket = first_bucket;
285 while (true) {290 while (true) {
286 detectLeaksInBucket(bucket, size_class, used_bits_count);291 leaks = detectLeaksInBucket(bucket, size_class, used_bits_count) or leaks;
287 bucket = bucket.next;292 bucket = bucket.next;
288 if (bucket == first_bucket)293 if (bucket == first_bucket)
289 break;294 break;
...@@ -292,9 +297,11 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -292,9 +297,11 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
292 for (self.large_allocations.items()) |*large_alloc| {297 for (self.large_allocations.items()) |*large_alloc| {
293 std.debug.print("\nMemory leak detected:\n", .{});298 std.debug.print("\nMemory leak detected:\n", .{});
294 large_alloc.value.dumpStackTrace();299 large_alloc.value.dumpStackTrace();
300 leaks = true;
295 }301 }
296 self.large_allocations.deinit(self.backing_allocator);302 self.large_allocations.deinit(self.backing_allocator);
297 self.* = undefined;303 self.* = undefined;
304 return leaks;
298 }305 }
299306
300 fn collectStackTrace(first_trace_addr: usize, addresses: *[stack_n]usize) void {307 fn collectStackTrace(first_trace_addr: usize, addresses: *[stack_n]usize) void {
...@@ -600,7 +607,7 @@ const test_config = Config{};...@@ -600,7 +607,7 @@ const test_config = Config{};
600607
601test "small allocations - free in same order" {608test "small allocations - free in same order" {
602 var gpda = GeneralPurposeAllocator(test_config){};609 var gpda = GeneralPurposeAllocator(test_config){};
603 defer gpda.deinit();610 defer std.testing.expect(!gpda.deinit());
604 const allocator = &gpda.allocator;611 const allocator = &gpda.allocator;
605612
606 var list = std.ArrayList(*u64).init(std.testing.allocator);613 var list = std.ArrayList(*u64).init(std.testing.allocator);
...@@ -619,7 +626,7 @@ test "small allocations - free in same order" {...@@ -619,7 +626,7 @@ test "small allocations - free in same order" {
619626
620test "small allocations - free in reverse order" {627test "small allocations - free in reverse order" {
621 var gpda = GeneralPurposeAllocator(test_config){};628 var gpda = GeneralPurposeAllocator(test_config){};
622 defer gpda.deinit();629 defer std.testing.expect(!gpda.deinit());
623 const allocator = &gpda.allocator;630 const allocator = &gpda.allocator;
624631
625 var list = std.ArrayList(*u64).init(std.testing.allocator);632 var list = std.ArrayList(*u64).init(std.testing.allocator);
...@@ -638,7 +645,7 @@ test "small allocations - free in reverse order" {...@@ -638,7 +645,7 @@ test "small allocations - free in reverse order" {
638645
639test "large allocations" {646test "large allocations" {
640 var gpda = GeneralPurposeAllocator(test_config){};647 var gpda = GeneralPurposeAllocator(test_config){};
641 defer gpda.deinit();648 defer std.testing.expect(!gpda.deinit());
642 const allocator = &gpda.allocator;649 const allocator = &gpda.allocator;
643650
644 const ptr1 = try allocator.alloc(u64, 42768);651 const ptr1 = try allocator.alloc(u64, 42768);
...@@ -651,7 +658,7 @@ test "large allocations" {...@@ -651,7 +658,7 @@ test "large allocations" {
651658
652test "realloc" {659test "realloc" {
653 var gpda = GeneralPurposeAllocator(test_config){};660 var gpda = GeneralPurposeAllocator(test_config){};
654 defer gpda.deinit();661 defer std.testing.expect(!gpda.deinit());
655 const allocator = &gpda.allocator;662 const allocator = &gpda.allocator;
656663
657 var slice = try allocator.alignedAlloc(u8, @alignOf(u32), 1);664 var slice = try allocator.alignedAlloc(u8, @alignOf(u32), 1);
...@@ -673,7 +680,7 @@ test "realloc" {...@@ -673,7 +680,7 @@ test "realloc" {
673680
674test "shrink" {681test "shrink" {
675 var gpda = GeneralPurposeAllocator(test_config){};682 var gpda = GeneralPurposeAllocator(test_config){};
676 defer gpda.deinit();683 defer std.testing.expect(!gpda.deinit());
677 const allocator = &gpda.allocator;684 const allocator = &gpda.allocator;
678685
679 var slice = try allocator.alloc(u8, 20);686 var slice = try allocator.alloc(u8, 20);
...@@ -696,7 +703,7 @@ test "shrink" {...@@ -696,7 +703,7 @@ test "shrink" {
696703
697test "large object - grow" {704test "large object - grow" {
698 var gpda = GeneralPurposeAllocator(test_config){};705 var gpda = GeneralPurposeAllocator(test_config){};
699 defer gpda.deinit();706 defer std.testing.expect(!gpda.deinit());
700 const allocator = &gpda.allocator;707 const allocator = &gpda.allocator;
701708
702 var slice1 = try allocator.alloc(u8, page_size * 2 - 20);709 var slice1 = try allocator.alloc(u8, page_size * 2 - 20);
...@@ -714,7 +721,7 @@ test "large object - grow" {...@@ -714,7 +721,7 @@ test "large object - grow" {
714721
715test "realloc small object to large object" {722test "realloc small object to large object" {
716 var gpda = GeneralPurposeAllocator(test_config){};723 var gpda = GeneralPurposeAllocator(test_config){};
717 defer gpda.deinit();724 defer std.testing.expect(!gpda.deinit());
718 const allocator = &gpda.allocator;725 const allocator = &gpda.allocator;
719726
720 var slice = try allocator.alloc(u8, 70);727 var slice = try allocator.alloc(u8, 70);
...@@ -731,7 +738,7 @@ test "realloc small object to large object" {...@@ -731,7 +738,7 @@ test "realloc small object to large object" {
731738
732test "shrink large object to large object" {739test "shrink large object to large object" {
733 var gpda = GeneralPurposeAllocator(test_config){};740 var gpda = GeneralPurposeAllocator(test_config){};
734 defer gpda.deinit();741 defer std.testing.expect(!gpda.deinit());
735 const allocator = &gpda.allocator;742 const allocator = &gpda.allocator;
736743
737 var slice = try allocator.alloc(u8, page_size * 2 + 50);744 var slice = try allocator.alloc(u8, page_size * 2 + 50);
...@@ -754,7 +761,7 @@ test "shrink large object to large object" {...@@ -754,7 +761,7 @@ test "shrink large object to large object" {
754761
755test "shrink large object to large object with larger alignment" {762test "shrink large object to large object with larger alignment" {
756 var gpda = GeneralPurposeAllocator(test_config){};763 var gpda = GeneralPurposeAllocator(test_config){};
757 defer gpda.deinit();764 defer std.testing.expect(!gpda.deinit());
758 const allocator = &gpda.allocator;765 const allocator = &gpda.allocator;
759766
760 var debug_buffer: [1000]u8 = undefined;767 var debug_buffer: [1000]u8 = undefined;
...@@ -782,7 +789,7 @@ test "shrink large object to large object with larger alignment" {...@@ -782,7 +789,7 @@ test "shrink large object to large object with larger alignment" {
782789
783test "realloc large object to small object" {790test "realloc large object to small object" {
784 var gpda = GeneralPurposeAllocator(test_config){};791 var gpda = GeneralPurposeAllocator(test_config){};
785 defer gpda.deinit();792 defer std.testing.expect(!gpda.deinit());
786 const allocator = &gpda.allocator;793 const allocator = &gpda.allocator;
787794
788 var slice = try allocator.alloc(u8, page_size * 2 + 50);795 var slice = try allocator.alloc(u8, page_size * 2 + 50);
...@@ -797,7 +804,7 @@ test "realloc large object to small object" {...@@ -797,7 +804,7 @@ test "realloc large object to small object" {
797804
798test "non-page-allocator backing allocator" {805test "non-page-allocator backing allocator" {
799 var gpda = GeneralPurposeAllocator(.{}){ .backing_allocator = std.testing.allocator };806 var gpda = GeneralPurposeAllocator(.{}){ .backing_allocator = std.testing.allocator };
800 defer gpda.deinit();807 defer std.testing.expect(!gpda.deinit());
801 const allocator = &gpda.allocator;808 const allocator = &gpda.allocator;
802809
803 const ptr = try allocator.create(i32);810 const ptr = try allocator.create(i32);
...@@ -806,7 +813,7 @@ test "non-page-allocator backing allocator" {...@@ -806,7 +813,7 @@ test "non-page-allocator backing allocator" {
806813
807test "realloc large object to larger alignment" {814test "realloc large object to larger alignment" {
808 var gpda = GeneralPurposeAllocator(test_config){};815 var gpda = GeneralPurposeAllocator(test_config){};
809 defer gpda.deinit();816 defer std.testing.expect(!gpda.deinit());
810 const allocator = &gpda.allocator;817 const allocator = &gpda.allocator;
811818
812 var debug_buffer: [1000]u8 = undefined;819 var debug_buffer: [1000]u8 = undefined;
...@@ -866,7 +873,7 @@ test "isAligned works" {...@@ -866,7 +873,7 @@ test "isAligned works" {
866test "large object shrinks to small but allocation fails during shrink" {873test "large object shrinks to small but allocation fails during shrink" {
867 var failing_allocator = std.testing.FailingAllocator.init(std.heap.page_allocator, 3);874 var failing_allocator = std.testing.FailingAllocator.init(std.heap.page_allocator, 3);
868 var gpda = GeneralPurposeAllocator(.{}){ .backing_allocator = &failing_allocator.allocator };875 var gpda = GeneralPurposeAllocator(.{}){ .backing_allocator = &failing_allocator.allocator };
869 defer gpda.deinit();876 defer std.testing.expect(!gpda.deinit());
870 const allocator = &gpda.allocator;877 const allocator = &gpda.allocator;
871878
872 var slice = try allocator.alloc(u8, page_size * 2 + 50);879 var slice = try allocator.alloc(u8, page_size * 2 + 50);
...@@ -883,7 +890,7 @@ test "large object shrinks to small but allocation fails during shrink" {...@@ -883,7 +890,7 @@ test "large object shrinks to small but allocation fails during shrink" {
883890
884test "objects of size 1024 and 2048" {891test "objects of size 1024 and 2048" {
885 var gpda = GeneralPurposeAllocator(test_config){};892 var gpda = GeneralPurposeAllocator(test_config){};
886 defer gpda.deinit();893 defer std.testing.expect(!gpda.deinit());
887 const allocator = &gpda.allocator;894 const allocator = &gpda.allocator;
888895
889 const slice = try allocator.alloc(u8, 1025);896 const slice = try allocator.alloc(u8, 1025);
...@@ -895,7 +902,7 @@ test "objects of size 1024 and 2048" {...@@ -895,7 +902,7 @@ test "objects of size 1024 and 2048" {
895902
896test "setting a memory cap" {903test "setting a memory cap" {
897 var gpda = GeneralPurposeAllocator(.{ .enable_memory_limit = true }){};904 var gpda = GeneralPurposeAllocator(.{ .enable_memory_limit = true }){};
898 defer gpda.deinit();905 defer std.testing.expect(!gpda.deinit());
899 const allocator = &gpda.allocator;906 const allocator = &gpda.allocator;
900907
901 gpda.setRequestedMemoryLimit(1010);908 gpda.setRequestedMemoryLimit(1010);
lib/std/mutex.zig+90-114
...@@ -15,8 +15,7 @@ const ResetEvent = std.ResetEvent;...@@ -15,8 +15,7 @@ const ResetEvent = std.ResetEvent;
15/// deadlock detection.15/// deadlock detection.
16///16///
17/// Example usage:17/// Example usage:
18/// var m = Mutex.init();18/// var m = Mutex{};
19/// defer m.deinit();
20///19///
21/// const lock = m.acquire();20/// const lock = m.acquire();
22/// defer lock.release();21/// defer lock.release();
...@@ -32,101 +31,11 @@ const ResetEvent = std.ResetEvent;...@@ -32,101 +31,11 @@ const ResetEvent = std.ResetEvent;
32pub const Mutex = if (builtin.single_threaded)31pub const Mutex = if (builtin.single_threaded)
33 Dummy32 Dummy
34else if (builtin.os.tag == .windows)33else if (builtin.os.tag == .windows)
35// https://locklessinc.com/articles/keyed_events/34 WindowsMutex
36 extern union {
37 locked: u8,
38 waiters: u32,
39
40 const WAKE = 1 << 8;
41 const WAIT = 1 << 9;
42
43 pub const Dummy = Dummy;
44
45 pub fn init() Mutex {
46 return Mutex{ .waiters = 0 };
47 }
48
49 pub fn deinit(self: *Mutex) void {
50 self.* = undefined;
51 }
52
53 pub fn tryAcquire(self: *Mutex) ?Held {
54 if (@atomicRmw(u8, &self.locked, .Xchg, 1, .Acquire) != 0)
55 return null;
56 return Held{ .mutex = self };
57 }
58
59 pub fn acquire(self: *Mutex) Held {
60 return self.tryAcquire() orelse self.acquireSlow();
61 }
62
63 fn acquireSpinning(self: *Mutex) Held {
64 @setCold(true);
65 while (true) : (SpinLock.yield()) {
66 return self.tryAcquire() orelse continue;
67 }
68 }
69
70 fn acquireSlow(self: *Mutex) Held {
71 // try to use NT keyed events for blocking, falling back to spinlock if unavailable
72 @setCold(true);
73 const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return self.acquireSpinning();
74 const key = @ptrCast(*const c_void, &self.waiters);
75
76 while (true) : (SpinLock.loopHint(1)) {
77 const waiters = @atomicLoad(u32, &self.waiters, .Monotonic);
78
79 // try and take lock if unlocked
80 if ((waiters & 1) == 0) {
81 if (@atomicRmw(u8, &self.locked, .Xchg, 1, .Acquire) == 0) {
82 return Held{ .mutex = self };
83 }
84
85 // otherwise, try and update the waiting count.
86 // then unset the WAKE bit so that another unlocker can wake up a thread.
87 } else if (@cmpxchgWeak(u32, &self.waiters, waiters, (waiters + WAIT) | 1, .Monotonic, .Monotonic) == null) {
88 const rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null);
89 assert(rc == .SUCCESS);
90 _ = @atomicRmw(u32, &self.waiters, .Sub, WAKE, .Monotonic);
91 }
92 }
93 }
94
95 pub const Held = struct {
96 mutex: *Mutex,
97
98 pub fn release(self: Held) void {
99 // unlock without a rmw/cmpxchg instruction
100 @atomicStore(u8, @ptrCast(*u8, &self.mutex.locked), 0, .Release);
101 const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return;
102 const key = @ptrCast(*const c_void, &self.mutex.waiters);
103
104 while (true) : (SpinLock.loopHint(1)) {
105 const waiters = @atomicLoad(u32, &self.mutex.waiters, .Monotonic);
106
107 // no one is waiting
108 if (waiters < WAIT) return;
109 // someone grabbed the lock and will do the wake instead
110 if (waiters & 1 != 0) return;
111 // someone else is currently waking up
112 if (waiters & WAKE != 0) return;
113
114 // try to decrease the waiter count & set the WAKE bit meaning a thread is waking up
115 if (@cmpxchgWeak(u32, &self.mutex.waiters, waiters, waiters - WAIT + WAKE, .Release, .Monotonic) == null) {
116 const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null);
117 assert(rc == .SUCCESS);
118 return;
119 }
120 }
121 }
122 };
123 }
124else if (builtin.link_libc or builtin.os.tag == .linux)35else if (builtin.link_libc or builtin.os.tag == .linux)
125// stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs36// stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs
126 struct {37 struct {
127 state: usize,38 state: usize = 0,
128
129 pub const Dummy = Dummy;
13039
131 /// number of times to spin trying to acquire the lock.40 /// number of times to spin trying to acquire the lock.
132 /// https://webkit.org/blog/6161/locking-in-webkit/41 /// https://webkit.org/blog/6161/locking-in-webkit/
...@@ -141,14 +50,6 @@ else if (builtin.link_libc or builtin.os.tag == .linux)...@@ -141,14 +50,6 @@ else if (builtin.link_libc or builtin.os.tag == .linux)
141 event: ResetEvent,50 event: ResetEvent,
142 };51 };
14352
144 pub fn init() Mutex {
145 return Mutex{ .state = 0 };
146 }
147
148 pub fn deinit(self: *Mutex) void {
149 self.* = undefined;
150 }
151
152 pub fn tryAcquire(self: *Mutex) ?Held {53 pub fn tryAcquire(self: *Mutex) ?Held {
153 if (@cmpxchgWeak(usize, &self.state, 0, MUTEX_LOCK, .Acquire, .Monotonic) != null)54 if (@cmpxchgWeak(usize, &self.state, 0, MUTEX_LOCK, .Acquire, .Monotonic) != null)
154 return null;55 return null;
...@@ -263,7 +164,7 @@ else...@@ -263,7 +164,7 @@ else
263/// This has the sematics as `Mutex`, however it does not actually do any164/// This has the sematics as `Mutex`, however it does not actually do any
264/// synchronization. Operations are safety-checked no-ops.165/// synchronization. Operations are safety-checked no-ops.
265pub const Dummy = struct {166pub const Dummy = struct {
266 lock: @TypeOf(lock_init),167 lock: @TypeOf(lock_init) = lock_init,
267168
268 const lock_init = if (std.debug.runtime_safety) false else {};169 const lock_init = if (std.debug.runtime_safety) false else {};
269170
...@@ -278,15 +179,7 @@ pub const Dummy = struct {...@@ -278,15 +179,7 @@ pub const Dummy = struct {
278 };179 };
279180
280 /// Create a new mutex in unlocked state.181 /// Create a new mutex in unlocked state.
281 pub fn init() Dummy {182 pub const init = Dummy{};
282 return Dummy{ .lock = lock_init };
283 }
284
285 /// Free a mutex created with init. Calling this while the
286 /// mutex is held is illegal behavior.
287 pub fn deinit(self: *Dummy) void {
288 self.* = undefined;
289 }
290183
291 /// Try to acquire the mutex without blocking. Returns null if184 /// Try to acquire the mutex without blocking. Returns null if
292 /// the mutex is unavailable. Otherwise returns Held. Call185 /// the mutex is unavailable. Otherwise returns Held. Call
...@@ -306,6 +199,90 @@ pub const Dummy = struct {...@@ -306,6 +199,90 @@ pub const Dummy = struct {
306 }199 }
307};200};
308201
202// https://locklessinc.com/articles/keyed_events/
203const WindowsMutex = struct {
204 state: State = State{ .waiters = 0 },
205
206 const State = extern union {
207 locked: u8,
208 waiters: u32,
209 };
210
211 const WAKE = 1 << 8;
212 const WAIT = 1 << 9;
213
214 pub fn tryAcquire(self: *WindowsMutex) ?Held {
215 if (@atomicRmw(u8, &self.state.locked, .Xchg, 1, .Acquire) != 0)
216 return null;
217 return Held{ .mutex = self };
218 }
219
220 pub fn acquire(self: *WindowsMutex) Held {
221 return self.tryAcquire() orelse self.acquireSlow();
222 }
223
224 fn acquireSpinning(self: *WindowsMutex) Held {
225 @setCold(true);
226 while (true) : (SpinLock.yield()) {
227 return self.tryAcquire() orelse continue;
228 }
229 }
230
231 fn acquireSlow(self: *WindowsMutex) Held {
232 // try to use NT keyed events for blocking, falling back to spinlock if unavailable
233 @setCold(true);
234 const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return self.acquireSpinning();
235 const key = @ptrCast(*const c_void, &self.state.waiters);
236
237 while (true) : (SpinLock.loopHint(1)) {
238 const waiters = @atomicLoad(u32, &self.state.waiters, .Monotonic);
239
240 // try and take lock if unlocked
241 if ((waiters & 1) == 0) {
242 if (@atomicRmw(u8, &self.state.locked, .Xchg, 1, .Acquire) == 0) {
243 return Held{ .mutex = self };
244 }
245
246 // otherwise, try and update the waiting count.
247 // then unset the WAKE bit so that another unlocker can wake up a thread.
248 } else if (@cmpxchgWeak(u32, &self.state.waiters, waiters, (waiters + WAIT) | 1, .Monotonic, .Monotonic) == null) {
249 const rc = windows.ntdll.NtWaitForKeyedEvent(handle, key, windows.FALSE, null);
250 assert(rc == .SUCCESS);
251 _ = @atomicRmw(u32, &self.state.waiters, .Sub, WAKE, .Monotonic);
252 }
253 }
254 }
255
256 pub const Held = struct {
257 mutex: *WindowsMutex,
258
259 pub fn release(self: Held) void {
260 // unlock without a rmw/cmpxchg instruction
261 @atomicStore(u8, @ptrCast(*u8, &self.mutex.state.locked), 0, .Release);
262 const handle = ResetEvent.OsEvent.Futex.getEventHandle() orelse return;
263 const key = @ptrCast(*const c_void, &self.mutex.state.waiters);
264
265 while (true) : (SpinLock.loopHint(1)) {
266 const waiters = @atomicLoad(u32, &self.mutex.state.waiters, .Monotonic);
267
268 // no one is waiting
269 if (waiters < WAIT) return;
270 // someone grabbed the lock and will do the wake instead
271 if (waiters & 1 != 0) return;
272 // someone else is currently waking up
273 if (waiters & WAKE != 0) return;
274
275 // try to decrease the waiter count & set the WAKE bit meaning a thread is waking up
276 if (@cmpxchgWeak(u32, &self.mutex.state.waiters, waiters, waiters - WAIT + WAKE, .Release, .Monotonic) == null) {
277 const rc = windows.ntdll.NtReleaseKeyedEvent(handle, key, windows.FALSE, null);
278 assert(rc == .SUCCESS);
279 return;
280 }
281 }
282 }
283 };
284};
285
309const TestContext = struct {286const TestContext = struct {
310 mutex: *Mutex,287 mutex: *Mutex,
311 data: i128,288 data: i128,
...@@ -314,8 +291,7 @@ const TestContext = struct {...@@ -314,8 +291,7 @@ const TestContext = struct {
314};291};
315292
316test "std.Mutex" {293test "std.Mutex" {
317 var mutex = Mutex.init();294 var mutex = Mutex{};
318 defer mutex.deinit();
319295
320 var context = TestContext{296 var context = TestContext{
321 .mutex = &mutex,297 .mutex = &mutex,
lib/std/once.zig+1-1
...@@ -10,7 +10,7 @@ pub fn once(comptime f: fn () void) Once(f) {...@@ -10,7 +10,7 @@ pub fn once(comptime f: fn () void) Once(f) {
10pub fn Once(comptime f: fn () void) type {10pub fn Once(comptime f: fn () void) type {
11 return struct {11 return struct {
12 done: bool = false,12 done: bool = false,
13 mutex: std.Mutex = std.Mutex.init(),13 mutex: std.Mutex = std.Mutex{},
1414
15 /// Call the function `f`.15 /// Call the function `f`.
16 /// If `call` is invoked multiple times `f` will be executed only the16 /// If `call` is invoked multiple times `f` will be executed only the
lib/std/special/test_runner.zig+10-1
...@@ -19,9 +19,14 @@ pub fn main() anyerror!void {...@@ -19,9 +19,14 @@ pub fn main() anyerror!void {
19 // ignores the alignment of the slice.19 // ignores the alignment of the slice.
20 async_frame_buffer = &[_]u8{};20 async_frame_buffer = &[_]u8{};
2121
22 var leaks: usize = 0;
22 for (test_fn_list) |test_fn, i| {23 for (test_fn_list) |test_fn, i| {
23 std.testing.allocator_instance = std.heap.GeneralPurposeAllocator(.{}){};24 std.testing.allocator_instance = std.heap.GeneralPurposeAllocator(.{}){};
24 defer std.testing.allocator_instance.deinit();25 defer {
26 if (std.testing.allocator_instance.deinit()) {
27 leaks += 1;
28 }
29 }
25 std.testing.log_level = .warn;30 std.testing.log_level = .warn;
2631
27 var test_node = root_node.start(test_fn.name, null);32 var test_node = root_node.start(test_fn.name, null);
...@@ -70,6 +75,10 @@ pub fn main() anyerror!void {...@@ -70,6 +75,10 @@ pub fn main() anyerror!void {
70 } else {75 } else {
71 std.debug.print("{} passed; {} skipped.\n", .{ ok_count, skip_count });76 std.debug.print("{} passed; {} skipped.\n", .{ ok_count, skip_count });
72 }77 }
78 if (leaks != 0) {
79 std.debug.print("{} tests leaked memory\n", .{ok_count});
80 std.process.exit(1);
81 }
73}82}
7483
75pub fn log(84pub fn log(
lib/std/std.zig+2-1
...@@ -13,7 +13,8 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM...@@ -13,7 +13,8 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM
13pub const DynLib = @import("dynamic_library.zig").DynLib;13pub const DynLib = @import("dynamic_library.zig").DynLib;
14pub const HashMap = hash_map.HashMap;14pub const HashMap = hash_map.HashMap;
15pub const HashMapUnmanaged = hash_map.HashMapUnmanaged;15pub const HashMapUnmanaged = hash_map.HashMapUnmanaged;
16pub const Mutex = @import("mutex.zig").Mutex;16pub const mutex = @import("mutex.zig");
17pub const Mutex = mutex.Mutex;
17pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray;18pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray;
18pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian;19pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian;
19pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;20pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;
src-self-hosted/main.zig+3-2
...@@ -60,9 +60,10 @@ pub fn log(...@@ -60,9 +60,10 @@ pub fn log(
60 std.debug.print(prefix ++ format, args);60 std.debug.print(prefix ++ format, args);
61}61}
6262
63var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
64
63pub fn main() !void {65pub fn main() !void {
64 // TODO general purpose allocator in the zig std lib66 const gpa = if (std.builtin.link_libc) std.heap.c_allocator else &general_purpose_allocator.allocator;
65 const gpa = if (std.builtin.link_libc) std.heap.c_allocator else std.heap.page_allocator;
66 var arena_instance = std.heap.ArenaAllocator.init(gpa);67 var arena_instance = std.heap.ArenaAllocator.init(gpa);
67 defer arena_instance.deinit();68 defer arena_instance.deinit();
68 const arena = &arena_instance.allocator;69 const arena = &arena_instance.allocator;
src-self-hosted/test.zig-3
...@@ -407,8 +407,6 @@ pub const TestContext = struct {...@@ -407,8 +407,6 @@ pub const TestContext = struct {
407 defer root_node.end();407 defer root_node.end();
408408
409 for (self.cases.items) |case| {409 for (self.cases.items) |case| {
410 std.testing.base_allocator_instance.reset();
411
412 var prg_node = root_node.start(case.name, case.updates.items.len);410 var prg_node = root_node.start(case.name, case.updates.items.len);
413 prg_node.activate();411 prg_node.activate();
414 defer prg_node.end();412 defer prg_node.end();
...@@ -419,7 +417,6 @@ pub const TestContext = struct {...@@ -419,7 +417,6 @@ pub const TestContext = struct {
419 progress.refresh_rate_ns = 0;417 progress.refresh_rate_ns = 0;
420418
421 try self.runOneCase(std.testing.allocator, &prg_node, case);419 try self.runOneCase(std.testing.allocator, &prg_node, case);
422 try std.testing.allocator_instance.validate();
423 }420 }
424 }421 }
425422