wip deserialize

This commit is contained in:
Richard Feldman 2025-07-28 09:42:25 -04:00
parent 7329b3f668
commit 0fb792432e
No known key found for this signature in database
2 changed files with 198 additions and 80 deletions

View file

@ -94,6 +94,58 @@ pub const Store = struct {
attributes: collections.SafeList(Attributes) = .{},
next_unique_name: u32 = 0,
/// Serialized representation of a Store
pub const Serialized = struct {
interner: SmallStringInterner,
attributes: collections.SafeList(Attributes).Serialized,
next_unique_name: u32,
/// Serialize a Store into this Serialized struct, appending data to the writer
pub fn serialize(
self: *Serialized,
store: *const Store,
allocator: std.mem.Allocator,
writer: *collections.CompactWriter,
) std.mem.Allocator.Error!void {
// Serialize the interner
self.interner = (try store.interner.serialize(allocator, writer)).*;
// Serialize the attributes SafeList
const attributes_serialized = try writer.appendAlloc(allocator, collections.SafeList(Attributes).Serialized);
try attributes_serialized.serialize(&store.attributes, allocator, writer);
self.attributes = attributes_serialized.*;
// Copy next_unique_name directly
self.next_unique_name = store.next_unique_name;
}
/// Deserialize this Serialized struct into a Store
pub fn deserialize(self: *Serialized, offset: i64) *Store {
// Debug assert that Serialized is at least as big as Store
std.debug.assert(@sizeOf(Serialized) >= @sizeOf(Store));
// Apply relocations
self.interner.relocate(@intCast(offset));
// Deserialize the attributes SafeList
const attributes = self.attributes.deserialize(offset);
// Build the Store
const store = Store{
.interner = self.interner,
.attributes = attributes.*,
.next_unique_name = self.next_unique_name,
};
// Write the Store to our memory location
const self_ptr = @intFromPtr(self);
const store_ptr = @as(*Store, @ptrFromInt(self_ptr));
store_ptr.* = store;
return store_ptr;
}
};
/// Initialize the memory for an `Ident.Store` with a specific capaicty.
pub fn initCapacity(gpa: std.mem.Allocator, capacity: usize) std.mem.Allocator.Error!Store {
return .{

View file

@ -112,6 +112,60 @@ pub fn SafeList(comptime T: type) type {
nonempty: Range,
};
/// Serialized representation of a SafeList
pub const Serialized = struct {
offset: u64,
len: u64,
capacity: u64,
/// Serialize a SafeList into this Serialized struct, appending data to the writer
pub fn serialize(
self: *Serialized,
safe_list: *const SafeList(T),
allocator: Allocator,
writer: *CompactWriter,
) Allocator.Error!void {
const items = safe_list.items.items;
// Append the slice data first
const slice_ptr = try writer.appendSlice(allocator, items);
// Store the offset, len, and capacity
self.offset = @intFromPtr(slice_ptr.ptr);
self.len = items.len;
self.capacity = items.len;
}
/// Deserialize this Serialized struct into a SafeList
pub fn deserialize(self: *Serialized, offset: i64) *SafeList(T) {
// Debug assert that Serialized is at least as big as SafeList
std.debug.assert(@sizeOf(Serialized) >= @sizeOf(SafeList(T)));
// Apply the offset to convert from serialized offset to actual pointer
const adjusted_offset: u64 = if (offset >= 0)
self.offset + @as(u64, @intCast(offset))
else
self.offset - @as(u64, @intCast(-offset));
// Build the SafeList
const items_ptr: [*]T = @ptrFromInt(adjusted_offset);
const items_slice = items_ptr[0..@intCast(self.len)];
const safe_list = SafeList(T){
.items = .{
.items = items_slice,
.capacity = @intCast(self.capacity),
},
};
// Write the SafeList to our memory location
const self_ptr = @intFromPtr(self);
const safe_list_ptr = @as(*SafeList(T), @ptrFromInt(self_ptr));
safe_list_ptr.* = safe_list;
return safe_list_ptr;
}
};
/// Initialize the `SafeList` with the specified capacity.
pub fn initCapacity(gpa: Allocator, capacity: usize) std.mem.Allocator.Error!SafeList(T) {
return .{
@ -1371,7 +1425,9 @@ test "SafeList(u32) CompactWriter roundtrip with file" {
};
defer writer.deinit(gpa);
_ = try original.serialize(gpa, &writer);
// Allocate and serialize using SafeList.Serialized
const serialized = try writer.appendAlloc(gpa, SafeList(u32).Serialized);
try serialized.serialize(&original, gpa, &writer);
// Write to file
try writer.writeGather(gpa, file);
@ -1384,13 +1440,12 @@ test "SafeList(u32) CompactWriter roundtrip with file" {
_ = try file.read(buffer);
// The key insight: we can just cast the buffer to a SafeList pointer
// The layout in memory is exactly what we need
const deserialized = @as(*SafeList(u32), @ptrCast(@alignCast(buffer.ptr + writer.total_bytes - @sizeOf(SafeList(u32)))));
// The key insight: we can just cast the buffer to a SafeList.Serialized pointer
const serialized_ptr = @as(*SafeList(u32).Serialized, @ptrCast(@alignCast(buffer.ptr + writer.total_bytes - @sizeOf(SafeList(u32).Serialized))));
// Relocate the pointers - this adjusts the internal pointer from offset to actual memory address
// Deserialize - this converts the Serialized struct to a SafeList
const base_addr = @intFromPtr(buffer.ptr);
deserialized.relocate(@as(isize, @intCast(base_addr)));
const deserialized = serialized_ptr.deserialize(@as(i64, @intCast(base_addr)));
// Verify the data matches
try testing.expectEqual(original.len(), deserialized.len());
@ -1430,7 +1485,9 @@ test "SafeList(struct) CompactWriter roundtrip with file" {
};
defer writer.deinit(gpa);
_ = try original.serialize(gpa, &writer);
// Allocate and serialize using SafeList.Serialized
const serialized = try writer.appendAlloc(gpa, SafeList(Point).Serialized);
try serialized.serialize(&original, gpa, &writer);
// Write to file
try writer.writeGather(gpa, file);
@ -1443,9 +1500,9 @@ test "SafeList(struct) CompactWriter roundtrip with file" {
_ = try file.read(buffer);
// Cast and relocate
const deserialized = @as(*SafeList(Point), @ptrCast(@alignCast(buffer.ptr + writer.total_bytes - @sizeOf(SafeList(Point)))));
deserialized.relocate(@as(isize, @intCast(@intFromPtr(buffer.ptr))));
// Cast to SafeList.Serialized and deserialize
const serialized_ptr = @as(*SafeList(Point).Serialized, @ptrCast(@alignCast(buffer.ptr + writer.total_bytes - @sizeOf(SafeList(Point).Serialized))));
const deserialized = serialized_ptr.deserialize(@as(i64, @intCast(@intFromPtr(buffer.ptr))));
// Verify the data
try testing.expectEqual(@as(usize, 3), deserialized.len());
@ -1478,7 +1535,9 @@ test "SafeList empty list CompactWriter roundtrip" {
};
defer writer.deinit(gpa);
_ = try original.serialize(gpa, &writer);
// Allocate and serialize using SafeList.Serialized
const serialized = try writer.appendAlloc(gpa, SafeList(u64).Serialized);
try serialized.serialize(&original, gpa, &writer);
// Write to file
try writer.writeGather(gpa, file);
@ -1491,9 +1550,9 @@ test "SafeList empty list CompactWriter roundtrip" {
_ = try file.read(buffer);
// Cast and relocate - empty list should still work
const deserialized = @as(*SafeList(u64), @ptrCast(@alignCast(buffer.ptr + writer.total_bytes - @sizeOf(SafeList(u64)))));
deserialized.relocate(@as(isize, @intCast(@intFromPtr(buffer.ptr))));
// Cast to SafeList.Serialized and deserialize - empty list should still work
const serialized_ptr = @as(*SafeList(u64).Serialized, @ptrCast(@alignCast(buffer.ptr + writer.total_bytes - @sizeOf(SafeList(u64).Serialized))));
const deserialized = serialized_ptr.deserialize(@as(i64, @intCast(@intFromPtr(buffer.ptr))));
// Verify empty
try testing.expectEqual(@as(usize, 0), deserialized.len());
@ -1541,14 +1600,17 @@ test "SafeList empty lists CompactWriter roundtrip multiple types" {
};
defer writer.deinit(gpa);
_ = try list1.serialize(gpa, &writer);
const offset1 = writer.total_bytes - @sizeOf(SafeList(T));
const serialized1 = try writer.appendAlloc(gpa, SafeList(T).Serialized);
try serialized1.serialize(&list1, gpa, &writer);
const offset1 = writer.total_bytes - @sizeOf(SafeList(T).Serialized);
_ = try list_u8.serialize(gpa, &writer);
const offset_u8 = writer.total_bytes - @sizeOf(SafeList(u8));
const serialized_u8 = try writer.appendAlloc(gpa, SafeList(u8).Serialized);
try serialized_u8.serialize(&list_u8, gpa, &writer);
const offset_u8 = writer.total_bytes - @sizeOf(SafeList(u8).Serialized);
_ = try list2.serialize(gpa, &writer);
const offset2 = writer.total_bytes - @sizeOf(SafeList(T));
const serialized2 = try writer.appendAlloc(gpa, SafeList(T).Serialized);
try serialized2.serialize(&list2, gpa, &writer);
const offset2 = writer.total_bytes - @sizeOf(SafeList(T).Serialized);
// Write to file
try writer.writeGather(gpa, file);
@ -1564,19 +1626,19 @@ test "SafeList empty lists CompactWriter roundtrip multiple types" {
const base = @intFromPtr(buffer.ptr);
// Verify first empty list
const d1 = @as(*SafeList(T), @ptrCast(@alignCast(buffer.ptr + offset1)));
d1.relocate(@as(isize, @intCast(base)));
const s1 = @as(*SafeList(T).Serialized, @ptrCast(@alignCast(buffer.ptr + offset1)));
const d1 = s1.deserialize(@as(i64, @intCast(base)));
try testing.expectEqual(@as(usize, 0), d1.len());
// Verify non-empty u8 list
const d_u8 = @as(*SafeList(u8), @ptrCast(@alignCast(buffer.ptr + offset_u8)));
d_u8.relocate(@as(isize, @intCast(base)));
const s_u8 = @as(*SafeList(u8).Serialized, @ptrCast(@alignCast(buffer.ptr + offset_u8)));
const d_u8 = s_u8.deserialize(@as(i64, @intCast(base)));
try testing.expectEqual(@as(usize, 1), d_u8.len());
try testing.expectEqual(@as(u8, 123), d_u8.get(@enumFromInt(0)).*);
// Verify second empty list
const d2 = @as(*SafeList(T), @ptrCast(@alignCast(buffer.ptr + offset2)));
d2.relocate(@as(isize, @intCast(base)));
const s2 = @as(*SafeList(T).Serialized, @ptrCast(@alignCast(buffer.ptr + offset2)));
const d2 = s2.deserialize(@as(i64, @intCast(base)));
try testing.expectEqual(@as(usize, 0), d2.len());
}
}
@ -1600,12 +1662,13 @@ test "SafeList CompactWriter verify offset calculation" {
};
defer writer.deinit(gpa);
const serialized_ptr = try list.serialize(gpa, &writer);
const serialized = try writer.appendAlloc(gpa, SafeList(u16).Serialized);
try serialized.serialize(&list, gpa, &writer);
// The offset should be the aligned size of the data
// 4 items * 2 bytes = 8 bytes, which is already aligned to 8
const expected_offset = 8;
try testing.expectEqual(@as(usize, expected_offset), @intFromPtr(serialized_ptr.items.items.ptr));
try testing.expectEqual(@as(u64, expected_offset), serialized.offset);
}
test "SafeList CompactWriter complete roundtrip example" {
@ -1633,11 +1696,12 @@ test "SafeList CompactWriter complete roundtrip example" {
};
defer writer.deinit(gpa);
// Step 3: Serialize - this writes data first, then the SafeList struct
const serialized_ptr = try original.serialize(gpa, &writer);
// Step 3: Serialize - this writes data first, then the SafeList.Serialized struct
const serialized = try writer.appendAlloc(gpa, SafeList(u32).Serialized);
try serialized.serialize(&original, gpa, &writer);
// Verify the offset is correct (4 * 4 = 16 bytes, already aligned to 8)
try testing.expectEqual(@as(usize, 16), @intFromPtr(serialized_ptr.items.items.ptr));
try testing.expectEqual(@as(u64, 16), serialized.offset);
// Step 4: Write to file using vectored I/O
try writer.writeGather(gpa, file);
@ -1650,12 +1714,12 @@ test "SafeList CompactWriter complete roundtrip example" {
_ = try file.read(buffer);
// Step 6: Cast buffer to SafeList - the struct is at the end
const list_offset = writer.total_bytes - @sizeOf(SafeList(u32));
const deserialized = @as(*SafeList(u32), @ptrCast(@alignCast(buffer.ptr + list_offset)));
// Step 6: Cast buffer to SafeList.Serialized - the struct is at the end
const list_offset = writer.total_bytes - @sizeOf(SafeList(u32).Serialized);
const serialized_ptr = @as(*SafeList(u32).Serialized, @ptrCast(@alignCast(buffer.ptr + list_offset)));
// Step 7: Relocate - convert offset to pointer
deserialized.relocate(@as(isize, @intCast(@intFromPtr(buffer.ptr))));
// Step 7: Deserialize - convert offset to pointer
const deserialized = serialized_ptr.deserialize(@as(i64, @intCast(@intFromPtr(buffer.ptr))));
// Step 8: Verify data is accessible and correct
try testing.expectEqual(@as(usize, 4), deserialized.len());
@ -1722,20 +1786,25 @@ test "SafeList CompactWriter multiple lists with different alignments" {
defer writer.deinit(gpa);
// Serialize all lists and track their positions
const ptr_u8 = try list_u8.serialize(gpa, &writer);
const offset_u8 = writer.total_bytes - @sizeOf(SafeList(u8));
const serialized_u8 = try writer.appendAlloc(gpa, SafeList(u8).Serialized);
try serialized_u8.serialize(&list_u8, gpa, &writer);
const offset_u8 = writer.total_bytes - @sizeOf(SafeList(u8).Serialized);
const ptr_u16 = try list_u16.serialize(gpa, &writer);
const offset_u16 = writer.total_bytes - @sizeOf(SafeList(u16));
const serialized_u16 = try writer.appendAlloc(gpa, SafeList(u16).Serialized);
try serialized_u16.serialize(&list_u16, gpa, &writer);
const offset_u16 = writer.total_bytes - @sizeOf(SafeList(u16).Serialized);
const ptr_u32 = try list_u32.serialize(gpa, &writer);
const offset_u32 = writer.total_bytes - @sizeOf(SafeList(u32));
const serialized_u32 = try writer.appendAlloc(gpa, SafeList(u32).Serialized);
try serialized_u32.serialize(&list_u32, gpa, &writer);
const offset_u32 = writer.total_bytes - @sizeOf(SafeList(u32).Serialized);
const ptr_u64 = try list_u64.serialize(gpa, &writer);
const offset_u64 = writer.total_bytes - @sizeOf(SafeList(u64));
const serialized_u64 = try writer.appendAlloc(gpa, SafeList(u64).Serialized);
try serialized_u64.serialize(&list_u64, gpa, &writer);
const offset_u64 = writer.total_bytes - @sizeOf(SafeList(u64).Serialized);
const ptr_struct = try list_struct.serialize(gpa, &writer);
const offset_struct = writer.total_bytes - @sizeOf(SafeList(AlignedStruct));
const serialized_struct = try writer.appendAlloc(gpa, SafeList(AlignedStruct).Serialized);
try serialized_struct.serialize(&list_struct, gpa, &writer);
const offset_struct = writer.total_bytes - @sizeOf(SafeList(AlignedStruct).Serialized);
// Write to file
try writer.writeGather(gpa, file);
@ -1752,23 +1821,23 @@ test "SafeList CompactWriter multiple lists with different alignments" {
const base_addr = @intFromPtr(buffer.ptr);
// 1. Deserialize u8 list
const deser_u8 = @as(*SafeList(u8), @ptrCast(@alignCast(buffer.ptr + offset_u8)));
deser_u8.relocate(@as(isize, @intCast(base_addr)));
const s_u8 = @as(*SafeList(u8).Serialized, @ptrCast(@alignCast(buffer.ptr + offset_u8)));
const deser_u8 = s_u8.deserialize(@as(i64, @intCast(base_addr)));
try testing.expectEqual(@as(usize, 3), deser_u8.len());
try testing.expectEqual(@as(u8, 10), deser_u8.get(@enumFromInt(0)).*);
try testing.expectEqual(@as(u8, 20), deser_u8.get(@enumFromInt(1)).*);
try testing.expectEqual(@as(u8, 30), deser_u8.get(@enumFromInt(2)).*);
// 2. Deserialize u16 list
const deser_u16 = @as(*SafeList(u16), @ptrCast(@alignCast(buffer.ptr + offset_u16)));
deser_u16.relocate(@as(isize, @intCast(base_addr)));
const s_u16 = @as(*SafeList(u16).Serialized, @ptrCast(@alignCast(buffer.ptr + offset_u16)));
const deser_u16 = s_u16.deserialize(@as(i64, @intCast(base_addr)));
try testing.expectEqual(@as(usize, 2), deser_u16.len());
try testing.expectEqual(@as(u16, 1000), deser_u16.get(@enumFromInt(0)).*);
try testing.expectEqual(@as(u16, 2000), deser_u16.get(@enumFromInt(1)).*);
// 3. Deserialize u32 list
const deser_u32 = @as(*SafeList(u32), @ptrCast(@alignCast(buffer.ptr + offset_u32)));
deser_u32.relocate(@as(isize, @intCast(base_addr)));
const s_u32 = @as(*SafeList(u32).Serialized, @ptrCast(@alignCast(buffer.ptr + offset_u32)));
const deser_u32 = s_u32.deserialize(@as(i64, @intCast(base_addr)));
try testing.expectEqual(@as(usize, 4), deser_u32.len());
try testing.expectEqual(@as(u32, 100_000), deser_u32.get(@enumFromInt(0)).*);
try testing.expectEqual(@as(u32, 200_000), deser_u32.get(@enumFromInt(1)).*);
@ -1776,15 +1845,15 @@ test "SafeList CompactWriter multiple lists with different alignments" {
try testing.expectEqual(@as(u32, 400_000), deser_u32.get(@enumFromInt(3)).*);
// 4. Deserialize u64 list
const deser_u64 = @as(*SafeList(u64), @ptrCast(@alignCast(buffer.ptr + offset_u64)));
deser_u64.relocate(@as(isize, @intCast(base_addr)));
const s_u64 = @as(*SafeList(u64).Serialized, @ptrCast(@alignCast(buffer.ptr + offset_u64)));
const deser_u64 = s_u64.deserialize(@as(i64, @intCast(base_addr)));
try testing.expectEqual(@as(usize, 2), deser_u64.len());
try testing.expectEqual(@as(u64, 10_000_000_000), deser_u64.get(@enumFromInt(0)).*);
try testing.expectEqual(@as(u64, 20_000_000_000), deser_u64.get(@enumFromInt(1)).*);
// 5. Deserialize struct list
const deser_struct = @as(*SafeList(AlignedStruct), @ptrCast(@alignCast(buffer.ptr + offset_struct)));
deser_struct.relocate(@as(isize, @intCast(base_addr)));
const s_struct = @as(*SafeList(AlignedStruct).Serialized, @ptrCast(@alignCast(buffer.ptr + offset_struct)));
const deser_struct = s_struct.deserialize(@as(i64, @intCast(base_addr)));
try testing.expectEqual(@as(usize, 2), deser_struct.len());
const item0 = deser_struct.get(@enumFromInt(0));
@ -1796,13 +1865,6 @@ test "SafeList CompactWriter multiple lists with different alignments" {
try testing.expectEqual(@as(u32, 99), item1.x);
try testing.expectEqual(@as(u64, 9999), item1.y);
try testing.expectEqual(@as(u8, 128), item1.z);
// Verify the serialized pointers match what we deserialized
_ = ptr_u8;
_ = ptr_u16;
_ = ptr_u32;
_ = ptr_u64;
_ = ptr_struct;
}
test "SafeList CompactWriter interleaved pattern with alignment tracking" {
@ -1838,8 +1900,9 @@ test "SafeList CompactWriter interleaved pattern with alignment tracking" {
_ = try list1.append(gpa, 3);
const start1 = writer.total_bytes;
_ = try list1.serialize(gpa, &writer);
try offsets.append(writer.total_bytes - @sizeOf(SafeList(u8)));
const serialized1 = try writer.appendAlloc(gpa, SafeList(u8).Serialized);
try serialized1.serialize(&list1, gpa, &writer);
try offsets.append(writer.total_bytes - @sizeOf(SafeList(u8).Serialized));
// 2. u64 list (8-byte aligned, forces significant padding)
var list2 = try SafeList(u64).initCapacity(gpa, 2);
@ -1848,11 +1911,12 @@ test "SafeList CompactWriter interleaved pattern with alignment tracking" {
_ = try list2.append(gpa, 2_000_000);
const start2 = writer.total_bytes;
_ = try list2.serialize(gpa, &writer);
try offsets.append(writer.total_bytes - @sizeOf(SafeList(u64)));
const serialized2 = try writer.appendAlloc(gpa, SafeList(u64).Serialized);
try serialized2.serialize(&list2, gpa, &writer);
try offsets.append(writer.total_bytes - @sizeOf(SafeList(u64).Serialized));
// Verify padding was added before u64 data
const padding_before_u64 = start2 - start1 - (3 + @sizeOf(SafeList(u8)));
const padding_before_u64 = start2 - start1 - (3 + @sizeOf(SafeList(u8).Serialized));
try testing.expect(padding_before_u64 > 0);
// 3. u16 list (2-byte aligned)
@ -1863,16 +1927,18 @@ test "SafeList CompactWriter interleaved pattern with alignment tracking" {
_ = try list3.append(gpa, 300);
_ = try list3.append(gpa, 400);
_ = try list3.serialize(gpa, &writer);
try offsets.append(writer.total_bytes - @sizeOf(SafeList(u16)));
const serialized3 = try writer.appendAlloc(gpa, SafeList(u16).Serialized);
try serialized3.serialize(&list3, gpa, &writer);
try offsets.append(writer.total_bytes - @sizeOf(SafeList(u16).Serialized));
// 4. u32 list (4-byte aligned)
var list4 = try SafeList(u32).initCapacity(gpa, 1);
defer list4.deinit(gpa);
_ = try list4.append(gpa, 42);
_ = try list4.serialize(gpa, &writer);
try offsets.append(writer.total_bytes - @sizeOf(SafeList(u32)));
const serialized4 = try writer.appendAlloc(gpa, SafeList(u32).Serialized);
try serialized4.serialize(&list4, gpa, &writer);
try offsets.append(writer.total_bytes - @sizeOf(SafeList(u32).Serialized));
// Write to file
try writer.writeGather(gpa, file);
@ -1887,23 +1953,23 @@ test "SafeList CompactWriter interleaved pattern with alignment tracking" {
const base = @intFromPtr(buffer.ptr);
// Deserialize and verify all lists
const d1 = @as(*SafeList(u8), @ptrCast(@alignCast(buffer.ptr + offsets.items[0])));
d1.relocate(@as(isize, @intCast(base)));
const s1 = @as(*SafeList(u8).Serialized, @ptrCast(@alignCast(buffer.ptr + offsets.items[0])));
const d1 = s1.deserialize(@as(i64, @intCast(base)));
try testing.expectEqual(@as(usize, 3), d1.len());
try testing.expectEqual(@as(u8, 1), d1.get(@enumFromInt(0)).*);
const d2 = @as(*SafeList(u64), @ptrCast(@alignCast(buffer.ptr + offsets.items[1])));
d2.relocate(@as(isize, @intCast(base)));
const s2 = @as(*SafeList(u64).Serialized, @ptrCast(@alignCast(buffer.ptr + offsets.items[1])));
const d2 = s2.deserialize(@as(i64, @intCast(base)));
try testing.expectEqual(@as(usize, 2), d2.len());
try testing.expectEqual(@as(u64, 1_000_000), d2.get(@enumFromInt(0)).*);
const d3 = @as(*SafeList(u16), @ptrCast(@alignCast(buffer.ptr + offsets.items[2])));
d3.relocate(@as(isize, @intCast(base)));
const s3 = @as(*SafeList(u16).Serialized, @ptrCast(@alignCast(buffer.ptr + offsets.items[2])));
const d3 = s3.deserialize(@as(i64, @intCast(base)));
try testing.expectEqual(@as(usize, 4), d3.len());
try testing.expectEqual(@as(u16, 100), d3.get(@enumFromInt(0)).*);
const d4 = @as(*SafeList(u32), @ptrCast(@alignCast(buffer.ptr + offsets.items[3])));
d4.relocate(@as(isize, @intCast(base)));
const s4 = @as(*SafeList(u32).Serialized, @ptrCast(@alignCast(buffer.ptr + offsets.items[3])));
const d4 = s4.deserialize(@as(i64, @intCast(base)));
try testing.expectEqual(@as(usize, 1), d4.len());
try testing.expectEqual(@as(u32, 42), d4.get(@enumFromInt(0)).*);
}