| ... | ... | @@ -297,6 +297,12 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 297 | 297 | const stack_addresses = bucket.stackTracePtr(size_class, slot_index, trace_kind); |
| 298 | 298 | collectStackTrace(ret_addr, stack_addresses); |
| 299 | 299 | } |
| 300 | |
| 301 | /// Only valid for buckets within `empty_buckets`, and relies on the `alloc_cursor` |
| 302 | /// of empty buckets being set to `slot_count` when they are added to `empty_buckets` |
| 303 | fn emptyBucketSizeClass(bucket: *BucketHeader) usize { |
| 304 | return @divExact(page_size, bucket.alloc_cursor); |
| 305 | } |
| 300 | 306 | }; |
| 301 | 307 | |
| 302 | 308 | pub fn allocator(self: *Self) Allocator { |
| ... | ... | @@ -447,7 +453,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 447 | 453 | self.backing_allocator.free(bucket.page[0..page_size]); |
| 448 | 454 | } |
| 449 | 455 | // alloc_cursor was set to slot count when bucket added to empty_buckets |
| 450 | | self.freeBucket(bucket, @divExact(page_size, bucket.alloc_cursor)); |
| 456 | self.freeBucket(bucket, bucket.emptyBucketSizeClass()); |
| 451 | 457 | self.bucket_node_pool.destroy(node); |
| 452 | 458 | } |
| 453 | 459 | self.empty_buckets.root = null; |
| ... | ... | @@ -726,6 +732,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 726 | 732 | if (!self.large_allocations.contains(@intFromPtr(old_mem.ptr))) { |
| 727 | 733 | // object not in active buckets or a large allocation, so search empty buckets |
| 728 | 734 | if (searchBucket(&self.empty_buckets, @intFromPtr(old_mem.ptr), null)) |bucket| { |
| 735 | size_class = bucket.emptyBucketSizeClass(); |
| 729 | 736 | // bucket is empty so is_used below will always be false and we exit there |
| 730 | 737 | break :blk bucket; |
| 731 | 738 | } else { |
| ... | ... | @@ -844,6 +851,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 844 | 851 | if (!self.large_allocations.contains(@intFromPtr(old_mem.ptr))) { |
| 845 | 852 | // object not in active buckets or a large allocation, so search empty buckets |
| 846 | 853 | if (searchBucket(&self.empty_buckets, @intFromPtr(old_mem.ptr), null)) |bucket| { |
| 854 | size_class = bucket.emptyBucketSizeClass(); |
| 847 | 855 | // bucket is empty so is_used below will always be false and we exit there |
| 848 | 856 | break :blk bucket; |
| 849 | 857 | } else { |
| ... | ... | @@ -1418,6 +1426,23 @@ test "double frees" { |
| 1418 | 1426 | try std.testing.expect(!gpa.large_allocations.contains(@intFromPtr(large.ptr))); |
| 1419 | 1427 | } |
| 1420 | 1428 | |
| 1429 | test "empty bucket size class" { |
| 1430 | const GPA = GeneralPurposeAllocator(.{ .safety = true, .never_unmap = true, .retain_metadata = true }); |
| 1431 | var gpa = GPA{}; |
| 1432 | defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak"); |
| 1433 | const allocator = gpa.allocator(); |
| 1434 | |
| 1435 | // allocate and free to create an empty bucket |
| 1436 | const size_class: usize = @as(usize, 1) << 6; |
| 1437 | const small = try allocator.alloc(u8, size_class); |
| 1438 | allocator.free(small); |
| 1439 | |
| 1440 | // the metadata tracking system relies on alloc_cursor of empty buckets |
| 1441 | // being set to the slot count so that we can get back the size class. |
| 1442 | const empty_bucket = GPA.searchBucket(&gpa.empty_buckets, @intFromPtr(small.ptr), null).?; |
| 1443 | try std.testing.expect(empty_bucket.emptyBucketSizeClass() == size_class); |
| 1444 | } |
| 1445 | |
| 1421 | 1446 | test "bug 9995 fix, large allocs count requested size not backing size" { |
| 1422 | 1447 | // with AtLeast, buffer likely to be larger than requested, especially when shrinking |
| 1423 | 1448 | var gpa = GeneralPurposeAllocator(.{ .enable_memory_limit = true }){}; |