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 }