authorgravatar for hampus.frojdholm@gmail.comHampus Fröjdholm <hampus.frojdholm@gmail.com> 2024-05-18 11:43:42+02:00
committergravatar for hampus.frojdholm@gmail.comHampus Fröjdholm <hampus.frojdholm@gmail.com> 2024-05-18 11:43:42+02:00
log61f1b2db704c9bfa96c6a965fdba57cf3692b2c9
treec9581f08f298b77908211240ad9a93f06364952d
parent6a65561e3e5f82f126ec4795e5cd9c07392b457b

gpa: Add helper to calculate size class of empty buckets

Empty buckets have their `alloc_cursor` set to `slot_count` to allow the size class to be calculated later. This happens deep within the free function. This adds a helper and a test to verify that the size class of empty buckets is indeed recoverable.

1 files changed, 24 insertions(+), 1 deletions(-)

lib/std/heap/general_purpose_allocator.zig+24-1
...@@ -297,6 +297,12 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -297,6 +297,12 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
297 const stack_addresses = bucket.stackTracePtr(size_class, slot_index, trace_kind);297 const stack_addresses = bucket.stackTracePtr(size_class, slot_index, trace_kind);
298 collectStackTrace(ret_addr, stack_addresses);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 };
301307
302 pub fn allocator(self: *Self) Allocator {308 pub fn allocator(self: *Self) Allocator {
...@@ -447,7 +453,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {...@@ -447,7 +453,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
447 self.backing_allocator.free(bucket.page[0..page_size]);453 self.backing_allocator.free(bucket.page[0..page_size]);
448 }454 }
449 // alloc_cursor was set to slot count when bucket added to empty_buckets455 // 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 self.bucket_node_pool.destroy(node);457 self.bucket_node_pool.destroy(node);
452 }458 }
453 self.empty_buckets.root = null;459 self.empty_buckets.root = null;
...@@ -1418,6 +1424,23 @@ test "double frees" {...@@ -1418,6 +1424,23 @@ test "double frees" {
1418 try std.testing.expect(!gpa.large_allocations.contains(@intFromPtr(large.ptr)));1424 try std.testing.expect(!gpa.large_allocations.contains(@intFromPtr(large.ptr)));
1419}1425}
14201426
1427test "empty bucket size class" {
1428 const GPA = GeneralPurposeAllocator(.{ .safety = true, .never_unmap = true, .retain_metadata = true });
1429 var gpa = GPA{};
1430 defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
1431 const allocator = gpa.allocator();
1432
1433 // allocate and free to create an empty bucket
1434 const size_class: usize = @as(usize, 1) << 6;
1435 const small = try allocator.alloc(u8, size_class);
1436 allocator.free(small);
1437
1438 // the metadata tracking system relies on alloc_cursor of empty buckets
1439 // being set to the slot count so that we can get back the size class.
1440 const empty_bucket = GPA.searchBucket(&gpa.empty_buckets, @intFromPtr(small.ptr), null).?;
1441 try std.testing.expect(empty_bucket.emptyBucketSizeClass() == size_class);
1442}
1443
1421test "bug 9995 fix, large allocs count requested size not backing size" {1444test "bug 9995 fix, large allocs count requested size not backing size" {
1422 // with AtLeast, buffer likely to be larger than requested, especially when shrinking1445 // with AtLeast, buffer likely to be larger than requested, especially when shrinking
1423 var gpa = GeneralPurposeAllocator(.{ .enable_memory_limit = true }){};1446 var gpa = GeneralPurposeAllocator(.{ .enable_memory_limit = true }){};