lib/choir/src/core/interfaces/entry.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 
  3 const Allocator = std.mem.Allocator;
  4 
  5 const TestInfo = struct {
  6     name: []const u8,
  7 
  8     pub fn init(name: []const u8) TestInfo {
  9         return .{ .name = name };
 10     }
 11 
 12     pub fn deinit(_: *TestInfo, _: Allocator) void {}
 13 };
 14 
 15 const TestInlineInfo = struct {
 16     name: []const u8,
 17     first_items: []u64,
 18     second_items: []u32,
 19     third_items: []u16,
 20     fourth_items: []u8,
 21 
 22     pub fn initEntryStorage(
 23         name: []const u8,
 24         first_storage: *[4]u64,
 25         second_storage: *[2]u32,
 26         third_storage: []u16,
 27         fourth_storage: []u8,
 28     ) TestInlineInfo {
 29         return .{
 30             .name = name,
 31             .first_items = first_storage[0..0],
 32             .second_items = second_storage[0..0],
 33             .third_items = third_storage[0..0],
 34             .fourth_items = fourth_storage[0..0],
 35         };
 36     }
 37 
 38     pub fn deinit(_: *TestInlineInfo, _: Allocator) void {}
 39 };
 40 
 41 pub fn InlineList(comptime Item: type, comptime inline_capacity: usize) type {
 42     const inline_mask = @as(usize, 1) << (@bitSizeOf(usize) - 1);
 43 
 44     return struct {
 45         const Self = @This();
 46 
 47         slice: []Item = &.{},
 48         capacity_and_inline: usize = 0,
 49 
 50         pub fn initInline(storage: *[inline_capacity]Item) Self {
 51             return initBorrowed(storage);
 52         }
 53 
 54         pub fn initBorrowed(storage: []Item) Self {
 55             std.debug.assert(storage.len & inline_mask == 0);
 56             return .{
 57                 .slice = storage[0..0],
 58                 .capacity_and_inline = inline_mask | storage.len,
 59             };
 60         }
 61 
 62         pub fn initBorrowedValues(storage: []Item, values_to_copy: []const Item) Self {
 63             std.debug.assert(storage.len & inline_mask == 0);
 64             std.debug.assert(values_to_copy.len <= storage.len);
 65             @memcpy(storage[0..values_to_copy.len], values_to_copy);
 66             return .{
 67                 .slice = storage[0..values_to_copy.len],
 68                 .capacity_and_inline = inline_mask | storage.len,
 69             };
 70         }
 71 
 72         pub fn deinit(self: *Self, allocator: Allocator) void {
 73             if (!self.isInline() and self.capacity() > 0) {
 74                 allocator.free(self.slice.ptr[0..self.capacity()]);
 75             }
 76             self.* = .{};
 77         }
 78 
 79         pub fn append(self: *Self, allocator: Allocator, item: Item) Allocator.Error!void {
 80             const capacity_value = self.capacity();
 81             if (self.slice.len < capacity_value) {
 82                 self.slice.ptr[self.slice.len] = item;
 83                 self.slice.len += 1;
 84                 return;
 85             }
 86 
 87             if (self.isInline()) {
 88                 std.debug.assert(self.slice.len == capacity_value);
 89                 const heap_capacity = std.math.add(usize, capacity_value, 1) catch {
 90                     return error.OutOfMemory;
 91                 };
 92                 const heap_items = try allocator.alloc(Item, heap_capacity);
 93                 errdefer allocator.free(heap_items);
 94                 @memcpy(heap_items[0..capacity_value], self.slice);
 95                 heap_items[capacity_value] = item;
 96                 self.slice = heap_items;
 97                 self.capacity_and_inline = heap_items.len;
 98                 return;
 99             }
100 
101             var heap: std.ArrayListUnmanaged(Item) = .{
102                 .items = self.slice,
103                 .capacity = capacity_value,
104             };
105             try heap.append(allocator, item);
106             std.debug.assert(heap.capacity & inline_mask == 0);
107             self.slice = heap.items;
108             self.capacity_and_inline = heap.capacity;
109         }
110 
111         pub fn values(self: *const Self) []const Item {
112             return self.slice;
113         }
114 
115         pub fn valuesMut(self: *Self) []Item {
116             return self.slice;
117         }
118 
119         fn capacity(self: *const Self) usize {
120             return self.capacity_and_inline & ~inline_mask;
121         }
122 
123         fn isInline(self: *const Self) bool {
124             return self.capacity_and_inline & inline_mask != 0;
125         }
126     };
127 }
128 
129 pub fn Storage(comptime Info: type) type {
130     const alignment = std.mem.Alignment.fromByteUnits(@alignOf(Info));
131 
132     return struct {
133         const Self = @This();
134 
135         pub fn capacity(name_len: usize) error{CapacityOverflow}!usize {
136             return std.math.add(usize, @sizeOf(Info), name_len) catch error.CapacityOverflow;
137         }
138 
139         pub fn create(allocator: Allocator, name: []const u8) Allocator.Error!*Info {
140             const total_bytes = Self.capacity(name.len) catch return error.OutOfMemory;
141             std.debug.assert(total_bytes >= @sizeOf(Info));
142             const bytes = allocator.rawAlloc(
143                 total_bytes,
144                 alignment,
145                 @returnAddress(),
146             ) orelse return error.OutOfMemory;
147             const info: *Info = @ptrCast(@alignCast(bytes));
148             const owned_name = bytes[@sizeOf(Info)..total_bytes];
149             std.debug.assert(owned_name.len == name.len);
150             @memcpy(owned_name, name);
151             info.* = Info.init(owned_name);
152             return info;
153         }
154 
155         pub fn destroy(allocator: Allocator, info: *Info) void {
156             const total_bytes = Self.capacity(info.name.len) catch unreachable;
157             const bytes: [*]u8 = @ptrCast(info);
158             const name_address = std.math.add(
159                 usize,
160                 @intFromPtr(info),
161                 @sizeOf(Info),
162             ) catch unreachable;
163             std.debug.assert(name_address == @intFromPtr(info.name.ptr));
164             info.deinit(allocator);
165             info.* = undefined;
166             allocator.rawFree(bytes[0..total_bytes], alignment, @returnAddress());
167         }
168     };
169 }
170 
171 pub fn BatchStorage(comptime Info: type) type {
172     const Region = struct {
173         next: ?*@This(),
174         total_bytes: usize,
175     };
176     const alignment = std.mem.Alignment.fromByteUnits(@max(@alignOf(Region), @alignOf(Info)));
177     const infos_offset = std.mem.alignForward(usize, @sizeOf(Region), @alignOf(Info));
178 
179     return struct {
180         const Self = @This();
181 
182         pub const RegionOwner = Region;
183 
184         pub const Allocation = struct {
185             region: *RegionOwner,
186             infos: []Info,
187             names: []u8,
188         };
189 
190         pub fn capacity(info_count: usize, name_bytes: usize) error{CapacityOverflow}!usize {
191             const info_bytes = std.math.mul(
192                 usize,
193                 info_count,
194                 @sizeOf(Info),
195             ) catch return error.CapacityOverflow;
196             const names_offset = std.math.add(
197                 usize,
198                 infos_offset,
199                 info_bytes,
200             ) catch return error.CapacityOverflow;
201             return std.math.add(usize, names_offset, name_bytes) catch error.CapacityOverflow;
202         }
203 
204         pub fn create(
205             allocator: Allocator,
206             info_count: usize,
207             name_bytes: usize,
208         ) Allocator.Error!Allocation {
209             std.debug.assert(info_count > 0);
210             const total_bytes = Self.capacity(info_count, name_bytes) catch return error.OutOfMemory;
211             const bytes = allocator.rawAlloc(
212                 total_bytes,
213                 alignment,
214                 @returnAddress(),
215             ) orelse return error.OutOfMemory;
216             const region: *RegionOwner = @ptrCast(@alignCast(bytes));
217             const infos_ptr: [*]Info = @ptrCast(@alignCast(bytes + infos_offset));
218             const infos = infos_ptr[0..info_count];
219             const names_offset = infos_offset + info_count * @sizeOf(Info);
220             const names = bytes[names_offset..total_bytes];
221             std.debug.assert(names.len == name_bytes);
222             region.* = .{
223                 .next = null,
224                 .total_bytes = total_bytes,
225             };
226             return .{
227                 .region = region,
228                 .infos = infos,
229                 .names = names,
230             };
231         }
232 
233         pub fn contains(region: *const RegionOwner, info: *const Info) bool {
234             const region_address = @intFromPtr(region);
235             const end_address = std.math.add(
236                 usize,
237                 region_address,
238                 region.total_bytes,
239             ) catch unreachable;
240             const info_address = @intFromPtr(info);
241             const first_info_address = std.math.add(
242                 usize,
243                 region_address,
244                 infos_offset,
245             ) catch unreachable;
246             return info_address >= first_info_address and info_address < end_address;
247         }
248 
249         pub fn destroy(allocator: Allocator, region: *RegionOwner) void {
250             const bytes: [*]u8 = @ptrCast(region);
251             allocator.rawFree(bytes[0..region.total_bytes], alignment, @returnAddress());
252         }
253     };
254 }
255 
256 pub fn InlineStorage(
257     comptime Info: type,
258     comptime FirstItem: type,
259     comptime first_capacity: usize,
260     comptime SecondItem: type,
261     comptime second_capacity: usize,
262     comptime ThirdItem: type,
263     comptime FourthItem: type,
264 ) type {
265     const alignment_bytes = @max(
266         @alignOf(Info),
267         @max(
268             @alignOf(FirstItem),
269             @max(@alignOf(SecondItem), @max(@alignOf(ThirdItem), @alignOf(FourthItem))),
270         ),
271     );
272     const alignment = std.mem.Alignment.fromByteUnits(alignment_bytes);
273     const first_offset = std.mem.alignForward(usize, @sizeOf(Info), @alignOf(FirstItem));
274     const first_bytes = @sizeOf([first_capacity]FirstItem);
275     const second_offset = std.mem.alignForward(
276         usize,
277         first_offset + first_bytes,
278         @alignOf(SecondItem),
279     );
280     const second_bytes = @sizeOf([second_capacity]SecondItem);
281     const third_offset = std.mem.alignForward(
282         usize,
283         second_offset + second_bytes,
284         @alignOf(ThirdItem),
285     );
286 
287     return struct {
288         const Self = @This();
289 
290         pub const BatchStorage = struct {
291             const Region = struct {
292                 next: ?*@This(),
293                 total_bytes: usize,
294             };
295             const region_alignment = std.mem.Alignment.fromByteUnits(
296                 @max(@alignOf(Region), alignment_bytes),
297             );
298             const entries_offset = std.mem.alignForward(
299                 usize,
300                 @sizeOf(Region),
301                 alignment_bytes,
302             );
303 
304             pub const RegionOwner = Region;
305 
306             pub const Capacity = struct {
307                 entry_count: usize = 0,
308                 total_bytes: usize = entries_offset,
309 
310                 pub fn add(
311                     self: *@This(),
312                     name_len: usize,
313                     third_capacity: usize,
314                     fourth_capacity: usize,
315                 ) error{CapacityOverflow}!void {
316                     const entry_bytes = try Self.capacity(
317                         name_len,
318                         third_capacity,
319                         fourth_capacity,
320                     );
321                     const entry_offset = try Self.alignForward(
322                         self.total_bytes,
323                         alignment_bytes,
324                     );
325                     const total_bytes = std.math.add(
326                         usize,
327                         entry_offset,
328                         entry_bytes,
329                     ) catch return error.CapacityOverflow;
330                     const entry_count = std.math.add(
331                         usize,
332                         self.entry_count,
333                         1,
334                     ) catch return error.CapacityOverflow;
335                     self.total_bytes = total_bytes;
336                     self.entry_count = entry_count;
337                 }
338             };
339 
340             pub const Allocation = struct {
341                 region: *RegionOwner,
342                 entries: []u8,
343             };
344 
345             pub const Cursor = struct {
346                 entries: []u8,
347                 offset: usize = 0,
348 
349                 pub fn init(entries: []u8) Cursor {
350                     return .{ .entries = entries };
351                 }
352 
353                 pub fn create(
354                     self: *Cursor,
355                     name: []const u8,
356                     third_capacity: usize,
357                     fourth_capacity: usize,
358                 ) *Info {
359                     const entry_offset = Self.alignForward(
360                         self.offset,
361                         alignment_bytes,
362                     ) catch unreachable;
363                     const entry_bytes = Self.capacity(
364                         name.len,
365                         third_capacity,
366                         fourth_capacity,
367                     ) catch unreachable;
368                     const entry_end = std.math.add(
369                         usize,
370                         entry_offset,
371                         entry_bytes,
372                     ) catch unreachable;
373                     std.debug.assert(entry_end <= self.entries.len);
374                     const info = Self.initInPlace(
375                         self.entries[entry_offset..entry_end],
376                         name,
377                         third_capacity,
378                         fourth_capacity,
379                     );
380                     self.offset = entry_end;
381                     return info;
382                 }
383             };
384 
385             pub fn create(
386                 allocator: Allocator,
387                 capacity_value: Capacity,
388             ) Allocator.Error!Allocation {
389                 std.debug.assert(capacity_value.entry_count > 0);
390                 const bytes = allocator.rawAlloc(
391                     capacity_value.total_bytes,
392                     region_alignment,
393                     @returnAddress(),
394                 ) orelse return error.OutOfMemory;
395                 const region: *RegionOwner = @ptrCast(@alignCast(bytes));
396                 region.* = .{
397                     .next = null,
398                     .total_bytes = capacity_value.total_bytes,
399                 };
400                 return .{
401                     .region = region,
402                     .entries = bytes[entries_offset..capacity_value.total_bytes],
403                 };
404             }
405 
406             pub fn contains(region: *const RegionOwner, info: *const Info) bool {
407                 const region_address = @intFromPtr(region);
408                 const end_address = std.math.add(
409                     usize,
410                     region_address,
411                     region.total_bytes,
412                 ) catch unreachable;
413                 const first_entry_address = std.math.add(
414                     usize,
415                     region_address,
416                     entries_offset,
417                 ) catch unreachable;
418                 const info_address = @intFromPtr(info);
419                 return info_address >= first_entry_address and info_address < end_address;
420             }
421 
422             pub fn destroy(allocator: Allocator, region: *RegionOwner) void {
423                 const bytes: [*]u8 = @ptrCast(region);
424                 allocator.rawFree(
425                     bytes[0..region.total_bytes],
426                     region_alignment,
427                     @returnAddress(),
428                 );
429             }
430         };
431 
432         const Layout = struct {
433             fourth_offset: usize,
434             name_offset: usize,
435             total_bytes: usize,
436         };
437 
438         fn alignForward(value: usize, comptime item_alignment: usize) error{CapacityOverflow}!usize {
439             const mask = item_alignment - 1;
440             const adjusted = std.math.add(usize, value, mask) catch return error.CapacityOverflow;
441             return adjusted & ~mask;
442         }
443 
444         fn layout(
445             name_len: usize,
446             third_capacity: usize,
447             fourth_capacity: usize,
448         ) error{CapacityOverflow}!Layout {
449             const third_bytes = std.math.mul(
450                 usize,
451                 third_capacity,
452                 @sizeOf(ThirdItem),
453             ) catch return error.CapacityOverflow;
454             const third_end = std.math.add(
455                 usize,
456                 third_offset,
457                 third_bytes,
458             ) catch return error.CapacityOverflow;
459             const fourth_offset = try alignForward(third_end, @alignOf(FourthItem));
460             const fourth_bytes = std.math.mul(
461                 usize,
462                 fourth_capacity,
463                 @sizeOf(FourthItem),
464             ) catch return error.CapacityOverflow;
465             const name_offset = std.math.add(
466                 usize,
467                 fourth_offset,
468                 fourth_bytes,
469             ) catch return error.CapacityOverflow;
470             const total_bytes = std.math.add(
471                 usize,
472                 name_offset,
473                 name_len,
474             ) catch return error.CapacityOverflow;
475             return .{
476                 .fourth_offset = fourth_offset,
477                 .name_offset = name_offset,
478                 .total_bytes = total_bytes,
479             };
480         }
481 
482         pub fn capacity(
483             name_len: usize,
484             third_capacity: usize,
485             fourth_capacity: usize,
486         ) error{CapacityOverflow}!usize {
487             return (try layout(name_len, third_capacity, fourth_capacity)).total_bytes;
488         }
489 
490         pub fn create(
491             allocator: Allocator,
492             name: []const u8,
493             third_capacity: usize,
494             fourth_capacity: usize,
495         ) Allocator.Error!*Info {
496             const entry_layout = Self.layout(
497                 name.len,
498                 third_capacity,
499                 fourth_capacity,
500             ) catch return error.OutOfMemory;
501             const bytes = allocator.rawAlloc(
502                 entry_layout.total_bytes,
503                 alignment,
504                 @returnAddress(),
505             ) orelse return error.OutOfMemory;
506             return initialize(
507                 bytes[0..entry_layout.total_bytes],
508                 entry_layout,
509                 name,
510                 third_capacity,
511                 fourth_capacity,
512             );
513         }
514 
515         pub fn initInPlace(
516             bytes: []u8,
517             name: []const u8,
518             third_capacity: usize,
519             fourth_capacity: usize,
520         ) *Info {
521             const entry_layout = Self.layout(
522                 name.len,
523                 third_capacity,
524                 fourth_capacity,
525             ) catch unreachable;
526             std.debug.assert(bytes.len == entry_layout.total_bytes);
527             std.debug.assert(@intFromPtr(bytes.ptr) % alignment_bytes == 0);
528             return initialize(
529                 bytes,
530                 entry_layout,
531                 name,
532                 third_capacity,
533                 fourth_capacity,
534             );
535         }
536 
537         fn initialize(
538             bytes: []u8,
539             entry_layout: Layout,
540             name: []const u8,
541             third_capacity: usize,
542             fourth_capacity: usize,
543         ) *Info {
544             const info: *Info = @ptrCast(@alignCast(bytes));
545             const first_items: *[first_capacity]FirstItem = @ptrCast(@alignCast(bytes.ptr + first_offset));
546             const second_items: *[second_capacity]SecondItem = @ptrCast(@alignCast(bytes.ptr + second_offset));
547             const third_items_ptr: [*]ThirdItem = @ptrCast(@alignCast(bytes.ptr + third_offset));
548             const third_items = third_items_ptr[0..third_capacity];
549             const fourth_items_ptr: [*]FourthItem = @ptrCast(
550                 @alignCast(bytes.ptr + entry_layout.fourth_offset),
551             );
552             const fourth_items = fourth_items_ptr[0..fourth_capacity];
553             const owned_name = bytes[entry_layout.name_offset..entry_layout.total_bytes];
554             std.debug.assert(owned_name.len == name.len);
555             @memcpy(owned_name, name);
556             info.* = Info.initEntryStorage(
557                 owned_name,
558                 first_items,
559                 second_items,
560                 third_items,
561                 fourth_items,
562             );
563             return info;
564         }
565 
566         pub fn destroy(allocator: Allocator, info: *Info) void {
567             const bytes: [*]u8 = @ptrCast(info);
568             const minimum_name_address = std.math.add(
569                 usize,
570                 @intFromPtr(info),
571                 third_offset,
572             ) catch unreachable;
573             const name_address = @intFromPtr(info.name.ptr);
574             std.debug.assert(name_address >= minimum_name_address);
575             const end_address = std.math.add(
576                 usize,
577                 name_address,
578                 info.name.len,
579             ) catch unreachable;
580             const total_bytes = std.math.sub(
581                 usize,
582                 end_address,
583                 @intFromPtr(info),
584             ) catch unreachable;
585             info.deinit(allocator);
586             info.* = undefined;
587             allocator.rawFree(bytes[0..total_bytes], alignment, @returnAddress());
588         }
589     };
590 }
591 
592 test "registry entry storage acquires one exact region and retries" {
593     const testing = std.testing;
594     const Owner = Storage(TestInfo);
595 
596     const expected_bytes = std.math.add(usize, @sizeOf(TestInfo), 4) catch unreachable;
597     try testing.expectEqual(expected_bytes, try Owner.capacity(4));
598     try testing.expectError(error.CapacityOverflow, Owner.capacity(std.math.maxInt(usize)));
599 
600     var failing = testing.FailingAllocator.init(testing.allocator, .{ .fail_index = 0 });
601     try testing.expectError(error.OutOfMemory, Owner.create(failing.allocator(), "name"));
602 
603     failing.fail_index = std.math.maxInt(usize);
604     const before = failing.alloc_index;
605     const before_allocated = failing.allocated_bytes;
606     const before_freed = failing.freed_bytes;
607     {
608         const info = try Owner.create(failing.allocator(), "name");
609         defer Owner.destroy(failing.allocator(), info);
610         try testing.expectEqual(
611             std.math.add(usize, before, 1) catch unreachable,
612             failing.alloc_index,
613         );
614         try testing.expectEqualStrings("name", info.name);
615         const name_address = std.math.add(
616             usize,
617             @intFromPtr(info),
618             @sizeOf(TestInfo),
619         ) catch unreachable;
620         try testing.expectEqual(name_address, @intFromPtr(info.name.ptr));
621     }
622     try testing.expectEqual(
623         std.math.add(usize, before_allocated, expected_bytes) catch unreachable,
624         failing.allocated_bytes,
625     );
626     try testing.expectEqual(
627         std.math.add(usize, before_freed, expected_bytes) catch unreachable,
628         failing.freed_bytes,
629     );
630 }
631 
632 test "registry entry batch storage acquires one exact region" {
633     const testing = std.testing;
634     const Owner = BatchStorage(TestInfo);
635 
636     const info_count = 2;
637     const name_bytes = 7;
638     const infos_offset = std.mem.alignForward(
639         usize,
640         @sizeOf(Owner.RegionOwner),
641         @alignOf(TestInfo),
642     );
643     const expected_bytes = infos_offset + info_count * @sizeOf(TestInfo) + name_bytes;
644     try testing.expectEqual(expected_bytes, try Owner.capacity(info_count, name_bytes));
645     try testing.expectError(error.CapacityOverflow, Owner.capacity(std.math.maxInt(usize), 0));
646     try testing.expectError(error.CapacityOverflow, Owner.capacity(1, std.math.maxInt(usize)));
647 
648     var failing = testing.FailingAllocator.init(testing.allocator, .{ .fail_index = 0 });
649     try testing.expectError(error.OutOfMemory, Owner.create(failing.allocator(), info_count, name_bytes));
650 
651     failing.fail_index = std.math.maxInt(usize);
652     const before = failing.alloc_index;
653     const before_allocated = failing.allocated_bytes;
654     const before_freed = failing.freed_bytes;
655     {
656         const allocation = try Owner.create(failing.allocator(), info_count, name_bytes);
657         defer Owner.destroy(failing.allocator(), allocation.region);
658         try testing.expectEqual(before + 1, failing.alloc_index);
659         try testing.expectEqual(expected_bytes, allocation.region.total_bytes);
660         try testing.expectEqual(info_count, allocation.infos.len);
661         try testing.expectEqual(name_bytes, allocation.names.len);
662         try testing.expectEqual(
663             @intFromPtr(allocation.region) + infos_offset,
664             @intFromPtr(allocation.infos.ptr),
665         );
666         try testing.expect(Owner.contains(allocation.region, &allocation.infos[0]));
667         try testing.expect(Owner.contains(allocation.region, &allocation.infos[1]));
668         const outside = TestInfo.init("outside");
669         try testing.expect(!Owner.contains(allocation.region, &outside));
670     }
671     try testing.expectEqual(before_allocated + expected_bytes, failing.allocated_bytes);
672     try testing.expectEqual(before_freed + expected_bytes, failing.freed_bytes);
673 }
674 
675 test "registry entry inline storage acquires one exact variable region" {
676     const testing = std.testing;
677     const Owner = InlineStorage(TestInlineInfo, u64, 4, u32, 2, u16, u8);
678     const first_offset = std.mem.alignForward(usize, @sizeOf(TestInlineInfo), @alignOf(u64));
679     const second_offset = std.mem.alignForward(usize, first_offset + 4 * @sizeOf(u64), @alignOf(u32));
680     const third_offset = std.mem.alignForward(usize, second_offset + 2 * @sizeOf(u32), @alignOf(u16));
681     const fourth_offset = std.mem.alignForward(usize, third_offset + 3 * @sizeOf(u16), @alignOf(u8));
682     const name_offset = fourth_offset + 5 * @sizeOf(u8);
683     const expected_bytes = name_offset + 4;
684 
685     try testing.expectEqual(expected_bytes, try Owner.capacity(4, 3, 5));
686     try testing.expectError(
687         error.CapacityOverflow,
688         Owner.capacity(4, std.math.maxInt(usize), 5),
689     );
690 
691     var failing = testing.FailingAllocator.init(testing.allocator, .{ .fail_index = 0 });
692     try testing.expectError(error.OutOfMemory, Owner.create(failing.allocator(), "name", 3, 5));
693 
694     failing.fail_index = std.math.maxInt(usize);
695     const before_allocated = failing.allocated_bytes;
696     const before_freed = failing.freed_bytes;
697     {
698         const info = try Owner.create(failing.allocator(), "name", 3, 5);
699         defer Owner.destroy(failing.allocator(), info);
700         try testing.expectEqualStrings("name", info.name);
701         try testing.expectEqual(@as(usize, 0), info.first_items.len);
702         try testing.expectEqual(@as(usize, 0), info.second_items.len);
703         try testing.expectEqual(@as(usize, 0), info.third_items.len);
704         try testing.expectEqual(@as(usize, 0), info.fourth_items.len);
705         const first_address = std.math.add(usize, @intFromPtr(info), first_offset) catch unreachable;
706         try testing.expectEqual(first_address, @intFromPtr(info.first_items.ptr));
707         const second_address = std.math.add(usize, @intFromPtr(info), second_offset) catch unreachable;
708         try testing.expectEqual(second_address, @intFromPtr(info.second_items.ptr));
709         const third_address = std.math.add(usize, @intFromPtr(info), third_offset) catch unreachable;
710         try testing.expectEqual(third_address, @intFromPtr(info.third_items.ptr));
711         const fourth_address = std.math.add(usize, @intFromPtr(info), fourth_offset) catch unreachable;
712         try testing.expectEqual(fourth_address, @intFromPtr(info.fourth_items.ptr));
713         const name_address = std.math.add(usize, @intFromPtr(info), name_offset) catch unreachable;
714         try testing.expectEqual(name_address, @intFromPtr(info.name.ptr));
715     }
716     try testing.expectEqual(
717         std.math.add(usize, before_allocated, expected_bytes) catch unreachable,
718         failing.allocated_bytes,
719     );
720     try testing.expectEqual(
721         std.math.add(usize, before_freed, expected_bytes) catch unreachable,
722         failing.freed_bytes,
723     );
724 }
725 
726 test "registry entry inline batch storage acquires one exact aligned region" {
727     const testing = std.testing;
728     const Owner = InlineStorage(TestInlineInfo, u64, 4, u32, 2, u16, u8);
729     const Batch = Owner.BatchStorage;
730     const entry_alignment = @alignOf(u64);
731     const entries_offset = std.mem.alignForward(
732         usize,
733         @sizeOf(Batch.RegionOwner),
734         entry_alignment,
735     );
736     const first_bytes = try Owner.capacity(3, 3, 5);
737     const second_offset = std.mem.alignForward(
738         usize,
739         entries_offset + first_bytes,
740         entry_alignment,
741     );
742     const second_bytes = try Owner.capacity(6, 1, 2);
743     const expected_bytes = second_offset + second_bytes;
744 
745     var capacity_value: Batch.Capacity = .{};
746     try capacity_value.add(3, 3, 5);
747     try capacity_value.add(6, 1, 2);
748     try testing.expectEqual(@as(usize, 2), capacity_value.entry_count);
749     try testing.expectEqual(expected_bytes, capacity_value.total_bytes);
750 
751     var overflowing: Batch.Capacity = .{};
752     try testing.expectError(
753         error.CapacityOverflow,
754         overflowing.add(std.math.maxInt(usize), 0, 0),
755     );
756     var count_overflow = Batch.Capacity{
757         .entry_count = std.math.maxInt(usize),
758     };
759     try testing.expectError(
760         error.CapacityOverflow,
761         count_overflow.add(0, 0, 0),
762     );
763     var total_overflow = Batch.Capacity{
764         .total_bytes = std.math.maxInt(usize),
765     };
766     try testing.expectError(
767         error.CapacityOverflow,
768         total_overflow.add(0, 0, 0),
769     );
770 
771     var failing = testing.FailingAllocator.init(testing.allocator, .{ .fail_index = 0 });
772     try testing.expectError(
773         error.OutOfMemory,
774         Batch.create(failing.allocator(), capacity_value),
775     );
776 
777     failing.fail_index = std.math.maxInt(usize);
778     const before = failing.alloc_index;
779     const before_allocated = failing.allocated_bytes;
780     const before_freed = failing.freed_bytes;
781     {
782         const allocation = try Batch.create(failing.allocator(), capacity_value);
783         defer Batch.destroy(failing.allocator(), allocation.region);
784         try testing.expectEqual(before + 1, failing.alloc_index);
785         try testing.expectEqual(expected_bytes, allocation.region.total_bytes);
786         try testing.expectEqual(
787             @intFromPtr(allocation.region) + entries_offset,
788             @intFromPtr(allocation.entries.ptr),
789         );
790 
791         var cursor = Batch.Cursor.init(allocation.entries);
792         const first = cursor.create("one", 3, 5);
793         const second = cursor.create("second", 1, 2);
794         try testing.expectEqual(allocation.entries.len, cursor.offset);
795         try testing.expectEqual(@as(usize, 0), @intFromPtr(first) % entry_alignment);
796         try testing.expectEqual(@as(usize, 0), @intFromPtr(second) % entry_alignment);
797         try testing.expectEqualStrings("one", first.name);
798         try testing.expectEqualStrings("second", second.name);
799         try testing.expect(Batch.contains(allocation.region, first));
800         try testing.expect(Batch.contains(allocation.region, second));
801         var outside: TestInlineInfo = undefined;
802         try testing.expect(!Batch.contains(allocation.region, &outside));
803         first.deinit(testing.allocator);
804         second.deinit(testing.allocator);
805         first.* = undefined;
806         second.* = undefined;
807     }
808     try testing.expectEqual(before_allocated + expected_bytes, failing.allocated_bytes);
809     try testing.expectEqual(before_freed + expected_bytes, failing.freed_bytes);
810 }