| author | |
| committer | |
| log | 0347df82e8c821906ef0d07ec65fe4b3884c0212 |
| tree | 20076f52a0024e2252eac24683e39d3ee25d4d28 |
| parent | cc17f84cccc540143f3fd19fe32218478d4a0c6f |
* 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 | } |
| 28 | 28 |
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 | }; |
| 49 | 49 | ||
| 50 | var stderr_mutex = std.Mutex.init(); | 50 | var stderr_mutex = std.Mutex{}; |
| 51 | 51 | ||
| 52 | /// Deprecated. Use `std.log` functions for logging or `std.debug.print` for | 52 | /// 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 { |
| 232 | var panicking: u8 = 0; | 232 | var panicking: u8 = 0; |
| 233 | 233 | ||
| 234 | // Locked to avoid interleaving panic messages from multiple threads. | 234 | // Locked to avoid interleaving panic messages from multiple threads. |
| 235 | var panic_mutex = std.Mutex.init(); | 235 | var panic_mutex = std.Mutex{}; |
| 236 | 236 | ||
| 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 | } |
| 466 | 466 | ||
| 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 {}; |
| 142 | 142 | ||
| 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{}; |
| 144 | 144 | ||
| 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 | } |
| 278 | 281 | ||
| 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 | } |
| 299 | 306 | ||
| 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{}; |
| 600 | 607 | ||
| 601 | test "small allocations - free in same order" { | 608 | test "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; |
| 605 | 612 | ||
| 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" { |
| 619 | 626 | ||
| 620 | test "small allocations - free in reverse order" { | 627 | test "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; |
| 624 | 631 | ||
| 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" { |
| 638 | 645 | ||
| 639 | test "large allocations" { | 646 | test "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; |
| 643 | 650 | ||
| 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" { |
| 651 | 658 | ||
| 652 | test "realloc" { | 659 | test "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; |
| 656 | 663 | ||
| 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" { |
| 673 | 680 | ||
| 674 | test "shrink" { | 681 | test "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; |
| 678 | 685 | ||
| 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" { |
| 696 | 703 | ||
| 697 | test "large object - grow" { | 704 | test "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; |
| 701 | 708 | ||
| 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" { |
| 714 | 721 | ||
| 715 | test "realloc small object to large object" { | 722 | test "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; |
| 719 | 726 | ||
| 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" { |
| 731 | 738 | ||
| 732 | test "shrink large object to large object" { | 739 | test "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; |
| 736 | 743 | ||
| 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" { |
| 754 | 761 | ||
| 755 | test "shrink large object to large object with larger alignment" { | 762 | test "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; |
| 759 | 766 | ||
| 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" { |
| 782 | 789 | ||
| 783 | test "realloc large object to small object" { | 790 | test "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; |
| 787 | 794 | ||
| 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" { |
| 797 | 804 | ||
| 798 | test "non-page-allocator backing allocator" { | 805 | test "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; |
| 802 | 809 | ||
| 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" { |
| 806 | 813 | ||
| 807 | test "realloc large object to larger alignment" { | 814 | test "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; |
| 811 | 818 | ||
| 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" { |
| 866 | test "large object shrinks to small but allocation fails during shrink" { | 873 | test "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; |
| 871 | 878 | ||
| 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" { |
| 883 | 890 | ||
| 884 | test "objects of size 1024 and 2048" { | 891 | test "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; |
| 888 | 895 | ||
| 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" { |
| 895 | 902 | ||
| 896 | test "setting a memory cap" { | 903 | test "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; |
| 900 | 907 | ||
| 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; |
| 32 | pub const Mutex = if (builtin.single_threaded) | 31 | pub const Mutex = if (builtin.single_threaded) |
| 33 | Dummy | 32 | Dummy |
| 34 | else if (builtin.os.tag == .windows) | 33 | else 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 | } | ||
| 124 | else if (builtin.link_libc or builtin.os.tag == .linux) | 35 | else 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.rs | 36 | // 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; | ||
| 130 | 39 | ||
| 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 | }; |
| 143 | 52 | ||
| 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 any | 164 | /// 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. |
| 265 | pub const Dummy = struct { | 166 | pub const Dummy = struct { |
| 266 | lock: @TypeOf(lock_init), | 167 | lock: @TypeOf(lock_init) = lock_init, |
| 267 | 168 | ||
| 268 | const lock_init = if (std.debug.runtime_safety) false else {}; | 169 | const lock_init = if (std.debug.runtime_safety) false else {}; |
| 269 | 170 | ||
| ... | @@ -278,15 +179,7 @@ pub const Dummy = struct { | ... | @@ -278,15 +179,7 @@ pub const Dummy = struct { |
| 278 | }; | 179 | }; |
| 279 | 180 | ||
| 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 | } | ||
| 290 | 183 | ||
| 291 | /// Try to acquire the mutex without blocking. Returns null if | 184 | /// Try to acquire the mutex without blocking. Returns null if |
| 292 | /// the mutex is unavailable. Otherwise returns Held. Call | 185 | /// 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 | }; |
| 308 | 201 | ||
| 202 | // https://locklessinc.com/articles/keyed_events/ | ||
| 203 | const 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 | |||
| 309 | const TestContext = struct { | 286 | const 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 | }; |
| 315 | 292 | ||
| 316 | test "std.Mutex" { | 293 | test "std.Mutex" { |
| 317 | var mutex = Mutex.init(); | 294 | var mutex = Mutex{}; |
| 318 | defer mutex.deinit(); | ||
| 319 | 295 | ||
| 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) { |
| 10 | pub fn Once(comptime f: fn () void) type { | 10 | pub 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{}, |
| 14 | 14 | ||
| 15 | /// Call the function `f`. | 15 | /// Call the function `f`. |
| 16 | /// If `call` is invoked multiple times `f` will be executed only the | 16 | /// 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{}; |
| 21 | 21 | ||
| 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; |
| 26 | 31 | ||
| 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 | } |
| 74 | 83 | ||
| 75 | pub fn log( | 84 | pub 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 |
| 13 | pub const DynLib = @import("dynamic_library.zig").DynLib; | 13 | pub const DynLib = @import("dynamic_library.zig").DynLib; |
| 14 | pub const HashMap = hash_map.HashMap; | 14 | pub const HashMap = hash_map.HashMap; |
| 15 | pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; | 15 | pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; |
| 16 | pub const Mutex = @import("mutex.zig").Mutex; | 16 | pub const mutex = @import("mutex.zig"); |
| 17 | pub const Mutex = mutex.Mutex; | ||
| 17 | pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; | 18 | pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; |
| 18 | pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; | 19 | pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; |
| 19 | pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; | 20 | pub 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 | } |
| 62 | 62 | ||
| 63 | var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; | ||
| 64 | |||
| 63 | pub fn main() !void { | 65 | pub fn main() !void { |
| 64 | // TODO general purpose allocator in the zig std lib | 66 | 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(); |
| 408 | 408 | ||
| 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; |
| 420 | 418 | ||
| 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 | } |
| 425 | 422 |